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 索引,再定位到相应的实
例上。实现高效的连接。
