Jedis cluster集群初始化源码剖析

环境

jar版本: spring-data-redis-1.8.4-RELEASE.jar、jedis-2.9.0.jar

测试环境: Redis 3.2.8,八个集群节点

applicationContext-redis-cluster.xml 配置文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
  5. <!-- 连接池配置. -->
  6. <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
  7. <!-- 连接池中最大连接数。高版本:maxTotal,低版本:maxActive -->
  8. <property name="maxTotal" value="8" />
  9. <!-- 连接池中最大空闲的连接数. -->
  10. <property name="maxIdle" value="4" />
  11. <!-- 连接池中最少空闲的连接数. -->
  12. <property name="minIdle" value="1" />
  13. <!-- 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时。高版本:maxWaitMillis,低版本:maxWait -->
  14. <property name="maxWaitMillis" value="5000" />
  15. <!-- 连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除. -->
  16. <property name="minEvictableIdleTimeMillis" value="300000" />
  17. <!-- 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3 -->
  18. <property name="numTestsPerEvictionRun" value="3" />
  19. <!-- “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1. -->
  20. <property name="timeBetweenEvictionRunsMillis" value="60000" />
  21. <!-- testOnBorrow:向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值. -->
  22. <!-- testOnReturn:向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值. -->
  23. <!-- testWhileIdle:向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值. -->
  24. <!-- whenExhaustedAction:当“连接池”中active数量达到阀值时,即“链接”资源耗尽时,连接池需要采取的手段, 默认为1(0:抛出异常。1:阻塞,直到有可用链接资源。2:强制创建新的链接资源) -->
  25. </bean>
  26. <bean id="n1" class="org.springframework.data.redis.connection.RedisNode">
  27. <constructor-arg value="127.0.0.1" />
  28. <constructor-arg value="6379" type="int" />
  29. </bean>
  30. <bean id="n2" class="org.springframework.data.redis.connection.RedisNode">
  31. <constructor-arg value="127.0.0.1" />
  32. <constructor-arg value="6380" type="int" />
  33. </bean>
  34. <bean id="n3" class="org.springframework.data.redis.connection.RedisNode">
  35. <constructor-arg value="127.0.0.1" />
  36. <constructor-arg value="6381" type="int" />
  37. </bean>
  38. <bean id="redisClusterConfiguration"
  39. class="org.springframework.data.redis.connection.RedisClusterConfiguration">
  40. <property name="clusterNodes">
  41. <set>
  42. <ref bean="n1" />
  43. <ref bean="n2" />
  44. <ref bean="n3" />
  45. </set>
  46. </property>
  47. <property name="maxRedirects" value="5" />
  48. </bean>
  49. <bean id="jedisConnectionFactory"
  50. class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  51. <constructor-arg ref="redisClusterConfiguration" />
  52. <constructor-arg ref="jedisPoolConfig" />
  53. </bean>
  54. <!-- Spring提供的访问Redis类. -->
  55. <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
  56. <property name="connectionFactory" ref="jedisConnectionFactory" />
  57. <property name="KeySerializer">
  58. <bean
  59. class="org.springframework.data.redis.serializer.StringRedisSerializer" />
  60. </property>
  61. <property name="ValueSerializer">
  62. <bean
  63. class="org.springframework.data.redis.serializer.StringRedisSerializer" />
  64. </property>
  65. <property name="hashKeySerializer">
  66. <bean
  67. class="org.springframework.data.redis.serializer.StringRedisSerializer" />
  68. </property>
  69. <property name="hashValueSerializer">
  70. <bean
  71. class="org.springframework.data.redis.serializer.StringRedisSerializer" />
  72. </property>
  73. </bean>
  74. <!-- Redis配置结束 -->
  75. </beans>

Jedis集群类说明

Jedis与Redis集群交互时,涉及的类可以分为两类,分别如下:

1、Redis集群信息配置类:

类名 说明
redis.clients.jedis.JedisPoolConfig 保存Jedis连接池配置信息
org.springframework.data.redis.connection.RedisNode 保存Redis集群节点信息
org.springframework.data.redis.connection.RedisClusterConfiguration 保存Redis集群配置信息
org.springframework.data.redis.connection.jedis.JedisConnectionFactory Jedis连接工厂,负责创建JedisCluster集群操作类,获取Redis连接对象
org.springframework.data.redis.connection.jedis.JedisClusterConnection 在JedisCluster基础上实现,根据key类型使用具体的Jedis类与Redis进行交互

