Redis Cluster


文章目录

  • 一、集群搭建
    • 1 节点规划
    • 2 集群启动
  • 二、配置一致性
    • 1 基本分工
    • 2 更新规则
  • 三、Sharding
    • 1 数据分片
      • 分片实现
      • 分片特点
    • 2 slot迁移
      • 迁移原因
      • 迁移支持
      • 集群扩容
      • 迁移错误
        • 背景
        • 现象
        • 问题分析
        • 验证
        • 猜想
      • 集群缩容
    • 3. 请求路由
      • client端
      • server端
      • migrating节点的读写
      • importing节点的读写
      • 关于salve节点
    • 4. 迁移小结
  • 四、Failover
    • 1. 状态变迁
    • 2. 故障发现
      • 单节点感知
      • 网络的不确定性
    • 3. 故障确认
    • 4. slave选举
    • 5. 最终变更同步
    • 6. Failover小结
  • 总结


一、集群搭建

1 节点规划

  1. 计划包含多少个master/slave组;
  2. 每组仅存储部分数据;
  3. 配置文件中需要启用cluster mode;

2 集群启动

#!/bin/bash
./redis-server.sh 7380
./redis-server.sh 7381
./redis-server.sh 7382
./redis-server.sh 7383
./redis-server.sh 7384
./redis-server.sh 7385

redis-cli --cluster create localhost:7380 localhost:7381 localhost:7382 localhost:7383 localhost:7384 localhost:7385 --cluster-replicas 1

二、配置一致性

1 基本分工

  1. 每个节点保持整个集群的所有信息;
  2. 每个节点持有集群信息更新版本号, 单调递增;
  3. 每个节点周期性地向集群中的其他节点发送心跳, PING中携带自身节点信息, PONG中携带对方所知道的集群信息;

2 更新规则

  1. 当某个节点率先知道了变更时,将自身的currentEpoch自增,并使之成为集群中的最大值。再用自增后的currentEpoch 作为新的Epoch版本;
  2. 当某个节点收到了比自己大的currentEpoch时,更新自己的currentEpoch;
  3. 当收到的Redis Cluster Bus 消息中的某个节点的Epoch > 自身的时,将更新自身的内容;
  4. 当Redis Cluster Bus 消息中,包含了自己没有的节点时,将其加入到自身的配置中。
    上述的规则保证了信息的更新都是单向的,最终朝着Epoch更大的信息收敛。同时Epoch也随着currentEpoch的增加而增加,最终将各节点信息趋于稳定。

三、Sharding

1 数据分片

分片实现

  1. server默认整个集群有16384个slot, 然后在集群初始化时会均匀分配到每个master上, 进而可以知道每个slotId与master的对应关系。
  2. 由于Redis是K-V结构, 当新增某个key时, 通过算法(slotId = crc16(key) % 16384)得到slotId, 然后将目标的key写入目标slot。这里想强调的是, 由于key本身是固定的, 因此其对应的slotId也是固定的, 这是一种逻辑上的对应关系。而物理对应关系则是由配置信息中slotId与master节点的映射来实现。而这个关系对Redis client来说非常重要。

分片特点

  1. 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
  2. 节点自身维护槽的映射关系,不需要客户端 或 代理服务维护数据分片关系。
  3. Redis Cluster的节点之间会共享消息,每个节点都知道另外节点负责管理的槽范围。每个节点只能对自己负责的槽进行维护 和 读写操作。
    虽然每个分片(shard)维护一定数量的slot, 但slot与shard的映射关系是可以动态调整的。此外, 数据迁移也是以slot为单位进行。

2 slot迁移

当sharding rebalance时, slotId与master的映射关系发生变化, slotId与key的映射关系不变。
此外, 具体的业务场景中, 考虑到数据的局部性, 可能会把相关的数据放入同一个slot上, 此时可以在key中加入{}。此时Redis server不再使用整个key, 而是仅使用{}中的内容来计算slotId。

迁移原因

  1. 新的master节点加入;
  2. 旧节点关机下线或者维护;
  3. 数据分布不均衡, 需要手动调整slot位置以均衡压力;

迁移支持

具体迁移过程由外部触发, Redis Cluster本身只提供了迁移过程中需要的指令支持。

  1. 节点迁移状态设置, 迁移前标记源/目标节点;
  2. key迁移的原子化命令;
  3. 将迁移后的配置循环广播到其他master节点;

集群扩容

  1. 将新节点加入集群;
    cluster meet ip
  2. 在新节点上设置待导入的slot
    cluster setslot {slotId} importing {sourceNodeId}

cluster setslot 0 importing 5df7af18093ac10b8a4a4121abb1b4fd6b0465c3
3. 数据源节点设置待迁移的slot
cluster setslot {slotId} migrating {targetNodeId}

