# Redis集群

# 单集群问题:

从一主多从,到读写分离,再到Sentinel哨兵机制

  1. 写并发
  2. 海量存储的压力

# Redis集群:

3.0加入集群模式,基于数据分布式存储,对数据进行分片,存储在不同的master节点上面

无中心化设计,key没有分配到节点时,redis会返回转向指令,指向正确的node

支持N个master,每个master可以挂载多个slave,故障时会自动提升slave到master

# 数据分布算法:哈希槽算法

  1. 普通hash算法
  2. 一致性hash算法
  3. 啥希槽算法:16384个槽,不同的槽在不同的节点上管理,集群采用crc16算法取模,通过结果找到对应的数据槽所对应的Redis节点,然后到节点上进行操作

优点:

  1. 解耦数据与节点关系,简化扩容收缩的难度
  2. 节点自身维护槽映射关系,不需要客户端维护
  3. 支持节点、槽、键映射查询,用于数据路由,在线伸缩等场景

默认情况下,读写都是到master上进行,不支持slave上读写操作,slave主要做数据备份,以及master故障下的主备切换,实现高可用

# 数据结构

  1. clusterNode数据结构:保存节点当前状态,比如创建时间,节点名称,节点当前配置的纪元,节点IP和地址等
  2. clusterState数据结构:记录当前节点所认为的集群当前的状态
    • 指向当前节点clusterNode的指针
    • 集群当前配置的纪元,用于故障迁移
    • 集群的状态,是在线还是下线
    • 集群中有槽在处理的节点的数量
    • 集群节点名单,键为节点名字,值为节点对应的clusterNode结构
    • 节点黑名单cluster forget
    • 节点迁移的状态
      • 当前迁移到其它节点的槽和目标节点
      • 要从源节点迁移到本节点的槽,以及正在进行迁移的源结点
    • 槽对应的处理的节点
    • 选举信息
      • 上次选举或下次选举的时间
      • 节点得票的数量
      • 本节点是否向其它节点进行了投票请求
      • ...
  3. 节点槽指派信息 节点的clusterNode数据结构中slots属性和numslot属性记录节点负责处理的槽,slot属性是二进制数组,长度为16384/8=2048字节,master节点用bit来标识某个槽位是否是自己拥有,时间复杂度为o(1)
  4. 集群槽指派信息 clusterState.slots[i],时间复杂度为O(1)

# 集群请求重定向

  1. MOVED请求:通知客户端数据迁移到新节点
  2. ASK请求:缓存节点数据迁移中时,旧节点通过ASK返回目标结点的信息,客户端向缓存目标结点发送ASKING,查询数据是否在新缓存结点

# 频繁重定向网络开销的处理:smart客户端

客户端本地维护一份hashslot->node的映射表缓存,大部分情况下直接通过本地缓存就能找到,不需要moved重定向

JedisCluster工作原理:

随机选择一个节点,初始化hashslot->node映射表,有Moved请求时,利用返回的信息,更新本地映射表,重试,失败5次则报错

  1. ASK重定向说明集群正在进行slot数据迁移,客户端无法知道迁移什么时候完成,因此只能是临时性的重定向,客户端不会更新slots缓存。
  2. MOVED重定向说明键对应的槽已经明确指定到新的节点,客户端需要更新slots缓存。

# 集群节点通信机制:goosip协议

在节点间不断交换信息,一段时间后,所有节点就有了整个集群的完整信息,并且状态都能达成一致;

优点:

  1. 扩展性:节点任意增加减少,状态最终一致
  2. 容错性:每节点持有完整元数据,节点不会影响goosip协议
  3. 健状性:所有节点地位平等,去中心,任何节点故障都不影响服务
  4. 最终一致性
  5. 元数据更新分散在不同节点,减轻压力;

缺点:

更新就有延时,导致集群的一些操作会滞后;只能保证最终的一致性

消息类型:

  1. Meet:通知新节点加入集群,cluster meet ip port
  2. Ping:交换节点元数据 每隔一秒钟,选择5个最久没有通信的节点,发送PING消息,检测对应的节点是否在线;同时还有一种策略是,如果某个节点的通信延迟大于了cluster-node-time的值的一半,就会立即给该节点发送PING消息,避免数据交换延迟过久
    • 自身节点状态
    • 其它部分节点的状态信息
    • 自身所管理的槽信息
    • ...
  3. Pong:ping与meet的响应,也可以主动通过pong向集群广播自己信息,包含自身节点状态和集群元数据信息
  4. Fail,节点判断某个节点fail后,向集群所有节点广播
  5. Publish:向指定channel发送消息,某个节点收到后会直接进行集群广播,这样客户端无论连接那个节点,都可订阅到这个channel

# 扩容与收缩

Redis-trib集群管理工具,封闭了所有迁移命令,实现reshard,重新分片

# 扩容

  1. 启动新节点
  2. cluster meet 加入新集群
  3. 迁移槽和数据
cluster meet 192.168.1.1 5002
# 1.客户端:对目标节点发起准备导入槽数据命令,目标节点准备好导入槽数据
cluster setslot {slot} importing {sourceNodeid}
# 2.客户端:对源节点发起全集,让源结点准备迁出对应的槽数据
cluster {slot} migrating {targetNodeid}
# 3.客户端:源节点准备迁移数据了,迁移前把迁移的数据获取出来
cluster getkeysinslot {slot} {count}
# 4.源节点上执行,migrate {targetIp} {targetPort} "" 0 {timeout} keys {keys}命令,把获取的键通过流水线批量迁移到目标节点
# 5.重复3.4操作
# 6.完成后,通知对应的槽分配到目标结点,并且广播到全网,更新自身槽节点对应表
cluster setslot {slot} node {targetNodeId}


1
2
3
4
5
6
7
8
9
10
11
12
13

# 收缩

  1. 迁移槽
  2. 忘记节点,`cluster forget {downNodeId}

# 故障检测与恢复

# 故障检测

  1. 主观下线:节点与其它节点通信时间超过cluster-node-timeout时,会更新本地节点状态,但不代表某个节点真的下线了
  2. 客观下线:半数以上的主节点认为某个节点下线了,则标记为客观下线

# 故障恢复

  1. 主结点故障:从节点中找出一个替换它,从节点定期监控主节点是否客观下线,如果是则触发恢复流程。选举基于Raft协议实现

  2. 从结点过滤:slave与master断开时间,如超过cluster-node-timeout * cluster-slave-validaity-factor,则不能成为master

  3. 选举策略

    • 节点排序:过滤后的从结点,按priority offset runid进行排序
    • 更新配置的纪元:主节点会更新纪元clusterNode.configEpoch,记录了每个节点的版本和整个集群的版本
    • 选举:从节点广播选举信息,所有收到消息并有投票权的主结点投票,一个纪元只能发起一次选举
    • 投票:每个主节点都可投票,如果这个主节点还未给其它从节点投标,那么它会向要求投票的从节点返回一个ack消息,表示支持,获取到足够票数的节点,可以切换为master。如果没有则更新配置纪元,进行第二轮的选举,直到选举到主节点为止
    • 替换主节点
      • 执行slaveof no on
      • 撤销所有对已下线主节点的槽指派,全指派给自己
      • 集群内广播pong消息,告诉已成为主结点
      • 接收和处理槽相关的请求

    备注:某个节点的master和slave全宕机,则集群处理fail状态;如果有半数以上的master挂掉,则集群同样进入fail状态

上次更新: : 5 months ago