Jedis cluster集群初始化源码剖析

SpringBoot Data Redis连接Redis-Cluster集群 最近在研究系统高并发下的缓存架构,因此自己在自己买的云服务器上搭建好Redis 5.0 版本的集群后,使用springboot的 RedisTemplate连接是发现总是访问不到集群节点。在某些部署中,Redis 群集节点地址发现失败,因为地址是 NAT-ted 或者因为端口被转发(典型的情况是Docker 和其他集装箱)为了让 Redis 集群在这样的环境中工作,静态配置,每个节点都知道它的公共地址是必需的。知道这个问题就好办了。有些网上的解决方法,不一定是我们真正需要的,凡是还是要自己去探索和动脑。 阅读详情

在项目中我们经常使用spring-data-redis来操作Redis,它封装了Jedis客户端来与Redis服务器进行各种命令操作。由于最近用到了Redis Cluster集群功能,这里就分析总结一下Jedis cluster集群初始化主要过程及源码。

环境

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

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

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

	<!-- 连接池配置. -->
	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
		<!-- 连接池中最大连接数。高版本:maxTotal,低版本:maxActive -->
		<property name="maxTotal" value="8" />
		<!-- 连接池中最大空闲的连接数. -->
		<property name="maxIdle" value="4" />
		<!-- 连接池中最少空闲的连接数. -->
		<property name="minIdle" value="1" />
		<!-- 当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时。高版本:maxWaitMillis,低版本:maxWait -->
		<property name="maxWaitMillis" value="5000" />
		<!-- 连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除. -->
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<!-- 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3 -->
		<property name="numTestsPerEvictionRun" value="3" />
		<!-- “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1. -->
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<!-- testOnBorrow:向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值. -->
		<!-- testOnReturn:向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值. -->
		<!-- testWhileIdle:向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值. -->
		<!-- whenExhaustedAction:当“连接池”中active数量达到阀值时,即“链接”资源耗尽时,连接池需要采取的手段, 默认为1(0:抛出异常。1:阻塞,直到有可用链接资源。2:强制创建新的链接资源) -->
	</bean>

	<bean id="n1" class="org.springframework.data.redis.connection.RedisNode">
		<constructor-arg value="127.0.0.1" />
		<constructor-arg value="6379" type="int" />
	</bean>
	<bean id="n2" class="org.springframework.data.redis.connection.RedisNode">
		<constructor-arg value="127.0.0.1" />
		<constructor-arg value="6380" type="int" />
	</bean>
	<bean id="n3" class="org.springframework.data.redis.connection.RedisNode">
		<constructor-arg value="127.0.0.1" />
		<constructor-arg value="6381" type="int" />
	</bean>
	
	<bean id="redisClusterConfiguration"
		class="org.springframework.data.redis.connection.RedisClusterConfiguration">
		<property name="clusterNodes">
			<set>
				<ref bean="n1" />
				<ref bean="n2" />
				<ref bean="n3" />
			</set>
		</property>
		<property name="maxRedirects" value="5" />
	</bean>

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

	<!-- Spring提供的访问Redis类. -->
	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory" />
		<property name="KeySerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="ValueSerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="hashKeySerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="hashValueSerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
	</bean>

	<!-- Redis配置结束 -->
</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.JedisConnectionFactoryJedis连接工厂,负责创建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.BinaryJedisClusterJedisCluster的父类,负责与Redis集群进行byte[]类型的key交互
redis.clients.jedis.JedisSlotBasedConnectionHandlerJedisClusterConnectionHandler类的子类,负责根据key的slot值获取Redis连接
redis.clients.jedis.JedisClusterConnectionHandler一个抽象类,负责初始化、重建、重置Redis slot槽缓存
redis.clients.jedis.JedisClusterInfoCacheRedis slot缓存类,负责保存、重建和自动发现Redis slot槽与集群节点的关系

Jedis集群初始化流程

集群初始化入口:JedisConnectionFactory类

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

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

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

public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
    
    private JedisPoolConfig poolConfig = new JedisPoolConfig();
    private RedisClusterConfiguration clusterConfig;

	public JedisConnectionFactory(RedisClusterConfiguration clusterConfig, JedisPoolConfig poolConfig) {
		this.clusterConfig = clusterConfig;
		this.poolConfig = poolConfig;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() {
		if (shardInfo == null) {
			shardInfo = new JedisShardInfo(hostName, port);

			if (StringUtils.hasLength(password)) {
				shardInfo.setPassword(password);
			}

			if (timeout > 0) {
				setTimeoutOn(shardInfo, timeout);
			}
		}

		if (usePool && clusterConfig == null) {
			this.pool = createPool();
		}
      
      //如果集群配置信息不为空,则创建JedisCluster对象
		if (clusterConfig != null) {
			this.cluster = createCluster();
		}
	}
}

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