cluster setslot 0 migrating 199a9dec48962ec0a017a28a85a5fa9b414d91f3
4. 源节点获取一批目标slot的key
cluster getkeysinslot {slotId} {count}
cluster getkeysinslot 0 100

  1. 从源节点发起迁移
    migrate {targetNodeIp} {targetNodePort} "" 0 {timeout} keys { key... }
    migrate localhost 7381 “” 0 1000 keys key-c19780 key-c13965 key-c9249
    该步骤手动执行, 如果目标地址错误, 则数据丢失。仅有

  2. 重复4和5直到获取不到新的key

  3. 广播新的slot位置
    cluster setslot {slotId} node {nodeId}

cluster setslot 0 node 7fc05faa8893c7f75aab12e057a40176a873e4ca
设置会让导入节点的Epoch自增,成为Cluster中的最新值,然后通过Redis Cluster Bus相互感知,传播到Cluster中的其他节点。
7. 如果是存量集群内部迁移, 则不考虑新节点加入, 其他步骤相同。

迁移错误

背景

源和目标节点状态设置正确, 但migrate的目标位置错误不是既定的目标节点;

现象

源节点slot状态为migrating;
目标节点slot状态为importing;
migrate也迁移成功,然后在不同节点的表现有点花:

  1. 在源节点上get返回ASK {实际migrate节点};
  2. 在{migrate目标节点}get返回MOVED {源节点};
  3. 在importing节点上get返回MOVED {源节点};

问题分析

  1. 数据是否丢失?
    a. 从上面get的结果来看, 无法从任何一个节点中读取出来;
    b. 从migrate成功来看, 数据一定在{实际migrate节点}上;
    c. 在{实际migrate节点}上, 执行keys发现key是存在的, 因此可以确定数据未丢失;

  2. 为何无法读取?
    显然每个key都有对应的slot, 并且slot需要在当前的节点。因此该问题的root cause是, 数据位置和slot配置信息不一致。而目前由于value无法读取, 因此无法再对数据做移动。只能调整slot位置, 尝试通过cluster set slot设置slot的位置为当前节点。

验证

  1. 执行cluster slot命令到实际数据接收节点, 成功;
  2. 在实际数据接收节点执行get key命令, 成功;
  3. 在其他节点执行get key命令, 统一MOVDED到新节点;
  4. 至此, 问题解决;

猜想

  1. Redis Cluster的配置和存储之间没有强约束, 也就是虽然我当前节点没有某个slot的配置信息, 但是migrate的情况下依然可以接收目标key, 只是无法读取出来;
  2. 设置importing和migrating的状态, 仅是告诉client这个slot中的key有不确定性。这种不确定性由其他节点完成最终确定, 如果是人为错误, 就一直在确定的路上;
  3. 每个节点由于不掌握全局信息, 只能按照自己已知的信息来回复client;
  4. 其实slot的迁移直接可以通过cluster setslot完成, 其中的数据迁移依赖外部的处理, 至于迁移过程中client的访问结果则看server的状态;
  5. 总体来看, 整个过程耦合非常松散, 手动操作容易出错, 运维时得务必小心;

集群缩容

  1. 确认目标节点是否有负责的slot;
  2. 如果有负责的slot, 则需要将其迁移到其他节点上, 数据迁移过程可参考集群扩容;
  3. 数据迁移完毕后, 通知其他master忘记该节点;
  4. 当所有节点都忘记该节点后, 即可下线;

3. 请求路由

从运行时来看, slotId与master节点的映射关系是动态的。因此每次请求都要先确定映射关系, 这就是请求路由。

client端

  1. 连接集群中任意一个host缓存其映射关系快照;
  2. 后续实际访问的过程中更新本地缓存;
  3. 每次操作key之前先读取本地缓存确定目标实例;

server端

ask命令: 如果slot在迁移过程中, 则重定向到源节点或者目标节点确认;
moved命令: 如果slot已经移动完毕, 则返回moved;

migrating节点的读写

当某个节点的状态置为migrating后,表示对应的slot正在导出,为保证该slot数据的一致性,节点此时提供的写服务和通常状态下有所区别。
a. 对于某个迁移中的slot, 如果Client访问的key尚未迁出,则正常的处理该key;
b. 对于某个迁移中的slot, 如果key已经迁出或者key不存在,则回复Client ASK信息让其跳转到importing节点处理;

importing节点的读写

当节点状态变成importing后,表示对应的slot正在导入。此时的读写服务和通常情况下有所区别。
a. 当Client的访问不是从ask跳转的,说明Client还不知道迁移。有可能操作了尚未迁移完成的,处于源节点上面的key,如果这个key在源节点上被修改了,则后续会产生冲突。所以对于该slot上所有非ask跳转的操作, 导入节点不会进行操作,而是通过moved让Client跳转至导出节点执行。
b. 这样的状态控制,保证了同一个key在迁移之前总是在源节点执行,迁移后总是在目标节点执行, 从而杜绝了双写的冲突;
c. 迁移过程中,新增加的key会在目标节点执行,源节点不会新增key, 使得迁移key趋向于收敛, 最终在某个时刻结束。

