买一个网站需要多少钱百度学术官网入口
文章目录
- 一、集群搭建
- 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 节点规划
- 计划包含多少个master/slave组;
- 每组仅存储部分数据;
- 配置文件中需要启用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 7385redis-cli --cluster create localhost:7380 localhost:7381 localhost:7382 localhost:7383 localhost:7384 localhost:7385 --cluster-replicas 1
二、配置一致性
1 基本分工
- 每个节点保持整个集群的所有信息;
- 每个节点持有集群信息更新版本号, 单调递增;
- 每个节点周期性地向集群中的其他节点发送心跳, PING中携带自身节点信息, PONG中携带对方所知道的集群信息;
2 更新规则
- 当某个节点率先知道了变更时,将自身的currentEpoch自增,并使之成为集群中的最大值。再用自增后的currentEpoch 作为新的Epoch版本;
- 当某个节点收到了比自己大的currentEpoch时,更新自己的currentEpoch;
- 当收到的Redis Cluster Bus 消息中的某个节点的Epoch > 自身的时,将更新自身的内容;
- 当Redis Cluster Bus 消息中,包含了自己没有的节点时,将其加入到自身的配置中。
上述的规则保证了信息的更新都是单向的,最终朝着Epoch更大的信息收敛。同时Epoch也随着currentEpoch的增加而增加,最终将各节点信息趋于稳定。
三、Sharding
1 数据分片
分片实现
- server默认整个集群有16384个slot, 然后在集群初始化时会均匀分配到每个master上, 进而可以知道每个slotId与master的对应关系。
- 由于Redis是K-V结构, 当新增某个key时, 通过算法(slotId = crc16(key) % 16384)得到slotId, 然后将目标的key写入目标slot。这里想强调的是, 由于key本身是固定的, 因此其对应的slotId也是固定的, 这是一种逻辑上的对应关系。而物理对应关系则是由配置信息中slotId与master节点的映射来实现。而这个关系对Redis client来说非常重要。
分片特点
- 解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
- 节点自身维护槽的映射关系,不需要客户端 或 代理服务维护数据分片关系。
- Redis Cluster的节点之间会共享消息,每个节点都知道另外节点负责管理的槽范围。每个节点只能对自己负责的槽进行维护 和 读写操作。
虽然每个分片(shard)维护一定数量的slot, 但slot与shard的映射关系是可以动态调整的。此外, 数据迁移也是以slot为单位进行。
2 slot迁移
当sharding rebalance时, slotId与master的映射关系发生变化, slotId与key的映射关系不变。
此外, 具体的业务场景中, 考虑到数据的局部性, 可能会把相关的数据放入同一个slot上, 此时可以在key中加入{}。此时Redis server不再使用整个key, 而是仅使用{}中的内容来计算slotId。
迁移原因
- 新的master节点加入;
- 旧节点关机下线或者维护;
- 数据分布不均衡, 需要手动调整slot位置以均衡压力;
迁移支持
具体迁移过程由外部触发, Redis Cluster本身只提供了迁移过程中需要的指令支持。
- 节点迁移状态设置, 迁移前标记源/目标节点;
- key迁移的原子化命令;
- 将迁移后的配置循环广播到其他master节点;
集群扩容
- 将新节点加入集群;
cluster meet ip
- 在新节点上设置待导入的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
-
从源节点发起迁移
migrate {targetNodeIp} {targetNodePort} "" 0 {timeout} keys { key... }
migrate localhost 7381 “” 0 1000 keys key-c19780 key-c13965 key-c9249
该步骤手动执行, 如果目标地址错误, 则数据丢失。仅有 -
重复4和5直到获取不到新的key
-
广播新的slot位置
cluster setslot {slotId} node {nodeId}
cluster setslot 0 node 7fc05faa8893c7f75aab12e057a40176a873e4ca
设置会让导入节点的Epoch自增,成为Cluster中的最新值,然后通过Redis Cluster Bus相互感知,传播到Cluster中的其他节点。
7. 如果是存量集群内部迁移, 则不考虑新节点加入, 其他步骤相同。
迁移错误
背景
源和目标节点状态设置正确, 但migrate的目标位置错误不是既定的目标节点;
现象
源节点slot状态为migrating;
目标节点slot状态为importing;
migrate也迁移成功,然后在不同节点的表现有点花:
- 在源节点上get返回ASK {实际migrate节点};
- 在{migrate目标节点}get返回MOVED {源节点};
- 在importing节点上get返回MOVED {源节点};
问题分析
-
数据是否丢失?
a. 从上面get的结果来看, 无法从任何一个节点中读取出来;
b. 从migrate成功来看, 数据一定在{实际migrate节点}上;
c. 在{实际migrate节点}上, 执行keys发现key是存在的, 因此可以确定数据未丢失; -
为何无法读取?
显然每个key都有对应的slot, 并且slot需要在当前的节点。因此该问题的root cause是, 数据位置和slot配置信息不一致。而目前由于value无法读取, 因此无法再对数据做移动。只能调整slot位置, 尝试通过cluster set slot设置slot的位置为当前节点。
验证
- 执行cluster slot命令到实际数据接收节点, 成功;
- 在实际数据接收节点执行get key命令, 成功;
- 在其他节点执行get key命令, 统一MOVDED到新节点;
- 至此, 问题解决;
猜想
- Redis Cluster的配置和存储之间没有强约束, 也就是虽然我当前节点没有某个slot的配置信息, 但是migrate的情况下依然可以接收目标key, 只是无法读取出来;
- 设置importing和migrating的状态, 仅是告诉client这个slot中的key有不确定性。这种不确定性由其他节点完成最终确定, 如果是人为错误, 就一直在确定的路上;
- 每个节点由于不掌握全局信息, 只能按照自己已知的信息来回复client;
- 其实slot的迁移直接可以通过cluster setslot完成, 其中的数据迁移依赖外部的处理, 至于迁移过程中client的访问结果则看server的状态;
- 总体来看, 整个过程耦合非常松散, 手动操作容易出错, 运维时得务必小心;
集群缩容
- 确认目标节点是否有负责的slot;
- 如果有负责的slot, 则需要将其迁移到其他节点上, 数据迁移过程可参考集群扩容;
- 数据迁移完毕后, 通知其他master忘记该节点;
- 当所有节点都忘记该节点后, 即可下线;
3. 请求路由
从运行时来看, slotId与master节点的映射关系是动态的。因此每次请求都要先确定映射关系, 这就是请求路由。
client端
- 连接集群中任意一个host缓存其映射关系快照;
- 后续实际访问的过程中更新本地缓存;
- 每次操作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. 迁移小结
- 关于slot迁移, 其中存量key的完整性由发起迁移的client保证, 增量key的完整性由Redis Cluster本身保证;
- 关于migrate原子性的实现, 个人YY是基于Redis单线程命令执行。当执行用户读写命令时, key存在则执行操作, key不存在则返回ask。migrate操作读取源数据, 写入目标节点, 确认成功后删除源数据的操作, 有点儿类似于同时在migrating和importing节点做操作, 结果一边删除key而另一边增加key。显然, 如果读写是并发执行的, 可能需要额外的协调机制;
四、Failover
同Sentinel 一样,Redis Cluster 也具备一套完整的故障发现、故障状态一致性保证、主备切换机制。
1. 状态变迁
- 故障发现:当某个master 宕机时,宕机时间如何被集群其他节点感知。
- 故障确认:多个节点就某个master 是否宕机如何达成一致。
- slave选举:集群确认了某个master 宕机后,如何将它的slave 升级成新的master;如果有多个slave,如何选择升级。
- 集群结构变更:成功选举成为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选举
-
如果一个节点B有多个slave(1/2/3)都认知到B处于FAIL状态了,那么可能会同时发起竞选。当B的slave个数 >= 3时,很有可能产生多轮竞选失败。为了减少冲突的出现,优先级高的slave 更有可能发起竞选,从而提升成功的可能性。这里的优先级是slave的数据最新的程度,数据越新的(最完整的)优先级越高。
-
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小结
- 从整个过程来看, 对于一组主从, 从节点发现主节点断开, 需要借助其他节点来帮助完成故障确认。
- 最终, 也是某个slave最先将master标记为Fail。一旦完成对master的Fail标记, slave节点就会发起选举(毕竟最具条件, 时刻准备谋权篡位)。
- 在选举成功后, 原来的master也要为新master让步。
总结
本文介绍了Redis Cluster模式集群的搭建、数据分区的迁移以及故障迁移过程, 希望能帮助你对Redis Cluster模式有更进一步的认识和理解, 感谢您的阅读。