private JedisCluster createCluster() {

		JedisCluster cluster = createCluster(this.clusterConfig, this.poolConfig);
		this.clusterCommandExecutor = new ClusterCommandExecutor(
				new JedisClusterConnection.JedisClusterTopologyProvider(cluster),
				new JedisClusterConnection.JedisClusterNodeResourceProvider(cluster), EXCEPTION_TRANSLATION);
		return cluster;
	}

	/**
	 * Creates {@link JedisCluster} for given {@link RedisClusterConfiguration} and {@link GenericObjectPoolConfig}.
	 * 
	 * @param clusterConfig must not be {@literal null}.
	 * @param poolConfig can be {@literal null}.
	 * @return
	 * @since 1.7
	 */
	protected JedisCluster createCluster(RedisClusterConfiguration clusterConfig, GenericObjectPoolConfig poolConfig) {

		Assert.notNull(clusterConfig, "Cluster configuration must not be null!");

		Set<HostAndPort> hostAndPort = new HashSet<HostAndPort>();
		for (RedisNode node : clusterConfig.getClusterNodes()) {
			hostAndPort.add(new HostAndPort(node.getHost(), node.getPort()));
		}

		int redirects = clusterConfig.getMaxRedirects() != null ? clusterConfig.getMaxRedirects().intValue() : 5;

		return StringUtils.hasText(getPassword())
				? new JedisCluster(hostAndPort, timeout, timeout, redirects, password, poolConfig)
				: new JedisCluster(hostAndPort, timeout, redirects, poolConfig);
	}

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

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

BinaryJedisCluster构造函数:

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

集群信息获取:JedisClusterInfoCache类

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

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

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

public Jedis getConnectionFromNode(HostAndPort node) {
    return cache.setupNodeIfNotExist(node).getResource();
  }
  
  public Map<String, JedisPool> getNodes() {
    return cache.getNodes();
  }

  private void initializeSlotsCache(Set<HostAndPort> startNodes, GenericObjectPoolConfig poolConfig, String password) {
    for (HostAndPort hostAndPort : startNodes) {
      Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort());
      if (password != null) {
        jedis.auth(password);
      }
      try {
        cache.discoverClusterNodesAndSlots(jedis);
        break;
      } catch (JedisConnectionException e) {
        // try next nodes
      } finally {
        if (jedis != null) {
          jedis.close();
        }
      }
    }
  }

  public void renewSlotCache() {
    cache.renewClusterSlots(null);
  }

  public void renewSlotCache(Jedis jedis) {
    cache.renewClusterSlots(jedis);
  }

  @Override
  public void close() {
    cache.reset();
  }

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

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

127.0.0.1:6379> cluster slots
1) 1) (integer) 12288
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 6382
      3) "65aea5fc4485bc7c0c3c4425fb3f500c562ee243"
   4) 1) "127.0.0.1"
      2) (integer) 6386
      3) "4061e306b094e707b6f4a7c8cd8e82bd61155060"
2) 1) (integer) 4096
   2) (integer) 8191
   3) 1) "127.0.0.1"
      2) (integer) 6380
      3) "c6e1b3691b968b009357dcac3349afbcd557fd8c"
   4) 1) "127.0.0.1"
      2) (integer) 6384
      3) "f915c7e6812a7d8fbe637c782ad261cd453022b2"
3) 1) (integer) 0
   2) (integer) 4095
   3) 1) "127.0.0.1"
      2) (integer) 6379
      3) "91bb43a956a04a9812e4d6950efebbb2e0f646fd"
   4) 1) "127.0.0.1"
      2) (integer) 6383
      3) "c1d9d907f6905dd826dad774d127b75484ef8ea8"
4) 1) (integer) 8192
   2) (integer) 12287
   3) 1) "127.0.0.1"
      2) (integer) 6381
      3) "745936c1192bc1b136fd1f5df842bc1dd517ef36"
   4) 1) "127.0.0.1"
      2) (integer) 6385
      3) "1c07bd8406156122eb4855d2e8b36e785e7901c7"

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

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

