initPool 方法如下:如果发现新的master节点与当前的master不同,则重新初始化。
private void initPool(HostAndPort master) { if (!master.equals(currentHostMaster)) { currentHostMaster = master; if (factory == null) { factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout, soTimeout, password, database, clientName, false, null, null, null); initPool(poolConfig, factory); } else { factory.setHostAndPort(currentHostMaster); // although we clear the pool, we still have to check the // returned object // in getResource, this call only clears idle instances, not // borrowed instances internalPool.clear(); } log.info("Created JedisPool to master at " + master); } }通过以上两步,Jedis客户端在只知道哨兵地址的情况下便能获得master节点的地址信息,并且当发生故障转移时能自动切换到新的master节点地址。
Spring Boot 2 整合Redis Cluster模式Spring Boot 2 整合Redis Cluster模式除了配置稍有差异,其它与整合单实例模式也类似,配置示例为
spring: redis: password: passw0rd timeout: 5000 database: 0 cluster: nodes: 192.168.40.201:7100,192.168.40.201:7200,192.168.40.201:7300,192.168.40.201:7400,192.168.40.201:7500,192.168.40.201:7600 max-redirects: 3 # 重定向的最大次数 jedis: pool: max-active: 8 max-wait: -1 max-idle: 8 min-idle: 0完整示例可查阅源码: https://github.com/ronwxy/springboot-demos/tree/master/springboot-redis-cluster
在 一文掌握Redis的三种集群方案 中已经介绍了Cluster模式访问的基本原理,可以通过任意节点跳转到目标节点执行命令,上面配置中 max-redirects 控制在集群中跳转的最大次数。
查看JedisClusterConnection的execute方法,
public Object execute(String command, byte[]... args) { Assert.notNull(command, "Command must not be null!"); Assert.notNull(args, "Args must not be null!"); return clusterCommandExecutor .executeCommandOnArbitraryNode((JedisClusterCommandCallback<Object>) client -> JedisClientUtils.execute(command, EMPTY_2D_BYTE_ARRAY, args, () -> client)) .getValue(); }集群命令的执行是通过ClusterCommandExecutor.executeCommandOnArbitraryNode来实现的,
public <T> NodeResult<T> executeCommandOnArbitraryNode(ClusterCommandCallback<?, T> cmd) { Assert.notNull(cmd, "ClusterCommandCallback must not be null!"); List<RedisClusterNode> nodes = new ArrayList<>(getClusterTopology().getActiveNodes()); return executeCommandOnSingleNode(cmd, nodes.get(new Random().nextInt(nodes.size()))); } private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S, T> cmd, RedisClusterNode node, int redirectCount) { Assert.notNull(cmd, "ClusterCommandCallback must not be null!"); Assert.notNull(node, "RedisClusterNode must not be null!"); if (redirectCount > maxRedirects) { throw new TooManyClusterRedirectionsException(String.format( "Cannot follow Cluster Redirects over more than %s legs. Please consider increasing the number of redirects to follow. Current value is: %s.", redirectCount, maxRedirects)); } RedisClusterNode nodeToUse = lookupNode(node); S client = this.resourceProvider.getResourceForSpecificNode(nodeToUse); Assert.notNull(client, "Could not acquire resource for node. Is your cluster info up to date?"); try { return new NodeResult<>(node, cmd.doInCluster(client)); } catch (RuntimeException ex) { RuntimeException translatedException = convertToDataAccessException(ex); if (translatedException instanceof ClusterRedirectException) { ClusterRedirectException cre = (ClusterRedirectException) translatedException; return executeCommandOnSingleNode(cmd, topologyProvider.getTopology().lookup(cre.getTargetHost(), cre.getTargetPort()), redirectCount + 1); } else { throw translatedException != null ? translatedException : ex; } } finally { this.resourceProvider.returnResourceForSpecificNode(nodeToUse, client); } }上述代码逻辑如下
从集群节点列表中随机选择一个节点
从该节点获取一个客户端连接(如果配置了连接池,从连接池中获取),执行命令
如果抛出ClusterRedirectException异常,则跳转到返回的目标节点上执行
如果跳转次数大于配置的值 max-redirects, 则抛出TooManyClusterRedirectionsException异常
可能遇到的问题Redis连接超时
检查服务是否正常启动(比如 ps -ef|grep redis查看进程,netstat -ano|grep 6379查看端口是否起来,以及日志文件),如果正常启动,则查看Redis服务器是否开启防火墙,关闭防火墙或配置通行端口。
Cluster模式下,报连接到127.0.0.1被拒绝错误,如 Connection refused: no further information: /127.0.0.1:7600