redis集群
数据分区
槽的分配
Redis Cluster采用虚拟槽分区,将所有键使用哈希函数映射到编号为0~16383槽(slot)内,每个节点拥有一部分的槽 → 分布式存储
通过以下方式计算key属于哪一个slot:
slot = CRC16(key) % 16384
每个节点只需要维护自己被分配的slot以及slot中的key-value关系
Hash Tag
在Redis Cluster中可以使用Hash Tag的方式使得具有指定形式的key保存在同一个slot中:
key{...}
各个key的命名拥有相同的Hash Tag即中括号及其中的内容{...}相同,例如:
127.0 . 0.1 : 6380 > set 1test hello -> Redirected to slot [ 15801 ] located at 127.0 . 0.1 : 6381 OK 127.0 . 0.1 : 6381 > set 2test hello -> Redirected to slot [ 4971 ] located at 127.0 . 0.1 : 6379 OK 127.0 . 0.1 : 6380 > set 1 {test} hello OK 127.0 . 0.1 : 6380 > set 2 {test} hello OK 127.0 . 0.1 : 6380 > set test{ 1 } 1 OK 127.0 . 0.1 : 6380 > set test{ 2 } 1 -> Redirected to slot [ 5649 ] located at 127.0 . 0.1 : 6384 OK |
集群伸缩
当集群中有节点上下线时,节点之间存在槽的迁移。
扩容集群
手动执行
- 准备节点
准备节点的配置文件并启动
redis-server conf/redis-XXXX.conf - 加入集群
在新节点上使用cluster meet命令使节点加入到集群中:
cluster meet {ip}
其中{ip}为已在集群中的任一节点。 - 迁移槽和数据
迁移流程
a)对目标节点发送cluster setslot {slot} importing {sourceNode}命令,使目标节点准备导入槽的数据。
b)对源节点发送cluster setslot {slot} migrating {sourceNode}命令,使源节点准备导入槽的数据。
c)源节点循环执行cluster getkeysinslot {slot} {count}命令获取{count}个属于槽{slot}的键。
d)在源节点上执行migrate {targetIP} {targetPort} "" 0 {timeout} keys {keys ...}命令,将获得的键批量迁移到目标节点。
e)重复执行c)和d)直至将槽中的所有键值数据迁移到目标节点
f)在集群所有主节点执行cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点。
使用redis-trib.rb进行槽重分片
redis-trib.rb reshard host:port --from --to --slots --yes --timeout --pipiline
使用redis-trib.rb
- 使用redis-trib.rb添加新节点
redis-trib.rb add-node new_host:new_port existing_host:existing_port [--slave / --master-id ] - 分配槽
redis-trib.rb reshard host:port --from --to --slots --yes --timeout --pipeline
从多个source node分配出n个slots到一个target node
收缩集群
与集群扩容相反,先将要下线节点的槽迁移到其他节点,再将节点下线cluster forget {NodeId},然后删除该节点的节点配置文件和数据文件
或使用redis-trib.rb del-node将该节点剔出集群(被执行的节点会被shutdown),然后删除该节点的节点配置文件和数据文件
请求重定向
MOVED重定向(长期性)
集群中的节点接收到客户端的键相关命令后计算键对应的槽,若槽在该节点则执行命令,若不在则返回MOVED重定向告知正确节点,客户端对该槽的命令长期地发送给正确节点
- 计算槽
- 槽节点查找
ASK重定向(临时性)
集群中的节点接收到客户端的键相关命令后计算键对应的槽,若槽不在可返回ASK重定向告知正确节点,客户端的下一命令请求发给正确节点,且发送前先发送ASKING命令
区别
MOVED重定向说明键对应的槽已指定到新的节点,需要客户端更新slots缓存
ASK重定向说明集群中正在进行槽和数据迁移,客户端不更新本地slots缓存
故障转移与failover机制
故障发现
- 主观下线(pfail):某个节点认为另一个节点不可用
集群内节点之间定期发送ping消息,若一个节点在一定时间(cluster-node-timeout)内与另一节点通信一直失败,则认为该节点存在故障
不同节点对同一节点通信状况不同,可能导致误判
- 客观下线(fail):集群内多个节点认为一个节点不可用
多个主节点对一个节点判断主观下线。通过Gossip消息传播,集群内节点不断收到故障节点的下线报告,并更新内部下线报告链表。若有大于槽主节点数量一半的节点判断主观下线,则进行客观下线,向集群广播fail消息
下线报告的有效期限为cluster-node-timeout * 2,超过期限则会被删除
故障恢复
故障节点客观下线后,若该节点为持有槽的主节点,则在它的从节点中选出一个升为主节点
- 资格检查:若从节点与主节点断线时间超过一定时间(cluster-node-timeout * cluster-slave-validity-factor)则不具备故障转移资格
- 准备选举时间:延迟触发机制,达到故障选举时间后(failover_auth_time)从节点才能继续发起选举,延迟越低的从节点复制偏移量越大,则优先级越高
- 发起选举:从节点到达故障选举时间后,更新配置纪元,在集群内广播选举消息
- 选举投票:持有槽的所有主节点包括故障节点投票,若从节点得票数大于总投票数的一半+1(N/2 + 1)则可升为主节点
- 替换主节点:从节点升为主节点,将主节点的槽迁移到从节点,向集群广播该从节点接管故障节点的消息
故障转移时间
- 主观下线(pfail)识别时间 = cluster-node-timeout
- 主观下线状态消息传播时间 ≤ cluster-node-timeout / 2
- 从节点转移时间 ≤ 1000ms
- 故障转移时间failover-time ≤ cluster-node-timeout + cluster-node-timeout / 2 + 1000
自动failover → 故障转移
当主节点故障时,其从节点升为主节点 → 自动回复集群的可用性
- 故障发现
- 故障恢复
手动failover → 手动故障转移
人为执行从节点接管主节点 → 集群的可运维操作
CLUSTER FAILOVER [ FORCE | TAKEOVER ]
FORCE:当master已不可用,无法通知到,则可以强制执行(直接从第四步开始),直接进行进行故障恢复的选举步骤(手动切换)
TAKEOVER:不需要集群达成共识,即不需要进行集群内的选举(直接到第五步)
TAKEOVER会生成新的configEpoch,可能与其他实例的冲突
其他
读写分离
- 维护主节点的可用从节点列表
cluster slaves {nodeId} 查看nodeId对应主节点的所有从节点信息 - 针对读命令维护请求节点路由
- 从节点新建连接开启readonly状态
集群倾斜
数据倾斜
- 槽分配不均
redis-trib.rb rebalance {ip}
重新平衡主节点上的槽 - 不同槽对应键数量差异过大
- 集合对象包含大量元素
- 内存相关配置不一致
请求倾斜
手动故障转移
在从节点执行cluster failover命令发起转移流程:
- 从节点通知主节点停止处理客户端请求
- 主节点发送对应从节点延迟复制的数据
- 从节点接收数据使复制偏移量一致
- 从节点立即发起投票选举,成功后断开复制成为主节点,广播消息
- 原来的主节点更新配置成为从节点,解除请求阻塞
- 原来的主节点成为从节点后向新的主节点发起全量复制
数据迁移
redis-trib.rb import
redis-migrate-tool