2、Redis集群信息操作类:

类名 说明
redis.clients.jedis.JedisCluster 扩展了BinaryJedisCluster类,负责与Redis集群进行String类型的key交互
redis.clients.jedis.BinaryJedisCluster JedisCluster的父类,负责与Redis集群进行byte[]类型的key交互
redis.clients.jedis.JedisSlotBasedConnectionHandler JedisClusterConnectionHandler类的子类,负责根据key的slot值获取Redis连接
redis.clients.jedis.JedisClusterConnectionHandler 一个抽象类,负责初始化、重建、重置Redis slot槽缓存
redis.clients.jedis.JedisClusterInfoCache Redis slot缓存类,负责保存、重建和自动发现Redis slot槽与集群节点的关系

Jedis集群初始化流程

集群初始化入口:JedisConnectionFactory类

从上面的配置文件applicationContext-redis-cluster.xml中我们声明了JedisConnectionFactory这个类:

  1. <bean id="jedisConnectionFactory"
  2. class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  3. <constructor-arg ref="redisClusterConfiguration" />
  4. <constructor-arg ref="jedisPoolConfig" />
  5. </bean>

这个类是用来创建、管理和销毁Jedis与Redis集群的连接的。由于我们在Spring配置文件中声明了这个类,因此当应用启动时,Spring会自动加载该类,Jedis集群信息初始化的动作也由此开始。该类初始化的方法代码如下:

  1. public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
  2. private JedisPoolConfig poolConfig = new JedisPoolConfig();
  3. private RedisClusterConfiguration clusterConfig;
  4. public JedisConnectionFactory(RedisClusterConfiguration clusterConfig, JedisPoolConfig poolConfig) {
  5. this.clusterConfig = clusterConfig;
  6. this.poolConfig = poolConfig;
  7. }
  8. /*
  9. * (non-Javadoc)
  10. * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
  11. */
  12. public void afterPropertiesSet() {
  13. if (shardInfo == null) {
  14. shardInfo = new JedisShardInfo(hostName, port);
  15. if (StringUtils.hasLength(password)) {
  16. shardInfo.setPassword(password);
  17. }
  18. if (timeout > 0) {
  19. setTimeoutOn(shardInfo, timeout);
  20. }
  21. }
  22. if (usePool && clusterConfig == null) {
  23. this.pool = createPool();
  24. }
  25. //如果集群配置信息不为空,则创建JedisCluster对象
  26. if (clusterConfig != null) {
  27. this.cluster = createCluster();
  28. }
  29. }
  30. }

在上面的配置文件中,我们使用构造函数注入的方式初始化了JedisConnectionFactory,由于该类实现了InitializingBean接口,因此在它被初始化之后会调用afterPropertiesSet()方法,在该方法中会根据clusterConfig集群配置信息是否为空来创建JedisCluster对象。createCluster()代码定义如下:

  1. private JedisCluster createCluster() {
  2. JedisCluster cluster = createCluster(this.clusterConfig, this.poolConfig);
  3. this.clusterCommandExecutor = new ClusterCommandExecutor(
  4. new JedisClusterConnection.JedisClusterTopologyProvider(cluster),
  5. new JedisClusterConnection.JedisClusterNodeResourceProvider(cluster), EXCEPTION_TRANSLATION);
  6. return cluster;
  7. }
  8. /**
  9. * Creates {@link JedisCluster} for given {@link RedisClusterConfiguration} and {@link GenericObjectPoolConfig}.
  10. *
  11. * @param clusterConfig must not be {@literal null}.
  12. * @param poolConfig can be {@literal null}.
  13. * @return
  14. * @since 1.7
  15. */
  16. protected JedisCluster createCluster(RedisClusterConfiguration clusterConfig, GenericObjectPoolConfig poolConfig) {
  17. Assert.notNull(clusterConfig, "Cluster configuration must not be null!");
  18. Set<HostAndPort> hostAndPort = new HashSet<HostAndPort>();
  19. for (RedisNode node : clusterConfig.getClusterNodes()) {
  20. hostAndPort.add(new HostAndPort(node.getHost(), node.getPort()));
  21. }
  22. int redirects = clusterConfig.getMaxRedirects() != null ? clusterConfig.getMaxRedirects().intValue() : 5;
  23. return StringUtils.hasText(getPassword())
  24. ? new JedisCluster(hostAndPort, timeout, timeout, redirects, password, poolConfig)
  25. : new JedisCluster(hostAndPort, timeout, redirects, poolConfig);
  26. }