public class JedisClusterInfoCache {
	// 保存Redis集群节点和节点连接池信息:key为节点地址、value为连接池
	private final Map<String, JedisPool> nodes = new HashMap<String, JedisPool>();
	// 保存Redis集群节点槽和槽所在的主节点连接池信息:key为节点槽、value为连接池
	private final Map<Integer, JedisPool> slots = new HashMap<Integer, JedisPool>();

	// 使用读写锁保证nodes和slots两个map的写安全
	private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	private final Lock r = rwl.readLock();
	private final Lock w = rwl.writeLock();
	// 重建缓存的标识变量,false为未进行,true为正在进行
	private volatile boolean rediscovering;
	private final GenericObjectPoolConfig poolConfig;

	private int connectionTimeout;
	private int soTimeout;
	private String password;

	// 主节点索引位置标识,遍历cluster slots结果时使用
	private static final int MASTER_NODE_INDEX = 2;

	public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, int timeout) {
		this(poolConfig, timeout, timeout, null);
	}

	public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout, final String password) {
		this.poolConfig = poolConfig;
		this.connectionTimeout = connectionTimeout;
		this.soTimeout = soTimeout;
		this.password = password;
	}

	/**
	 * 在jedis封装的redis集群节点信息上发送cluster slots命令,获取所有集群节点信息和槽信息
	 * 
	 * @param jedis
	 */
	public void discoverClusterNodesAndSlots(Jedis jedis) {
		w.lock();// 由当前线程获得写锁,在当前线程操作未结束之前,其他线程只能等待

		try {
			reset();// 重置nodes、slots两个Map,释放JedisPool连接池资源
			List<Object> slots = jedis.clusterSlots();// 在redis集群节点信息上发送cluster slots命令,获取所有集群节点信息和槽信息

			// 遍历slots集合,保存Redis集群节点和节点连接池信息到nodes Map中,保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
			for (Object slotInfoObj : slots) {
				List<Object> slotInfo = (List<Object>) slotInfoObj;

				if (slotInfo.size() <= MASTER_NODE_INDEX) {
					continue;
				}

				// 获取槽节点集合
				List<Integer> slotNums = getAssignedSlotArray(slotInfo);

				// hostInfos
				int size = slotInfo.size();
				// 遍历slots集合元素中的主从节点信息,保存Redis集群节点和节点连接池信息到nodes Map中,保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
				for (int i = MASTER_NODE_INDEX; i < size; i++) {
					List<Object> hostInfos = (List<Object>) slotInfo.get(i);
					if (hostInfos.size() <= 0) {
						continue;
					}

					// 获取集群节点的服务器地址和端口
					HostAndPort targetNode = generateHostAndPort(hostInfos);
					// 保存Redis集群节点和节点连接池信息到nodes Map中
					setupNodeIfNotExist(targetNode);
					// 如果当前遍历的是主节点信息,则保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
					if (i == MASTER_NODE_INDEX) {
						assignSlotsToNode(slotNums, targetNode);
					}
				}
			}
		} finally {
			w.unlock();// 释放写锁,使其他线程使用
		}
	}

	/**
	 * 重建Cluster集群节点和Slot槽缓存
	 * 
	 * @param jedis
	 */
	public void renewClusterSlots(Jedis jedis) {
		// 如果重建操作未进行,则开始重建缓存操作
		if (!rediscovering) {
			try {
				w.lock();
				rediscovering = true;// 设重建缓存标识变量的值为true,表示重建操作正在进行

				// 如果封装redis连接信息的jedis对象不为空,则使用该节点进行重建缓存操作并返回
				if (jedis != null) {
					try {
						discoverClusterSlots(jedis);
						return;
					} catch (JedisException e) {
						// try nodes from all pools
					}
				}

				// 如果封装redis连接信息的jedis对象为空,则打乱nodes Map中保存的jedis连接池信息,遍历连接池中的节点进行重建缓存操作并返回
				for (JedisPool jp : getShuffledNodesPool()) {
					try {
						jedis = jp.getResource();
						discoverClusterSlots(jedis);
						return;
					} catch (JedisConnectionException e) {
						// try next nodes
					} finally {
						if (jedis != null) {
							jedis.close();
						}
					}
				}
			} finally {
				rediscovering = false;// 设重建缓存标识变量的值为false,表示重建操作未进行
				w.unlock();
			}
		}
	}

	/**
	 * 逻辑类似discoverClusterNodesAndSlots方法
	 * 
	 * @param jedis
	 */
	private void discoverClusterSlots(Jedis jedis) {
		List<Object> slots = jedis.clusterSlots();
		this.slots.clear();

		for (Object slotInfoObj : slots) {
			List<Object> slotInfo = (List<Object>) slotInfoObj;

			if (slotInfo.size() <= MASTER_NODE_INDEX) {
				continue;
			}

			List<Integer> slotNums = getAssignedSlotArray(slotInfo);

			// hostInfos
			List<Object> hostInfos = (List<Object>) slotInfo.get(MASTER_NODE_INDEX);
			if (hostInfos.isEmpty()) {
				continue;
			}

			// at this time, we just use master, discard slave information
			HostAndPort targetNode = generateHostAndPort(hostInfos);
			assignSlotsToNode(slotNums, targetNode);
		}
	}

	private HostAndPort generateHostAndPort(List<Object> hostInfos) {
		return new HostAndPort(SafeEncoder.encode((byte[]) hostInfos.get(0)), ((Long) hostInfos.get(1)).intValue());
	}

	/**
	 * 保存Redis集群节点和节点连接池信息到nodes Map中
	 * 
	 * @param node
	 * @return
	 */
	public JedisPool setupNodeIfNotExist(HostAndPort node) {
		w.lock();
		try {
			// 获取节点key,形式为"服务器地址:端口"
			String nodeKey = getNodeKey(node);
			// 如果节点已存在nodes Map中,则直接返回
			JedisPool existingPool = nodes.get(nodeKey);
			if (existingPool != null)
				return existingPool;

			// 创建节点相应的JedisPool连接池对象,并保存到nodes Map中,然后返回JedisPool连接池对象
			JedisPool nodePool = new JedisPool(poolConfig, node.getHost(), node.getPort(), connectionTimeout, soTimeout, password, 0, null, false,
					null, null, null);
			nodes.put(nodeKey, nodePool);
			return nodePool;
		} finally {
			w.unlock();
		}
	}

	/**
	 * 遍历槽集合,保存Redis集群节点槽和槽所在的主节点连接池信息到slots Map中
	 * 
	 * @param targetSlots
	 * @param targetNode
	 */
	public void assignSlotsToNode(List<Integer> targetSlots, HostAndPort targetNode) {
		w.lock();
		try {
			JedisPool targetPool = setupNodeIfNotExist(targetNode);
			for (Integer slot : targetSlots) {
				slots.put(slot, targetPool);
			}
		} finally {
			w.unlock();
		}
	}

	/**
	 * 根据节点key获取JedisPool连接池对象
	 * 
	 * @param nodeKey
	 * @return
	 */
	public JedisPool getNode(String nodeKey) {
		r.lock();
		try {
			return nodes.get(nodeKey);
		} finally {
			r.unlock();
		}
	}

	/**
	 * 根据slot槽值获取JedisPool连接池对象
	 * 
	 * @param slot
	 * @return
	 */
	public JedisPool getSlotPool(int slot) {
		r.lock();
		try {
			return slots.get(slot);
		} finally {
			r.unlock();
		}
	}

	/**
	 * 获取节点信息和节点对象对应的连接池信息
	 * 
	 * @return
	 */
	public Map<String, JedisPool> getNodes() {
		r.lock();
		try {
			return new HashMap<String, JedisPool>(nodes);
		} finally {
			r.unlock();
		}
	}

	/**
	 * 获取nodes Map打乱顺序后的Redis集群节点连接池信息
	 * 
	 * @return
	 */
	public List<JedisPool> getShuffledNodesPool() {
		r.lock();
		try {
			List<JedisPool> pools = new ArrayList<JedisPool>(nodes.values());
			Collections.shuffle(pools);
			return pools;
		} finally {
			r.unlock();
		}
	}

	/**
	 * 清空集群节点集合和槽集合,释放JedisPool资源
	 */
	public void reset() {
		w.lock();
		try {
			for (JedisPool pool : nodes.values()) {
				try {
					if (pool != null) {
						pool.destroy();
					}
				} catch (Exception e) {
					// pass
				}
			}
			nodes.clear();
			slots.clear();
		} finally {
			w.unlock();
		}
	}

	public static String getNodeKey(HostAndPort hnp) {
		return hnp.getHost() + ":" + hnp.getPort();
	}

	/**
	 * 遍历槽区间,获取槽节点集合
	 * 
	 * @param slotInfo
	 * @return
	 */
	private List<Integer> getAssignedSlotArray(List<Object> slotInfo) {
		List<Integer> slotNums = new ArrayList<Integer>();
		for (int slot = ((Long) slotInfo.get(0)).intValue(); slot <= ((Long) slotInfo.get(1)).intValue(); slot++) {
			slotNums.add(slot);
		}
		return slotNums;
	}
}