关于salve节点

a. 单个key的迁移过程可以通过原子化的migrate命令完成;
b. 对于A/B的slave节点则通过主备复制,从而达到增删数据;

4. 迁移小结

  1. 关于slot迁移, 其中存量key的完整性由发起迁移的client保证, 增量key的完整性由Redis Cluster本身保证;
  2. 关于migrate原子性的实现, 个人YY是基于Redis单线程命令执行。当执行用户读写命令时, key存在则执行操作, key不存在则返回ask。migrate操作读取源数据, 写入目标节点, 确认成功后删除源数据的操作, 有点儿类似于同时在migrating和importing节点做操作, 结果一边删除key而另一边增加key。显然, 如果读写是并发执行的, 可能需要额外的协调机制;

四、Failover

同Sentinel 一样,Redis Cluster 也具备一套完整的故障发现、故障状态一致性保证、主备切换机制。

1. 状态变迁

  1. 故障发现:当某个master 宕机时,宕机时间如何被集群其他节点感知。
  2. 故障确认:多个节点就某个master 是否宕机如何达成一致。
  3. slave选举:集群确认了某个master 宕机后,如何将它的slave 升级成新的master;如果有多个slave,如何选择升级。
  4. 集群结构变更:成功选举成为master后,如何让整个集群知道,以更新Cluster 结构信息。

2. 故障发现

单节点感知

Redis Cluster 节点间通过Redis Cluster Bus 两两周期性的PING/PONG 交互。当某个节点宕机时,其他Node 发出的PING消息没有收到响应,并且超过一定时间(NODE_TIMEOUT)未收到,则认为该节点故障,将其置为PFAIL状态(Possible Fail)。后续通过Gossip 发出的PING/PONG消息中,这个节点的PFAIL 状态会传播到集群的其他节点。

网络的不确定性

Redis Cluster的节点两两保持TCP连接,当对PING 无反馈时,可能是节点故障,也可能是TCP链接断开。如果是TCP 断开导致的误报,虽然误报消息会因为其他节点的正常连接被忽略,但是也可以通过一定的方式减少误报。Redis Cluster 通过预重试机制排除此类误报:当 NODE_TIMEOUT/2 过去了,但是还未收到响应,则重新连接重发PING消息,如果对端正常则在很短的时间内就会有响应。同样如果是TCP连接断开, 也会对连接有效性做一次检测, 最终可以得出已确认的网络不可达。

3. 故障确认

对于网络分隔的情况,假设集群有4个节点(A,A1,B,B1),B并没有故障, 然而和B1无法连接,同时可以和A,A1可以正常联通。此时只会有B1将B标记为PFAIL状态,其他节点认为B正常,此时Redis Cluster通过故障确认协议达成一致。

集群中每个节点都是Gossip的接收者, B1也会接收到来自其他节点的GOSSIP消息,被告知B是否处于PFAIL状态。当B1收到来气其他master节点对于B的PFAIL达到一定数量后,会将B的PFAIL状态升级为FAIL状态, 表示B已经确认为故障态。后面会发起master选举流程。

4. slave选举

  1. 如果一个节点B有多个slave(1/2/3)都认知到B处于FAIL状态了,那么可能会同时发起竞选。当B的slave个数 >= 3时,很有可能产生多轮竞选失败。为了减少冲突的出现,优先级高的slave 更有可能发起竞选,从而提升成功的可能性。这里的优先级是slave的数据最新的程度,数据越新的(最完整的)优先级越高。

  2. slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选,master收到后回复FAILOVER_AUTH_ACK消息告知是否同意。slave 发送FAILOVER_AUTH_REQUEST 前会将currentEpoch自增,并将最新的Epoch带入到FAILOVER_AUTH_REQUEST消息中,如果自己未投过票,则回复同意,否则回复拒绝。

5. 最终变更同步

当slave 收到过半的master 同意时,会替代B成为新的master。此时会以最新的Epoch 通过PONG 消息广播自己成为master,让Cluster 的其他节点尽快的更新拓扑结构。
当B恢复可用之后,它仍然认为自己是master,但逐渐的通过Gossip 协议得知某个slave已经替代了自己,然后主动降级为新master的slave。

6. Failover小结

  1. 从整个过程来看, 对于一组主从, 从节点发现主节点断开, 需要借助其他节点来帮助完成故障确认。
  2. 最终, 也是某个slave最先将master标记为Fail。一旦完成对master的Fail标记, slave节点就会发起选举(毕竟最具条件, 时刻准备谋权篡位)。
  3. 在选举成功后, 原来的master也要为新master让步。

总结

本文介绍了Redis Cluster模式集群的搭建、数据分区的迁移以及故障迁移过程, 希望能帮助你对Redis Cluster模式有更进一步的认识和理解, 感谢您的阅读。

你可能感兴趣的:(Redis,redis)