上面的代码调用了JedisCluster的构造函数来创建JedisCluster对象,JedisCluster使用super关键字调用父类的构造函数:

  1. public JedisCluster(Set<HostAndPort> jedisClusterNode, int timeout, int maxAttempts,
  2. final GenericObjectPoolConfig poolConfig) {
  3. super(jedisClusterNode, timeout, maxAttempts, poolConfig);
  4. }

BinaryJedisCluster构造函数:

  1. public BinaryJedisCluster(Set<HostAndPort> jedisClusterNode, int timeout, int maxAttempts,
  2. final GenericObjectPoolConfig poolConfig) {
  3. this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig,
  4. timeout);
  5. this.maxAttempts = maxAttempts;
  6. }

集群信息获取:JedisClusterInfoCache类

初始化流程到这里,主要的部分就要浮出水面了。在BinaryJedisCluster类的构造函数中初始化了JedisSlotBasedConnectionHandler类,该类的出现说明Jedis要开始获取Redis集群的slot槽和Redis集群节点信息了,该类也是使用super关键字调用父类构造函数来初始化的,它的父类JedisClusterConnectionHandler构造函数如下:

  1. public JedisClusterConnectionHandler(Set<HostAndPort> nodes,
  2. final GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password) {
  3. this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout, password);
  4. //这里是关键
  5. initializeSlotsCache(nodes, poolConfig, password);
  6. }

JedisClusterConnectionHandler类的构造函数中创建了JedisClusterInfoCache对象,并调用initializeSlotsCache()方法对Redis集群信息进行初始化。该类的主要方法如下:

  1. public Jedis getConnectionFromNode(HostAndPort node) {
  2. return cache.setupNodeIfNotExist(node).getResource();
  3. }
  4. public Map<String, JedisPool> getNodes() {
  5. return cache.getNodes();
  6. }
  7. private void initializeSlotsCache(Set<HostAndPort> startNodes, GenericObjectPoolConfig poolConfig, String password) {
  8. for (HostAndPort hostAndPort : startNodes) {
  9. Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort());
  10. if (password != null) {
  11. jedis.auth(password);
  12. }
  13. try {
  14. cache.discoverClusterNodesAndSlots(jedis);
  15. break;
  16. } catch (JedisConnectionException e) {
  17. // try next nodes
  18. } finally {
  19. if (jedis != null) {
  20. jedis.close();
  21. }
  22. }
  23. }
  24. }
  25. public void renewSlotCache() {
  26. cache.renewClusterSlots(null);
  27. }
  28. public void renewSlotCache(Jedis jedis) {
  29. cache.renewClusterSlots(jedis);
  30. }
  31. @Override
  32. public void close() {
  33. cache.reset();
  34. }

可以看到,该类主要还是调用JedisClusterInfoCache对象的方法来完成slot的相关操作。因此我们重点看一下JedisClusterInfoCache类。

JedisClusterInfoCache类主要负责发送cluster slots命令来获取Redis集群节点的槽和Redis集群节点信信息,并将相应信息保存到Map缓存中。我们使用redis-cli客户端工具连接上任意一个Redis中的集群节点,向Redis发送该命令之后,获得的结果如下:

  1. 127.0.0.1:6379> cluster slots
  2. 1) 1) (integer) 12288
  3. 2) (integer) 16383
  4. 3) 1) "127.0.0.1"
  5. 2) (integer) 6382
  6. 3) "65aea5fc4485bc7c0c3c4425fb3f500c562ee243"
  7. 4) 1) "127.0.0.1"
  8. 2) (integer) 6386
  9. 3) "4061e306b094e707b6f4a7c8cd8e82bd61155060"
  10. 2) 1) (integer) 4096
  11. 2) (integer) 8191
  12. 3) 1) "127.0.0.1"
  13. 2) (integer) 6380
  14. 3) "c6e1b3691b968b009357dcac3349afbcd557fd8c"
  15. 4) 1) "127.0.0.1"
  16. 2) (integer) 6384
  17. 3) "f915c7e6812a7d8fbe637c782ad261cd453022b2"
  18. 3) 1) (integer) 0
  19. 2) (integer) 4095
  20. 3) 1) "127.0.0.1"
  21. 2) (integer) 6379
  22. 3) "91bb43a956a04a9812e4d6950efebbb2e0f646fd"
  23. 4) 1) "127.0.0.1"
  24. 2) (integer) 6383
  25. 3) "c1d9d907f6905dd826dad774d127b75484ef8ea8"
  26. 4) 1) (integer) 8192
  27. 2) (integer) 12287
  28. 3) 1) "127.0.0.1"
  29. 2) (integer) 6381
  30. 3) "745936c1192bc1b136fd1f5df842bc1dd517ef36"
  31. 4) 1) "127.0.0.1"
  32. 2) (integer) 6385
  33. 3) "1c07bd8406156122eb4855d2e8b36e785e7901c7"

