1. 为什么 需要Redis Cluster ?
为什么 要有 cluster 集群?
1. 为什么 需要Redis Cluster ?
哨兵模式基于主从 模式,实现读写分离,它还可以自动切 换,系统可用性更高。但是它每个节点
存储的数据是一样的,浪费内存,并且不 好在线扩容。
因此,Reids Cluster 集群(切片集群的实现方案)应运而生,它在Redis3.0 加入的,实现了Redis 的
分布式存储。对数据进行分片,也就是说每台Redis 节点上存储不同的内容,来解决在线扩容的问
题。并且,它可以保 存大量数据,即分散数 据到各个Redis 实例,还提供复制和故障转移的功能。
比如你一个Redis 实例保 存15G 甚至更大的数据,响应就会很慢,这是因为Redis RDB 持久化
机制导致的,Redis 会fork 子进程完成 RDB 持久化操作,fork 执行的耗时与 Redis 数据量成正
相关。
这时候你很容易想到,把15G 数据分散来存储就好了嘛。这就是Redis 切片集群的初衷。
切片集群是啥呢?来看个例子,如果你要用Redis 保存15G 的数据,可以用单实例Redis ,或者3台
Redis 实例组成切片集群,对比如下:
切片集群和Redis Cluster 的区别:Redis Cluster 是从Redis3.0 版本开始,官方提供的一种实现
切片集群的方案。

既然数据是分片分布到不同Redis 实例的,那客⼾端到底是怎么确定想要访问的数据在哪个实例上
呢?我们一起来看下Reids Cluster 是怎么做的哈。
2. 客⼾端是怎样知道该访 问哪个分片的?哈希槽
Redis Cluster 方案采用哈希槽(Hash Slot ),来处理数据和实例之间的映射关系。
一个切片集群被分为16384 个slot (槽),每个进入Redis 的键值对,根据key 进行散列,分配
到这16384 插槽中的一个。使用的哈希映射也比较简单,用CRC16 算法计算出一个16bit 的
值,再对16384 取模。数据库中的每个键都属于这16384 个槽的其中一个,集群中的每个节
点都可以处理这16384 个槽。
集群中的每个节点负责 一部分的哈希槽,假设当前集群有A、B、C3 个节点,每个节点上负责 的哈
希槽数 =16384/3 ,那么可能存在的一种分配:
节点A负责 0~5460 号哈 希槽
节点B负责 5461~10922 号哈 希槽
节点C负责 10923~16383 号哈 希槽
客⼾端给一个Redis 实例发送数据读写操作时,如果这个实例上并没有相应的数据,会怎么样呢?
MOVED 重定向和 ASK 重定向了解一下哈
# 3. 实例上并没有相应的数据,会怎么样?(MOVED 重定向和 ASK
# 重定向)在Redis cluster 模式下,节点对请求的处理过程如下:
通过哈希槽映射,检查当前Redis key 是否存在当前节点
若哈希槽不是由自身节点负责 ,就返回MOVED 重定向
若哈希槽确实由自身负责 ,且key 在slot 中,则返回该key 对应结果
若Redis key 不存在此哈希槽中,检查该哈希槽是否正在迁出(MIGRATING )?
若Redis key 正在迁出,返回ASK 错误重定向客⼾端到迁移的目的服务器上
若哈希槽未迁出,检查哈希槽是否导入中?
若哈希槽导入中且 有ASKING 标记,则直接操作,否则返回MOVED 重定向
3.1 Moved 重定向
客⼾端给一个Redis 实例发送数据读写操作时,如果计算出来的槽不是在该节点上,这时候它会返
回MOVED 重定向错误,MOVED 重定向错误中,会将哈希槽所在的新实例的IP 和port 端口带回去。
这就是Redis Cluster 的MOVED 重定向机制。流程图如下:

3.2 ASK 重定向
Ask 重定向一般发生于集群伸缩的时候。集群伸缩会导致槽迁移,当我们去源节点访问时,此时数
据已经可能已经迁移到了目标节点,使用Ask 重定向可 以解决此种情况。

# 4. 各个节点之间是怎么通信的呢(Gossip)一个Redis 集群由多个节点组成,各个节点之间是怎么通信的呢?通过Gossip 协议!Gossip 是一种
谣言传播协议,每个节点周期性地从节点列表中选择 k 个节点,将本节点存储的信息传播出去,
直到所有节点信息一致,即算法收敛 了。
Gossip 协议基本思想:一个节点想要分享一些信息给网络中的其他的一些节点。于是,它周
期性的随机选择一些节点,并把信息传递给这些节点。这些收到信息的节点接下来会做同样
的事情,即把这些信息传递给其他一些随机选择的节点。一般而言,信息会周期性的传递给
N个目标节点,而不只是一个。这个N被称为fanout
Redis Cluster 集群通过Gossip 协议进行通信,节点之前不断交换信息,交换的信息内容包括节点出
现故障、新节点加入、主从 节点变更信息、slot 信息等等 。gossip 协议包含多种消息类型,包括
ping ,pong ,meet ,fail 等等

