• Redis Cluster




    一、集群搭建

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    二、配置一致性

    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模式有更进一步的认识和理解, 感谢您的阅读。

  • 相关阅读:
    还摆个屁的烂?用Python画如此漂亮的专业插图 ?简直So easy!
    Session与Cookie
    从零实现深度学习框架——衡量算法的基本指标
    计算机毕业设计springboot+vue基本微信小程序的适老化老人健康预警系统
    matlab 读写磁共振影像.nii 数据
    Java实现Excel的导入以及导出,极其简单
    Zabbix
    发育转录组:什么样的猪肉最好吃
    Python + Django4 搭建个人博客(九):引入Bootstrap渲染和美化博文列表
    RSA公私钥对及相关加解密格式详解(一)
  • 原文地址:https://blog.csdn.net/weilaizhixing007/article/details/132611738