我现在本地的Redis集群有八个节点,四个主节点,四个从节点,通过cluster slots命令的结果都可以清楚地看到这些节点信息。这个命令的每一组结果由四个部分组成:起始槽节点、终止槽节点、主节点IP和端口加节点ID、从节点IP和端口加节点ID。

在JedisClusterInfoCache类中,相关的源码如下:

  1. public class JedisClusterInfoCache {
  2. // 保存Redis集群节点和节点连接池信息:key为节点地址、value为连接池
  3. private final Map<String, JedisPool> nodes = new HashMap<String, JedisPool>();
  4. // 保存Redis集群节点槽和槽所在的主节点连接池信息:key为节点槽、value为连接池
  5. private final Map<Integer, JedisPool> slots = new HashMap<Integer, JedisPool>();
  6. // 使用读写锁保证nodes和slots两个map的写安全
  7. private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  8. private final Lock r = rwl.readLock();
  9. private final Lock w = rwl.writeLock();
  10. // 重建缓存的标识变量,false为未进行,true为正在进行
  11. private volatile boolean rediscovering;
  12. private final GenericObjectPoolConfig poolConfig;
  13. private int connectionTimeout;
  14. private int soTimeout;
  15. private String password;
  16. // 主节点索引位置标识,遍历cluster slots结果时使用
  17. private static final int MASTER_NODE_INDEX = 2;
  18. public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, int timeout) {
  19. this(poolConfig, timeout, timeout, null);
  20. }
  21. public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout, final String password) {
  22. this.poolConfig = poolConfig;
  23. this.connectionTimeout = connectionTimeout;
  24. this.soTimeout = soTimeout;
  25. this.password = password;
  26. }
  27. /**
  28. * 在jedis封装的redis集群节点信息上发送cluster slots命令,获取所有集群节点信息和槽信息
  29. *
  30. * @param jedis
  31. */
  32. public void discoverClusterNodesAndSlots(Jedis jedis) {
  33. w.lock();// 由当前线程获得写锁,在当前线程操作未结束之前,其他线程只能等待
  34. try {
  35. reset();// 重置nodes、slots两个Map,释放JedisPool连接池资源
  36. List<Object> slots = jedis.clusterSlots();// 在redis集群节点信息上发送cluster slots命令,获取所有集群节点信息和槽信息
  37. // 遍历slots集合,保存Redis集群节点和节点连接池信息到nodes Map中,保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
  38. for (Object slotInfoObj : slots) {
  39. List<Object> slotInfo = (List<Object>) slotInfoObj;
  40. if (slotInfo.size() <= MASTER_NODE_INDEX) {
  41. continue;
  42. }
  43. // 获取槽节点集合
  44. List<Integer> slotNums = getAssignedSlotArray(slotInfo);
  45. // hostInfos
  46. int size = slotInfo.size();
  47. // 遍历slots集合元素中的主从节点信息,保存Redis集群节点和节点连接池信息到nodes Map中,保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
  48. for (int i = MASTER_NODE_INDEX; i < size; i++) {
  49. List<Object> hostInfos = (List<Object>) slotInfo.get(i);
  50. if (hostInfos.size() <= 0) {
  51. continue;
  52. }
  53. // 获取集群节点的服务器地址和端口
  54. HostAndPort targetNode = generateHostAndPort(hostInfos);
  55. // 保存Redis集群节点和节点连接池信息到nodes Map中
  56. setupNodeIfNotExist(targetNode);
  57. // 如果当前遍历的是主节点信息,则保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
  58. if (i == MASTER_NODE_INDEX) {
  59. assignSlotsToNode(slotNums, targetNode);
  60. }
  61. }
  62. }
  63. } finally {
  64. w.unlock();// 释放写锁,使其他线程使用
  65. }
  66. }
  67. /**
  68. * 重建Cluster集群节点和Slot槽缓存
  69. *
  70. * @param jedis
  71. */
  72. public void renewClusterSlots(Jedis jedis) {
  73. // 如果重建操作未进行,则开始重建缓存操作
  74. if (!rediscovering) {
  75. try {
  76. w.lock();
  77. rediscovering = true;// 设重建缓存标识变量的值为true,表示重建操作正在进行
  78. // 如果封装redis连接信息的jedis对象不为空,则使用该节点进行重建缓存操作并返回
  79. if (jedis != null) {
  80. try {
  81. discoverClusterSlots(jedis);
  82. return;
  83. } catch (JedisException e) {
  84. // try nodes from all pools
  85. }
  86. }
  87. // 如果封装redis连接信息的jedis对象为空,则打乱nodes Map中保存的jedis连接池信息,遍历连接池中的节点进行重建缓存操作并返回
  88. for (JedisPool jp : getShuffledNodesPool()) {
  89. try {
  90. jedis = jp.getResource();
  91. discoverClusterSlots(jedis);
  92. return;
  93. } catch (JedisConnectionException e) {
  94. // try next nodes
  95. } finally {
  96. if (jedis != null) {
  97. jedis.close();
  98. }
  99. }
  100. }
  101. } finally {
  102. rediscovering = false;// 设重建缓存标识变量的值为false,表示重建操作未进行
  103. w.unlock();
  104. }
  105. }
  106. }
  107. /**
  108. * 逻辑类似discoverClusterNodesAndSlots方法
  109. *
  110. * @param jedis
  111. */
  112. private void discoverClusterSlots(Jedis jedis) {
  113. List<Object> slots = jedis.clusterSlots();
  114. this.slots.clear();
  115. for (Object slotInfoObj : slots) {
  116. List<Object> slotInfo = (List<Object>) slotInfoObj;
  117. if (slotInfo.size() <= MASTER_NODE_INDEX) {
  118. continue;
  119. }
  120. List<Integer> slotNums = getAssignedSlotArray(slotInfo);
  121. // hostInfos
  122. List<Object> hostInfos = (List<Object>) slotInfo.get(MASTER_NODE_INDEX);
  123. if (hostInfos.isEmpty()) {
  124. continue;
  125. }
  126. // at this time, we just use master, discard slave information
  127. HostAndPort targetNode = generateHostAndPort(hostInfos);
  128. assignSlotsToNode(slotNums, targetNode);
  129. }
  130. }
  131. private HostAndPort generateHostAndPort(List<Object> hostInfos) {
  132. return new HostAndPort(SafeEncoder.encode((byte[]) hostInfos.get(0)), ((Long) hostInfos.get(1)).intValue());
  133. }
  134. /**
  135. * 保存Redis集群节点和节点连接池信息到nodes Map中
  136. *
  137. * @param node
  138. * @return
  139. */
  140. public JedisPool setupNodeIfNotExist(HostAndPort node) {
  141. w.lock();
  142. try {
  143. // 获取节点key,形式为"服务器地址:端口"
  144. String nodeKey = getNodeKey(node);
  145. // 如果节点已存在nodes Map中,则直接返回
  146. JedisPool existingPool = nodes.get(nodeKey);
  147. if (existingPool != null)
  148. return existingPool;
  149. // 创建节点相应的JedisPool连接池对象,并保存到nodes Map中,然后返回JedisPool连接池对象
  150. JedisPool nodePool = new JedisPool(poolConfig, node.getHost(), node.getPort(), connectionTimeout, soTimeout, password, 0, null, false,
  151. null, null, null);
  152. nodes.put(nodeKey, nodePool);
  153. return nodePool;
  154. } finally {
  155. w.unlock();
  156. }
  157. }
  158. /**
  159. * 遍历槽集合,保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
  160. *
  161. * @param targetSlots
  162. * @param targetNode
  163. */
  164. public void assignSlotsToNode(List<Integer> targetSlots, HostAndPort targetNode) {
  165. w.lock();
  166. try {
  167. JedisPool targetPool = setupNodeIfNotExist(targetNode);
  168. for (Integer slot : targetSlots) {
  169. slots.put(slot, targetPool);
  170. }
  171. } finally {
  172. w.unlock();
  173. }
  174. }
  175. /**
  176. * 根据节点key获取JedisPool连接池对象
  177. *
  178. * @param nodeKey
  179. * @return
  180. */
  181. public JedisPool getNode(String nodeKey) {
  182. r.lock();
  183. try {
  184. return nodes.get(nodeKey);
  185. } finally {
  186. r.unlock();
  187. }
  188. }
  189. /**
  190. * 根据slot槽值获取JedisPool连接池对象
  191. *
  192. * @param slot
  193. * @return
  194. */
  195. public JedisPool getSlotPool(int slot) {
  196. r.lock();
  197. try {
  198. return slots.get(slot);
  199. } finally {
  200. r.unlock();
  201. }
  202. }
  203. /**
  204. * 获取节点信息和节点对象对应的连接池信息
  205. *
  206. * @return
  207. */
  208. public Map<String, JedisPool> getNodes() {
  209. r.lock();
  210. try {
  211. return new HashMap<String, JedisPool>(nodes);
  212. } finally {
  213. r.unlock();
  214. }
  215. }
  216. /**
  217. * 获取nodes Map打乱顺序后的Redis集群节点连接池信息
  218. *
  219. * @return
  220. */
  221. public List<JedisPool> getShuffledNodesPool() {
  222. r.lock();
  223. try {
  224. List<JedisPool> pools = new ArrayList<JedisPool>(nodes.values());
  225. Collections.shuffle(pools);
  226. return pools;
  227. } finally {
  228. r.unlock();
  229. }
  230. }
  231. /**
  232. * 清空集群节点集合和槽集合,释放JedisPool资源
  233. */
  234. public void reset() {
  235. w.lock();
  236. try {
  237. for (JedisPool pool : nodes.values()) {
  238. try {
  239. if (pool != null) {
  240. pool.destroy();
  241. }
  242. } catch (Exception e) {
  243. // pass
  244. }
  245. }
  246. nodes.clear();
  247. slots.clear();
  248. } finally {
  249. w.unlock();
  250. }
  251. }
  252. public static String getNodeKey(HostAndPort hnp) {
  253. return hnp.getHost() + ":" + hnp.getPort();
  254. }
  255. /**
  256. * 遍历槽区间,获取槽节点集合
  257. *
  258. * @param slotInfo
  259. * @return
  260. */
  261. private List<Integer> getAssignedSlotArray(List<Object> slotInfo) {
  262. List<Integer> slotNums = new ArrayList<Integer>();
  263. for (int slot = ((Long) slotInfo.get(0)).intValue(); slot <= ((Long) slotInfo.get(1)).intValue(); slot++) {
  264. slotNums.add(slot);
  265. }
  266. return slotNums;
  267. }
  268. }

