一. Redis 架构模式
Redis 单机服务在高可用和高并发环境要求下提供的本领是非常有限的,因此 Redis 提供了多种架构部署模式,比如我们熟知的主从模式、哨兵模式与切片集群模式。
1. 主从模式
主从模式由主节点(master)和多个从节点(slave)构成,多个Redis服务实例之间通过replicaof下令构成主从关系并创建长毗连。其中,主节点可以用于处理客户端读写利用,而从节点一般仅处理客户端读利用,从而实现读写分离;除此之外,为了实现主从之间的数据同等性,主节点支持通过异步的方式将主节点数据变更单向同步到从节点,即主从复制过程,其核心同步下令为psync [runId] [offset]:
- 全量复制:在初次同步环境下,从节点会向主节点哀求进行全量数据同步(开销大);
- 增量复制:在断线重连环境下,从节点会根据生存的同步数据偏移量offset向主节点哀求增量数据同步;
同时,在运行过程中,主从服务之间还会通过下令传播来保证数据同等性。但是,由于主从之间的数据同步是异步进行的,以是无法保证实现强同等性,在网络波动环境下,从节点读取到的数据可能会出现过时。
2. 哨兵模式
在主从复制模式中,一旦主节点宕机,就需要通过人工介入来手动切换从节点为新的主节点,从而继续对外提供服务,这在现实运维过程中非常被动和不便。因此,为了实现自动服务监控、主从切换和故障转移,可以在主从模式基础上,添加一组“哨兵”节点进行服务监控,这就构成了哨兵模式(Redis Sentinel)。其中,“哨兵节点”是一组特殊的Redis进程节点,可以通过sentinel monitor <master-name>设置哨兵节点信息(监听主节点信息),并使用redis-sentinel xxx.conf下令启动哨兵节点服务,启动后它们会自动读取网络拓扑并感知其他哨兵节点的存在;注意哨兵节点并不提供数据读写服务,仅与主从节点之间进行通信,实现监控、选主、切换和通知。
哨兵节点们会周期性的向所有主从节点发送心跳检测,并获取其状态信息。当主节点被判定为客观下线后,就会在哨兵集群中根据投票算法进行哨兵Leader节点的推举的过程,并由哨兵Leader节点按照规则和优先级从剩余从节点中选定新的主节点,该过程包括:
- 对选定从节点发送slaveof no one下令将其转为主节点;
- 向其他从节点发送slaveof <masterIp>下令以指向新的主节点;
末了在故障迁徙完成后,向客户端或其他订阅者发送通知,以更新Redis服务拓扑结构。
3. 切片集群模式
主从模式和哨兵模式都保证了Redis服务架构的高可用性,但是现在为止不论是主节点还是从节点,所有数据仍然都被存储在该节点上,这并不满足分布式和数据水平拓展的需要,且随着数据量越来越大,数据的存储压力并没有被分担。在社区发展中,Redis的分布式集群架构涌现出许多优秀的项目,比如TwemProxy、Codis等,现在主流的集群方案应该是Redis 3.x之后官方推出的Redis-Cluster切片集群模式,其采取的是去中心化分布式架构。
3.1 基本架构
在Redis-Cluster集群模式中,Redis服务由多个集群节点共同提供,每个集群节点都是主从架构,即一主多从模式(主从之间数据同步),这保证了每个集群节点的高可用性;除此之外,Redis-Cluster 将数据分布在多个集群节点上存储,每个集群节点负责存储部分数据即可,这实现了数据的水平拓展。末了,所谓的去中心化是指Redis Cluster不存在中心节点或者像Zookeeper这样的同一管理中心,而是每个节点都纪录集群的部分状态信息,并且周期性的通过Gossip协议(分布式同等性协议)与其他节点通信从而交换信息(节点地址、状态、维护的哈希槽等),使每个节点纪录的信息实现终极同等性。
故障检测与迁徙与哨兵模式的核心头脑基本同等,集群中的每个节点都会周期性地向集群中的别的节点发送PING消息,以此来检测节点是否在线。当判定某主节点下线之后,就会由具有投票权的主节点们基于Raft算法对其从节点进行投票推举,新当选的主节点会接管后续的迁徙(接管管理的哈希槽)、重指向(其他从节点指向新的主节点)和通知工作(广播,更新其他节点所生存的集群设置),以此来完成之前哨兵的工作。
3.2 数据分配
Redis Cluster 使用哈希槽(Hash Slot)机制来分配数据。在
Redis Cluster 方案中,切片集群共有 16384 (0~16383) 个哈希槽(固定),每个集群主节点被指派并负责管理一部分哈希槽,在对16384个槽指派完成后,集群才会进入上线状态。其中,哈希槽类似于数据分区,用来处理数据和节点之间的映射关系,从而实现数据在差别集群节点上的分配与读写;当客户端向集群恣意节点发送与数据库键有关的下令时,接收下令的节点管帐算出下令要处理的数据库键key属于哪个槽:
- 通过CRC16算法(Hash算法)计算对应哈希槽的映射,即 slot = CRC16(key) % 16384;
- 若计算slot哈希槽刚好属于当前节点管理的区间,那么由当前节点直接执行下令即可;
- 若计算slot哈希槽不属于当前节点管理区间,则节点会基于生存的集群信息向客户端返回MOVED指令,对客户端哀求进行重定向转至精确节点;
比如,当前集群有三个分片节点,⼀种可能的分配⽅式如下:
0号分⽚:[0,5461],共5462个槽位;
1号分⽚:[5462,10923],共5462个槽位;
2号分⽚:[10924,16383],共5460个槽位;
3.3 集群部署
Redis Cluster集群的部署非常简单,起首需要单独启动每个Redis实例,并在设置文件conf中修改开启集群模式cluster-enabled yes,然后可以通过官方脚本 redis-cli --cluster create --cluster-replicas <cnt> <master list> <slaves list>来在恣意Redis服务节点呆板上启动集群服务,并指定分片的主从关系(cnt表示每个主节点master的从节点数量),以及自动匀称分配哈希槽slots;
末了,在集群搭建完成后,此时使⽤客户端连上集群中的任何⼀个节点都相当于连上了整个集群;客户端不需要毗连集群中所有的节点,只需要恣意毗连集群中的一个可用节点即可自动读取拓扑。
二. Redis Java 客户端
Redis 服务搭建完成后,访问 Redis 服务需要通过 Redis 的 Java 客户端,常见的客户端类型包括:
- Jedis:最经典也是Redis官方提供的客户端,其特点是简单、易上手、学习本钱低,但Jedis实例是线程不安全的,在多线程环境下频仍的创建和销毁又会存在性能损耗,因此通常需要基于毗连池使用;
- Lettuce:现在主流的Java客户端之一,其是基于Netty实现的,支持同步、异步和相应式编程,功能和性能优化都不错,并且是线程安全的;
- Redission:基于Redis实现的分布式客户端,功能非常强盛,提供许多Redis场景办理方案框架,比如WatchDog、分布式锁等;
1. Jedis 直连方式
1.1 引入依靠
- <!-- jedis -->
- <dependency>
- <groupId>redis.clients</groupId>
- <artifactId>jedis</artifactId>
- </dependency>
复制代码 1.2 代码使用
- public void testRedis(){
- // 建立连接对象
- Jedis jedis = new Jedis("localhost", 6379);
- // 设置密码(如果 Redis 服务设置了密码的话)
- jedis.auth("password");
- // 选择操作库(可选操作,默认为0)
- jedis.select(0);
- // redis 操作
- // keys
- Set<String> keys = jedis.keys("*");
- System.out.println(keys);
- // string
- String result = jedis.set("name","user_name");
- String name = jedis.get("name");
- System.out.println(name);
- // hash
- jedis.hset("user:1","name","user_1_name");
- String hname = jedis.hget("user:1","name");
- System.out.println(hname);
- // 释放连接
- if(jedis != null){
- jedis.close();
- }
- }
复制代码 2. Spring Data Redis
类似于 Spring Data JPA,为了屏蔽 Redis Java 客户端之间的差异,SpringBoot 提供了 Spring Data Redis 项目,对 Jedis 和 Letture 客户端进行了封装和整合,实现了 RedisTemplate 并提供了同一的 Redis 服务交互利用,支持毗连池、序列化、集群等;接下来本节将以 Jedis 为例进行介绍。
2.1 引入依靠
- <!--在springboot 2.0版本后,spring-boot-starter-data-redis 默认使用Lettuce客户端-->
- <!--如果要使用Jedis, 就要在pom.xml中去掉Lettuce 并且添加 Jedis 依赖-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- <exclusions>
- <exclusion>
- <groupId>io.lettuce</groupId>
- <artifactId>lettuce-core</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <!-- jedis 内置了 commons-pool2 连接池-->
- <!-- 注意:若是 Lettuce 使用连接池的话,需要单独引入 commons-pool2 依赖 -->
- <dependency>
- <groupId>redis.clients</groupId>
- <artifactId>jedis</artifactId>
- </dependency>
复制代码 2.2 SpringBoot 设置
2.2.1 YML设置
- spring:
- redis:
- host: 127.0.0.1
- port: 6379
- database: 0
- password: wangxin88
- # 连接超时时间(ms)
- timeout: 5000
- jedis:
- pool:
- # 连接池中的最小空闲连接
- min-idle: 0
- # 连接池中的最大空闲连接
- max-idle: 8
- # 连接池的最大数据库连接数
- max-active: 30
- # #连接池最大阻塞等待时间 ms(使用负值表示没有限制)
- max-wait: -1
复制代码 2.2.2 自定义设置类
SpringBoot YML 设置中以 spring.redis 为前缀的设置项默认绑定到SpringBoot提供的 RedisProperties 设置类中,因此可以默认使用 RedisProperties接收设置项,并在Config中初始化;除此之外,使用定义设置类,绑定 YML 自定义设置项,接收 YML 设置参数也是可以的。
- @Configuration
- public class RedisConfig {
- @Resource
- RedisProperties redisProperties;
- // 配置 RedisTemplate,并设置序列化
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(String.class);
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- // key 序列化器
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- // value 序列化器(jackson2Json)
- redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
- // hash的key也采用String的序列化方式
- redisTemplate.setHashKeySerializer(new StringRedisSerializer());
- // hash的value序列化方式采用jackson
- redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
- redisTemplate.setConnectionFactory(redisConnectionFactory);
- // 初始化配置
- redisTemplate.afterPropertiesSet();
- return redisTemplate;
- }
- // 配置 StringRedisTemplate
- @Bean
- public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
- StringRedisTemplate redisTemplate = new StringRedisTemplate();
- redisTemplate.setConnectionFactory(redisConnectionFactory);
- return redisTemplate;
- }
- // 配置连接工厂方式一: 构造函数配置(推荐)
- // SpringBoot 提供默认的连接工厂,如果自己提供则会覆盖原有工厂对象
- @Bean
- public RedisConnectionFactory redisConnectionFactory() {
- // 方式一: 构造函数配置
- // 将连接池配置转化到客户端配置 JedisClientConfiguration
- JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder()
- .usePooling()
- .poolConfig(jedisPoolConfig())
- .build();
- // 单机配置 + 客户端配置 = jedis连接工厂
- return new JedisConnectionFactory(jedisStandaloneConfg(), jedisClientConfiguration);
- }
- // 配置连接工厂方式二: set配置
- // 注意:该方式从 Jedis 2.0 之后弃用,改为RedisStandaloneConfiguration, RedisSentinelConfiguration,RedisClusterConfiguration的方式
- //@Bean
- //public RedisConnectionFactory redisConnectionFactory() {
- // JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
- // // set 方法均废弃
- // jedisConnectionFactory.setHostName(redisProperties.getHost());
- // jedisConnectionFactory.setPort(redisProperties.getPort());
- // jedisConnectionFactory.setPassword(redisProperties.getPassword());
- // jedisConnectionFactory.setPoolConfig(jedisPoolConfig());
- // // 初始化配置
- // jedisConnectionFactory.afterPropertiesSet();
- // return jedisConnectionFactory;
- //}
- // 配置 Redis Standalone 模式(单机模式)
- public RedisStandaloneConfiguration jedisStandaloneConfg() {
- RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
- redisStandaloneConfiguration.setHostName(redisProperties.getHost());
- redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());
- redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
- redisStandaloneConfiguration.setPort(redisProperties.getPort());
- return redisStandaloneConfiguration;
- }
- // 配置连接池
- public JedisPoolConfig jedisPoolConfig() {
- // 继承自 GenericObjectPoolConfig(commons.pool2),但 JedisPoolConfig 是 redis.clients.jedis 中的
- JedisPoolConfig poolConfig = new JedisPoolConfig();
- // 最小空闲连接数
- poolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle());
- // 最大空闲连接数
- poolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle());
- // 最大阻塞等待时间 :setMaxWaitMillis 已被废弃
- poolConfig.setMaxWait(redisProperties.getJedis().getPool().getMaxWait());
- // 最大连接数
- poolConfig.setMaxTotal(redisProperties.getJedis().getPool().getMaxActive());
- // 连接在池中最小空闲时间(以毫秒为单位)
- // poolConfig.setMinEvictableIdleTimeMillis();
- return poolConfig;
- }
- }
复制代码 三. 整合 Redis-Cluster 集群设置
在整合Redis-Cluster时,固然RedisTemplate在二者之上封装了一层,屏蔽了底层差异,但Jedis客户端和Letture客户端的区别是:
- JedisCluster 可以自动感知集群节点变化,并自动刷新节点拓扑;Letture Cluster 在 SpringBoot 2.3 之后是默认支持属性设置开启集群拓扑刷新点;
- JedisCluster 不支持集群读写分离,默认主库读写,从库仅同步数据;Letture 支持读写分离设置,但是从库数据可能过时,不保证时效性;
- JedisCluster不支持批量mset,mget,pipline等批量方法,因为RedisCluster集群本身就不支持;而Letture支持这些利用,因为客户端层面进行了分组处理;
本节以 Jedis Cluster 的设置为例,集群拓扑实例设置介绍如下:
1. YML 设置项
- spring:
- redis:
- # 密码(若有)
- password: wangxin88
- # 连接超时时间(ms)
- timeout: 5000
- jedis:
- pool:
- # 连接池中的最小空闲连接
- min-idle: 0
- # 连接池中的最大空闲连接
- max-idle: 8
- # 连接池的最大连接数
- max-active: 30
- # #连接池最大阻塞等待时间 ms(使用负值表示没有限制)
- max-wait: -1
- cluster:
- # 集群节点初始列表 List<String> nodes
- # 连接集群任意可用节点地址即可,会自动读取全部拓扑
- nodes:
- - 192.18.216.249:31754
- # 集群执行命令时的最大重定向数
- max-redirects: 5
复制代码 2. 自定义设置类
(1)方式一:单数据源
- @Configuration
- public class ClusterConfig {
- @Resource
- private RedisProperties redisProperties;
- // 配置 RedisTemplate,并设置序列化
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(String.class);
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- // string 序列化方式
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
- // hash 序列化方式
- redisTemplate.setHashKeySerializer(new StringRedisSerializer());
- redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
- redisTemplate.setConnectionFactory(redisConnectionFactory);
- // 初始化配置
- redisTemplate.afterPropertiesSet();
- return redisTemplate;
- }
- // 配置 StringRedisTemplate
- @Bean
- public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
- StringRedisTemplate redisTemplate = new StringRedisTemplate();
- redisTemplate.setConnectionFactory(redisConnectionFactory);
- return redisTemplate;
- }
- // 配置连接工厂
- @Bean
- public RedisConnectionFactory redisConnectionFactory() {
- // 将连接池配置转化到客户端配置 JedisClientConfiguration
- JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder()
- .usePooling()
- .poolConfig(jedisPoolConfig())
- .build();
- // 集群配置 + 客户端配置 = jedis连接工厂
- return new JedisConnectionFactory(jedisClusterConfg(), jedisClientConfiguration);
- }
- // 配置 Redis Cluster 模式(集群模式)
- public RedisClusterConfiguration jedisClusterConfg() {
- RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes());
- redisClusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
- redisClusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
- return redisClusterConfiguration;
- }
- // 配置连接池
- public JedisPoolConfig jedisPoolConfig() {
- JedisPoolConfig poolConfig = new JedisPoolConfig();
- // 最小空闲连接数
- poolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle());
- // 最大空闲连接数
- poolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle());
- // 最大阻塞等待时间 :setMaxWaitMillis 已被废弃
- poolConfig.setMaxWait(redisProperties.getJedis().getPool().getMaxWait());
- // 最大连接数
- poolConfig.setMaxTotal(redisProperties.getJedis().getPool().getMaxActive());
- // 连接在池中最小空闲时间(以毫秒为单位)
- // poolConfig.setMinEvictableIdleTimeMillis();
- return poolConfig;
- }
- }
复制代码 (2)方式二:多数据源
注意: 毗连工厂ConnectionFactory在使用@Bean注入时会自动调用afterPropertiesSet()方法进行JedisCluster的初始化,以实例化JedisCluster对象。但是如果通过非注入方式创建ConnectionFactory(如下),则需要手动调用connectionFactory.afterPropertiesSet()方法进行初始化,否则将报错“JedisCluster is null”;
- @Configuration
- public class ClusterConfig {
- @Bean(name = "redisClusterTemplate")
- @ConditionalOnProperty(prefix = "cluster", name = "nodes")
- public StringRedisTemplate redisClusterTemplate(@Value("${cluster.nodes}") String nodes,
- @Value("${cluster.maxRedirects}") int maxRedirects,
- @Value("${cluster.maxIdle}") int maxIdle,
- @Value("${cluster.maxTotal}") int maxTotal,
- @Value("${cluster.maxWaitMillis}") long maxWaitMillis,
- @Value("${cluster.password}") String password,
- @Value("${cluster.minEvictableIdleTimeMillis:-1}") long minEvictableIdleTimeMillis) {
- StringRedisTemplate temple = new StringRedisTemplate();
- temple.setConnectionFactory(redisConnectionFactory(nodes, maxRedirects, maxIdle, maxTotal,
- maxWaitMillis, password, minEvictableIdleTimeMillis));
- return temple;
- }
- public RedisConnectionFactory redisConnectionFactory(String nodes, int maxRedirects,
- int maxIdle, int maxTotal, long maxWaitMillis,
- String password, long minEvictableIdleTimeMillis) {
- JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder()
- .usePooling()
- .poolConfig(redisPoolCofig(maxIdle, maxTotal, maxWaitMillis, minEvictableIdleTimeMillis))
- .build();
- JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(redisClusterConfg(password,nodes,maxRedirects),
- jedisClientConfiguration);
- // 注意: 非@Bean注入情况下需手动初始化ConnectionFactory!
- redisConnectionFactory.afterPropertiesSet();
- return redisConnectionFactory;
- }
- // 配置 Redis Cluster
- public RedisClusterConfiguration redisClusterConfg(String password, String nodes, int maxRedirects) {
- List<String> nodesList = Arrays.asList(StringUtils.split(nodes, ","));
- RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(nodesList);
- redisClusterConfiguration.setMaxRedirects(maxRedirects);
- if (StringUtils.isNotEmpty(password)) {
- redisClusterConfiguration.setPassword(RedisPassword.of(password));
- }
- return redisClusterConfiguration;
- }
- public JedisPoolConfig redisPoolCofig(int poolMaxIdle, int poolMaxTotal,
- long poolMaxWaitMillis, long poolMinEvictableIdleTimeMillis) {
- JedisPoolConfig poolConfig = new JedisPoolConfig();
- poolConfig.setMinIdle(poolMaxIdle);
- poolConfig.setMaxIdle(poolMaxIdle);
- poolConfig.setMaxTotal(poolMaxTotal);
- poolConfig.setMaxWaitMillis(poolMaxWaitMillis);
- poolConfig.setMinEvictableIdleTimeMillis(poolMinEvictableIdleTimeMillis);
- return poolConfig;
- }
- }
复制代码- // 按需注入
- @Autowired(required = false)
- @Qualifier("redisClusterTemplate")
- RedisTemplate clusterTemplate;
复制代码 3. JedisCluster 原理分析
基于Jedis设置的Cluster,JedisConnectionFactory 初始化完成后会自动调用afterPropertiesSet()方法初始化设置,若RedisConfiguration是ClusterConfiguration类型,则表示其在集群模式下,此时会通过createrCluster()方法创建一个 JedisCluster 实例(Jedis提供的集群操尴尬刁难象)并交给 RedisTemplate使用,以是本质上 RedisTemplate 还是调用的 JedisCluster。
- // 创建 JedisCluster 实例
- protected JedisCluster createCluster(RedisClusterConfiguration clusterConfig,
- GenericObjectPoolConfig<Jedis> poolConfig) {
- Assert.notNull(clusterConfig, "Cluster configuration must not be null!");
- Set<HostAndPort> hostAndPort = new HashSet<>();
- for (RedisNode node : clusterConfig.getClusterNodes()) {
- hostAndPort.add(new HostAndPort(node.getHost(), node.getPort()));
- }
- int redirects = clusterConfig.getMaxRedirects() != null ? clusterConfig.getMaxRedirects() : 5;
- return new JedisCluster(hostAndPort, this.clientConfig, redirects, poolConfig);
- }
- // 获取 JedisClusterConnection 连接
- @Override
- public RedisClusterConnection getClusterConnection() {
- assertInitialized();
- if (!isRedisClusterAware()) {
- throw new InvalidDataAccessApiUsageException("Cluster is not configured!");
- }
- return new JedisClusterConnection(this.cluster, this.clusterCommandExecutor, this.topologyProvider);
- }
复制代码 我们知道在 Redis 中一共有 16384 个Slot槽位,每个集群节点各映射并负责部分Slot,当对 Key 进行利用时,Redis会通过CRC16算法计算出key对应的Slot,然后将Key映射到Slot所在集群节点上执行利用。我们先看一下JedisCluster是如何执行下令的:
(1)JedisCluster是通过JedisSlotBasedConnectionHandler获取毗连的,在JedisCluster的方法中,会创建一个JedisSlotBasedConnectionHandler,它有一个字段cache,类型为JedisClusterInfoCache;JedisClusterInfoCache缓存了每个主节点对应的毗连池nodes,以及每个槽位对应的毗连池。
(2)该JedisClusterInfoCache类型的成员变量cache有两个HashMap类型的成员变量nodes和slots,nodes生存节点和JedisPool的映射关系,slots生存16384个slot和JedisPool的映射关系,这里slot和节点实现了映射关系。接着,cache会调用getSlotPool(),从成员变量slots中通过slot取到了相应节点的JedisPool。
JedisCluster在发送死令前会根据CRC16(key) % 16384 计算出key所在的槽位,根据槽位获取对应的节点毗连池,再从毗连池中获取一个Jedis毗连。
(3)注意: 上图中109、105、198三个从节点对应的 JedisPool 是没有在 slots 中映射的(slots中映射的JedisPool都是主redis节点),因此在创建毗连时也是从主库拿的毗连Connection,没有经过从库中访问(已验证,包括读也没有),从库仅作为数据同步的高可用。但是 Redis-Cluster 从库是支持读取的,JedisCluster可能考虑到从库数据的滞后性风险,没有提供从库读利用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |