package com.lambdaworks.redis.masterslave; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.lambdaworks.redis.RedisException; import com.lambdaworks.redis.RedisURI; import com.lambdaworks.redis.api.StatefulRedisConnection; import com.lambdaworks.redis.internal.LettuceAssert; import com.lambdaworks.redis.models.role.RedisInstance; import com.lambdaworks.redis.models.role.RedisNodeDescription; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; /** * Topology provider using Redis Standalone and the {@code INFO REPLICATION} output. Slaves are listed as {@code slaveN=...} * entries. * * @author Mark Paluch * @since 4.1 */ public class MasterSlaveTopologyProvider implements TopologyProvider { public static final Pattern ROLE_PATTERN = Pattern.compile("^role\\:([a-z]+)$", Pattern.MULTILINE); public static final Pattern SLAVE_PATTERN = Pattern.compile("^slave(\\d+)\\:([a-zA-Z\\,\\=\\d\\.\\:]+)$", Pattern.MULTILINE); public static final Pattern MASTER_HOST_PATTERN = Pattern.compile("^master_host\\:([a-zA-Z\\,\\=\\d\\.\\:]+)$", Pattern.MULTILINE); public static final Pattern MASTER_PORT_PATTERN = Pattern.compile("^master_port\\:(\\d+)$", Pattern.MULTILINE); public static final Pattern IP_PATTERN = Pattern.compile("ip\\=([a-zA-Z\\d\\.\\:]+)"); public static final Pattern PORT_PATTERN = Pattern.compile("port\\=([\\d]+)"); private static final InternalLogger logger = InternalLoggerFactory.getInstance(MasterSlaveTopologyProvider.class); private final StatefulRedisConnection<?, ?> connection; private final RedisURI redisURI; /** * Creates a new {@link MasterSlaveTopologyProvider}. * * @param connection must not be {@literal null} * @param redisURI must not be {@literal null} */ public MasterSlaveTopologyProvider(StatefulRedisConnection<?, ?> connection, RedisURI redisURI) { LettuceAssert.notNull(connection, "Redis Connection must not be null"); LettuceAssert.notNull(redisURI, "RedisURI must not be null"); this.connection = connection; this.redisURI = redisURI; } @Override public List<RedisNodeDescription> getNodes() { logger.debug("Performing topology lookup"); String info = connection.sync().info("replication"); try { return getNodesFromInfo(info); } catch (RuntimeException e) { throw new RedisException(e); } } protected List<RedisNodeDescription> getNodesFromInfo(String info) { List<RedisNodeDescription> result = new ArrayList<>(); RedisNodeDescription currentNodeDescription = getCurrentNodeDescription(info); result.add(currentNodeDescription); if (currentNodeDescription.getRole() == RedisInstance.Role.MASTER) { result.addAll(getSlavesFromInfo(info)); } else { result.add(getMasterFromInfo(info)); } return result; } private RedisNodeDescription getCurrentNodeDescription(String info) { Matcher matcher = ROLE_PATTERN.matcher(info); if (!matcher.find()) { throw new IllegalStateException("No role property in info " + info); } return getRedisNodeDescription(matcher); } private List<RedisNodeDescription> getSlavesFromInfo(String info) { List<RedisNodeDescription> slaves = new ArrayList<>(); Matcher matcher = SLAVE_PATTERN.matcher(info); while (matcher.find()) { String group = matcher.group(2); String ip = getNested(IP_PATTERN, group, 1); String port = getNested(PORT_PATTERN, group, 1); slaves.add(new RedisMasterSlaveNode(ip, Integer.parseInt(port), redisURI, RedisInstance.Role.SLAVE)); } return slaves; } private RedisNodeDescription getMasterFromInfo(String info) { Matcher masterHostMatcher = MASTER_HOST_PATTERN.matcher(info); Matcher masterPortMatcher = MASTER_PORT_PATTERN.matcher(info); boolean foundHost = masterHostMatcher.find(); boolean foundPort = masterPortMatcher.find(); if (!foundHost || !foundPort) { throw new IllegalStateException("Cannot resolve master from info " + info); } String host = masterHostMatcher.group(1); int port = Integer.parseInt(masterPortMatcher.group(1)); return new RedisMasterSlaveNode(host, port, redisURI, RedisInstance.Role.MASTER); } private String getNested(Pattern pattern, String string, int group) { Matcher matcher = pattern.matcher(string); if (matcher.find()) { return matcher.group(group); } throw new IllegalArgumentException("Cannot extract group " + group + " with pattern " + pattern + " from " + string); } private RedisNodeDescription getRedisNodeDescription(Matcher matcher) { String roleString = matcher.group(1); RedisInstance.Role role = null; if (RedisInstance.Role.MASTER.name().equalsIgnoreCase(roleString)) { role = RedisInstance.Role.MASTER; } if (RedisInstance.Role.SLAVE.name().equalsIgnoreCase(roleString)) { role = RedisInstance.Role.SLAVE; } if (role == null) { throw new IllegalStateException("Cannot resolve role " + roleString + " to " + RedisInstance.Role.MASTER + " or " + RedisInstance.Role.SLAVE); } return new RedisMasterSlaveNode(redisURI.getHost(), redisURI.getPort(), redisURI, role); } }