总结

Jedis初始化Redis集群信息时,先使用JedisConnectionFactory获取JedisCluster对象,再根据JedisCluster去逐步引出JedisClusterInfoCache对象完成Redis集群信息的获取。在这个类中,主要有以下几点:

  • 整个类的核心是discoverClusterNodesAndSlots方法,它在jedis封装的redis集群节点上发送cluster slots命令,来获取所有集群节点信息和槽信息,然后分别缓存在nodes和slots两个HashMap中
  • 读写锁一般在读多写少的场景下使用。进行Redis集群信息保存和获取操作时,使用了读写锁ReentrantReadWriteLock,保证写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题
  • 在定义重建缓存标识变量rediscovering时,使用了volatile关键字,保证重建缓存的操作对于其他线程的内存可见性,使JVM主内存与方法线程工作内存状态同步
  • 客户端内部维护slots缓存表,并且针对每个节点维护连接池,当集群规模非常大时,客户端会维护非常多的连接并消耗更多的内存

Jedis cluster集群初始化源码剖析的更多相关文章

  1. tomcat集群实现源码级别剖析

    随着互联网快速发展,各种各样供外部访问的系统越来越多且访问量越来越大,以前Web容器可以包揽接收-逻辑处理-响应整个请求生命周期的工作,现在为了构建让更多用户访问更强大的系统,人们通过不断地业务解耦. ...

  2. [转]RMI方式Ehcache集群的源码分析

    RMI方式Ehcache集群的源码分析   Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群.如下图所示:   Ehcache支持 ...

  3. RMI方式Ehcache集群的源码分析

    Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群.如下图所示: Ehcache支持多种集群方式,下面以RMI通信方式为例,来具体分 ...

  4. 【一起学源码-微服务】Nexflix Eureka 源码十二:EurekaServer集群模式源码分析

    前言 前情回顾 上一讲看了Eureka 注册中心的自我保护机制,以及里面提到的bug问题. 哈哈 转眼间都2020年了,这个系列的文章从12.17 一直写到现在,也是不容易哈,每天持续不断学习,输出博 ...

  5. Hadoop源码学习笔记之NameNode启动场景流程三:FSNamesystem初始化源码剖析

    上篇内容分析了http server的启动代码,这篇文章继续从initialize()方法中按执行顺序进行分析.内容还是分为三大块: 一.源码调用关系分析 二.伪代码执行流程 三.代码图解 一.源码调 ...

  6. Quartz.net 定时任务之储存与持久化和集群(源码)

    一.界面 1.这篇博客不上教程.直接看结果(包括把quartz任务转换成Windows服务) (1).主界面 (2).添加任务(默认执行) (3).编辑(默认开启) (4).关闭和开启 2.代码说明 ...

  7. 吾日三省吾身 java核心代码 高并发集群 spring源码&思想

    阿里面试题    未解决https://my.oschina.net/wuweixiang/blog/1863322 java基础  有答案  https://www.cnblogs.com/xdp- ...

  8. K8S部署Redis Cluster集群

    kubernetes部署单节点redis: https://www.cnblogs.com/zisefeizhu/p/14282299.html Redis 介绍 • Redis代表REmote DI ...

  9. K8S部署Redis Cluster集群(三主三从模式) - 部署笔记

    一.Redis 介绍 Redis代表REmote DIctionary Server是一种开源的内存中数据存储,通常用作数据库,缓存或消息代理.它可以存储和操作高级数据类型,例如列表,地图,集合和排序 ...