总结

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

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

下一篇文章剖析Jedis cluster命令执行流程。

分布式缓存技术redis学习系列(八)——JedisCluster源码解读:集群初始化、slot(槽)的分配、值的存取 redis集群环境,客户端使用JedisCluster获取连接并操作redis服务,上一篇 分布式缓存技术redis学习系列(七)——spring整合jediscluster 简单介绍了spring使用JedisCluster,这篇从JedisCluster源码层面看看是如何使用。一、集群初始化1、从单元测试开始,代码如下: @Autowired private JedisClus 阅读详情

相关推荐

Redis集群交互与Jedis客户端优化解析

本文深入探讨了Redis集群中Jedis客户端的交互细节,分析了JedisConnectionException异常的原因及处理机制,并介绍了JedisCluster类的使用和优化策略。通过阅读本文,读者将对Jedis在集群环境下的工作原理和潜在问题有更深刻的理解,并掌握如何有效避免集群交互中出现的性能瓶颈。

weixin_26854475的博客 825

springboot2.x 整合redis

1.jar包依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version&g...

weixin_30550271的博客 102

ggml_cgraph 详解

是 GGML 库的(Computation Graph)结构,它是深度学习框架的核心,用于表示、优化和执行神经网络的计算过程。

zhouzwd87的专栏 1203