meet 消息:通知新节点加入。消息发送者通知接收者加入到当前集群,meet 消息通信正常完成
后,接收节点会加入到集群中并进行周期性的ping 、pong 消息交换。
ping 消息:节点每秒会向集群中其他节点发送 ping 消息,消息中带有自己已知的两个 节点的地址、槽、状态信息、最后一次通信时间等
pong 消息:当接收到ping 、meet 消息时,作为响应消息回复给发送方确认消息正常通信。消息
中同样带有自己已知的两个 节点信息。
fail 消息:当节点判定集群内另一个节点下线时,会向集群内广播一个fail 消息,其他节点接收到
fail 消息之后把对应节点更新为下 线状态。
特别的,每个节点是通过集群总线(cluster bus) 与其他的节点进行通信的。通讯时,使用特殊的端口号,即对外服务端口号加10000 。例如如 果某 个node 的端口号是6379 ,那么它与其
它nodes 通信的端口号是 16379 。nodes 之间的通信采用特殊的二进制协议。
5. 集群内节点出现故障怎么办(故障转移)
Redis 集群实现了高可用,当集群内节点出现故障时,通过故障转移,以保 证集群正常对外提供服
务。
redis 集群通过ping/pong 消息,实现故障发现。这个环境包括主观下线和客观下线。
主观下线:某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,
只能代表一个节点的意⻅,可能存在误判情况。

客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识
的结果。如果是持有槽的主节点故障,需要为该节点进行故障转移。
假如节点A标记节点B为主 观下线,一段时间后,节点A通过消息把节点B的状态发到其它
节点,当节点C接受到消息并解析出消息体时,如果发现节点B的pfail 状态时,会触发客
观下线流程;
当下线为主 节点时,此时Redis Cluster 集群为统计持有槽的主节点投票,看投票数是否达
到一半,当下线报告统计数大于一半时,被标记为客观下线状态。
流程如下:

故障恢复:故障发现后,如果下线节点的是主节点,则需要在它的从节点中选一个替换它,以保证集群的高可用。流程如下:

资格检 查:检查从节点是否具备替换故障主节点的条件。
准备选举时间:资格检 查通过后,更新触发故障选举时间。
发起选举:到了故障选举时间,进行选举。
选举投票:只有持有槽的主节点才有票,从节点收集到足够的选票(大于一半),触发替
换主节点操
6. 加餐:为什么 Redis Cluster 的Hash Slot 是16384 ?
对于客⼾端请求过来的键值key ,哈希槽=CRC16(key) % 16384 ,CRC16 算法产生的哈希值是16bit
的,按道理该算法是可以产生2^16=65536 个值,** 为什么不 用65536 ,用的是16384 (2^14 )**呢?
大家可以看下作者的原始回答:

Redis 每个实例节点上都保存对应有哪些slots ,它是一个 unsigned char

slots[REDIS_CLUSTER_SLOTS/8] 类型
在redis 节点发送心跳包时需要把所 有的槽放到这个心跳包里,如果slots 数量是65536 ,占空间=
65536 / 8( 一个字节 8bit) / 1024(1024 个字节 1kB) =8kB ,如果使用slots 数量是16384 ,所占空
间 = 16384 / 8( 每个字节 8bit) / 1024(1024 个字节 1kB) = 2kB ,可⻅16384 个slots 比 65536 省6kB 内存左右,假如一个集群有100 个节点,那每个实例里就省了600kB 啦
一般情况下Redis cluster 集群主节点数量基本不可能超过1000 个,超过1000 会导致网络拥堵。
对于节点数在1000 以内的Redis cluster 集群,16384 个槽位其实够用了。
既然为了 节省内存网络开销,为什么 slots 不选择用8192 (即16384/2 )呢?
8192 / 8( 每个字节 8bit) / 1024(1024 个字节 1kB) = 1kB, 只需要1KB !可以先看下Redis 把Key 换算成所 属 slots 的方法

Redis 将key 换算成slots 的方法:其实就是是 将 crc16(key) 之后再和slots 的数量进行与计算
这里为什么 用 0x3FFF(16383) 来计算,而不是 16384 呢?因为在不产 生溢出的情况下 x % (2^n) 等
价 于 x & (2^n - 1) ,即 x % 16384 == x & 16383那到底为什么不 用8192 呢?
crc16 出来结果,理论上出现重复的概率为 ¹⁄65536 ,但实际结果重复概率可能比这个大不
少,就像crc32 结果 理论上 1/40 亿 分之一,但实际有人测下来10 万碰撞的概率就比较大
了。
假如 slots 设置成 8192 ,200 个实例的节点情况下,理论值是 每40 个不 同key 请求,命中就
会失效一次,假如节点数增加到 400, 那就是20 个请求。并且1kb 并不会比 2k 省太多 ,性价比
不是特别高,所以可能选16384 会更为通用一点。
7. 总结
哨兵模式已经实现了故障自动转移的能力,但业务规模的不断扩展,用⼾量膨胀,并发量持续
提升,会出现了 Redis 响应慢的情况。
使用 Redis Cluster 集群,主要解 决了大数据量存储导致的各种慢问题,同时也便于横向拓展。
在面对千万级甚至亿级别的流量的时候,很多大厂的做法是在千百台的实例节点组成的集群上
进行流量调度、服务治理的。
整个Redis 数据库划分 为16384 个哈希槽,Redis 集群可能有n个实例节点,每个节点可以处理0个
到至多 16384 个槽点,这些节点把 16384 个槽位瓜分完成。
Cluster 是具备Master 和 Slave 模式,Redis 集群中的每个实例节点都负责 一些槽位,节点之间
保持TCP 通信,当Master 发生了宕机, Redis Cluster 自动会将对 应的Slave 节点选为Master ,来
继续提供服务。
客⼾端能够快捷的连接到服务端,主要是将slots 与实例节点的映射关系存储在本地,当需要访
问的时候,对key 进行CRC16 计算后,再对16384 取模得到对应的 Slot 索引,再定位到相应的实
例上。实现高效的连接。