随机推荐

  1. include 模板标签

    {%load staticfiles %}就能使用include标签了 {% include %}该标签允许在(模板中)包含其他的模板的内容,标签的参数是所要包含的模板名称,可以是一个变量,也可以是用 ...

  2. Redis进阶实践之一VMWare Pro虚拟机安装和Linux系统的安装(转载)(1)

    Redis进阶实践之一VMWare Pro虚拟机安装和Linux系统的安装 一.引言 设计模式写完了,相当于重新学了一遍,每次学习都会有不同的感受,对设计模式的理解又加深了,理解的更加透彻了.还差一篇 ...

  3. Todolist功能开发

    一.属性绑定和双向数据绑定: v-bind:title 或简写成 :title实现title属性绑定: v-model实现双向数据绑定(双向是指:当数据变了,input的value会改变:当input ...

  4. 【剑指offer】将字符串中的空格替换成"%20"

    #include <iostream> #include <string> using namespace std; char *ReplaceSpace(char *str, ...

  5. PHP脚本不报错的两点原因

    -------------------------------------------------------------------------------------------------- P ...

  6. 使用jQuery+huandlebars遍历展示对象中的数组

    兼容ie8(很实用,复制过来,仅供技术参考,更详细内容请看源地址:http://www.cnblogs.com/iyangyuan/archive/2013/12/12/3471227.html) & ...

  7. Delphi中For In 语法应用实例

    一.遍历 TStrings var List: TStrings; s: string; begin List := TStringList.Create; List.CommaText := 'aa ...

  8. servlet 验证码生成

    servlet package com.htpo.net; import java.awt.Color;import java.awt.Font;import java.awt.Graphics2D; ...

  9. Java8获取当前时间、新的时间日期类如Java8的LocalDate与Date相互转换、ZonedDateTime等常用操作包含多个使用示例、Java8时区ZoneId的使用方法、Java8时间字符串解析成类

     下面将依次介绍 Date转Java8时间类操作 ,Java8时间类LocalDate常用操作(如获得当前日期,两个日期相差多少天,下个星期的日期,下个月第一天等) 解析不同时间字符串成对应的Java ...

  10. 内核进程切换 switch_to

    参考: http://www.cnblogs.com/visayafan/archive/2011/12/10/2283660.html