StringRedisTemplate支持交并补源码分析

集群状态下,redis并不支持某些命令,如mset,集合的交并补运算等操作,但是springboot整合redis后并使用集群模式,发现在使用集合的交并补操作时并不会报错,且返回的结果符合预期: @RunWith(SpringRunner.class) @SpringBootTest public class RedisClusterApplicationTests { @Autowir...

qq_33371372的博客 969

手把手教你用DCA1000EVM解析IWR6843毫米波雷达数据(附MATLAB代码)

本文提供了一份详尽的实战指南,手把手教你使用DCA1000EVM采集并解析TI毫米波雷达IWR6843的原始数据。内容涵盖硬件连接配置、mmWave Studio参数设置、LVDS数据流结构解析,并附有核心MATLAB代码,详细演示了从原始二进制文件到距离-速度谱的信号处理全链路,帮助开发者快速掌握数据解析与处理的关键步骤。

terraform7cloud的博客 227

软件测试 | 手把手教你如何使用 Charles 抓包工具(电脑+手机端)

软件测试 | 手把手教你如何使用 Charles 抓包工具(电脑+手机端)一、Charles简介二、Charles下载与安装三、Charles配置(1)Charles-PC端的配置如下:(2)过滤网络请求:(3)Charles抓取HTTPS请求包前置条件:(4)Charles抓取HTTPS双向认证包:关注我,免费领取学习资料 嘿。大家好,我是4U: 做过 APP 测试的同学都会接触抓包工具,通过抓包工具可以获取到客户端与服务端接口的交互数据,在实际测试APP的过程中,我们往往会发现服务端返回 的参数字段错

leboxy的博客 4151

Spring-data-redis+Jedis+Redis集群导致的阻塞问题

一、现象 1、项目上使用三个队列来缓存数据,发现其中有一个队列有很多数据,但是消费却非常慢 2、代码使用redisTemplate.opsForList().rightPop 来获取队列里面的数据,这个命令对应的Redis命令是bRPop 3、通过调用链查看,发现brpop命令执行的耗时非常长,最大响应时间达到2秒多 4、在Redis客户端直接使用BRPOP course 30获取数据,执行非常快,并没有感觉到阻塞 二、分析过程 1、BRPOP 是列表的阻塞式(blocking)弹出原语.

duzm200542901104的专栏 1567

vit的cam和注意力图: VIT模型的可解释性

VIT的热力图怎么画

YI_SHU_JIA的博客 1万+

【解决办法】Cannot initialize Cluster. Please check you..

**eclipse 运行 hadoop mapreduce 程序 提交到集群运行 报错:**Exception in thread "main" java.io.IOException: Cannot initialize Cluster. Please check your configuration for mapreduce.framework.name and the correspond

Chail 6354

GNN(图神经网络)的 MATLAB 仿真

图神经网络(Graph Neural Network, GNN)是一种用于处理图结构数据的深度学习模型。GNN 通过聚合节点及其邻居的信息,能够有效捕捉图结构中的复杂关系,广泛应用于社交网络分析、推荐系统、化学分子分析等领域。GNN 通过消息传递机制聚合节点及其邻居的信息,能够有效处理图结构数据。MATLAB 仿真展示了 GNN 的基本流程和节点分类性能,适用于社交网络分析、推荐系统、化学分子分析和知识图谱等多种应用场景。

走向CTO的路上... 837

银河麒麟桌面系统 必会操作 | 加装双系统(麒麟桌面系统+Windows系统)的详细方法(含常见故障和解决方法)

装好麒麟桌面操作系统的电脑上、再加装Windows 操作系统的详细方法;和在已经装好Windows系统的电脑上、再加装麒麟桌面操作系统的详细方法;含加装双系统过程中常见问题的解决方法。

qq_33216613的博客 2407

centos k8s 初始化集群报错

k8s 初始化

weixin_45014813的博客 926

MacBook 长期挂机 OpenClaw:电池保护 + 防休眠设置

本文介绍了MacBook长期挂机运行OpenClaw时的电池保护和防休眠设置方案。主要内容包括:1)电池保护策略,通过电源连接、充电限制和定期充放电维护电池健康;2)防休眠设置,禁用系统休眠、自动休眠和屏幕休眠;3)OpenClaw的Docker部署方法。文章提供了详细的命令行脚本和配置说明,帮助用户实现MacBook的长期稳定运行,同时保护电池寿命。

一键难忘的博客 1692

Redis Client Jedis 源码分析

Redis模型: 所采用的是Redis集群(直连型)模型。 模型介绍: 从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。 特点: 1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。 2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分...

hqw11的博客 715

Java:PriorityQueue详解——从零基础到应用

看了网上的详解,我反是一篇没看懂,都是什么垃圾玩意也发出来,无语,我自己根据这两天的学习总结出来了下面这些干货,我就是这么学会的,大家要是零基础看肯定木问题。

流楚丶格念的博客 6140

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

淘淘商城第38讲——使用Jedis操作集群版Redis时,报错:redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException

问题描述 今天碰到一个问题,那就是在使用Jedis操作集群版Redis时,报了一个如下这样一个错误,具体错误信息记录如下: redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirections? at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:34) at re

李阿昀的博客 5598

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Jedis集群管理:深入解析槽位信息的获取与动态更新机制

​。

u014200244的专栏 716

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Jedis cluster命令执行流程剖析

面试:你懂什么是分布式系统吗?Redis分布式锁都不会?>>> ...

Viking的博客 1653

Redis学习(二):redis集群之cluster模式下的跨节点的批量操作 I

说明 通过之前的博文《Redis学习(一):redis集群之哨兵模式下的负载均衡》,对redis哨兵模式下的读负载进行学习研究。在本篇博文中,将对redis cluster模式下的跨节点集合操作进行研究学习。通过本篇博文,我们将了解redis cluster模式的基本原理及在Jedis客户端中如何对redis cluster集群进行集合操作。 正文 Redis Cluster redis clus...

wds的博客 1万+

深入理解Redis Cluster和Jedis Cluster

本文转载自:https://zhuanlan.zhihu.com/p/69800024 Redis Cluster是Redis官方提供的集群解决方案。由于业务的飞速增长,单机模式总会遇到内存、性能等各种瓶颈,这个时候我们总会喊,上集群啊。就跟我家热得快炸了,你总喊开空调呀一样。的确,上集群可以解决大多数问题,但是在使用集群的过程中,不可避免会遇到这样那样的问题,这个时候怎么办呢,各种百度各种群里去问吗?NO,作为开发人员,在享受第三方提供的方便前,有必要去了解其基本的工作机制,这样才能在遇到问题时快速定位,

IT云清 3390
上一篇: redis cluster环境下jedis expireAt命令bug探究
下一篇: Jedis cluster命令执行流程剖析
Viking_bird
博客等级 码龄11年 92粉丝 183原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值