目录
目标
概述
优势
每个node需要两个TPC端口
hash slot 哈希槽
master-slave模式
集群不保证写的强一致性
说明书
设计说明
目标
命令支持
集群中server和client角色
可接受的写安全
一定程度的可用性
性能
主要组件
key分布式模型
key 哈希tag
node属性
cluster bus
重定向
运行时reconfiguration
容错
Heartbeat
失败监测
配置
epoch
currentEpoch
configEpoch
slave选举与提升
slave rank
node重新加入集群
副本迁移Replica migration
node reset
node remove
目标
对redis cluster两篇官网doc进行翻译和整理,以便加深了解原理。
概述
优势
- 多个node间自动分片
- 通过slave提升和副本迁移,提升集群可用性
每个node需要两个TPC端口
- 服务端口,为client提供服务,如6379
- cluster bus端口,服务端口+10000,如16379,cluster bus用于node间互联互通,如失败监测、配置更新、故障切换授权等
hash slot 哈希槽
- 集群具有(2的14方)16384个哈希槽,计算key对应的哈希槽的方式 CRC16(key) mod 16384
- 集群中每个master负责哈希槽的子集
- 运行时动态增减node,不需要集群启停。
- 抽象为哈希槽的迁移
- 减node A
- 将A负责的哈希槽分配给其他node
- 其他所有master上执行cluster forget A
- 增加node
- 添加节点
- 作为master:redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
- 作为salve:redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave --cluster-master-id masterId
- 通过命令重新分片
- redis-cli reshard : --cluster-from --cluster-to --cluster-slots --cluster-yes
master-slave模式
为master节点增加slave,作为副本,当master故障时,集群会选slave成为新master,保证集群可用。
集群不保证写的强一致性
说明书
设计说明
目标
- 高性能,线性扩展(最多1000节点)。不使用代理、异步复制,不支持合并类操作。
- 可接受的写安全
- 可用性。大部分master可用且不可用master至少具有一个slave时,可保证集群所有分区继续可用。
命令支持
- 所有单key的命令
- 待操作key在同一个node上的多key操作,如并集 交集
集群中server和client角色
集群Node负责存储数据\维护集群状态。
所有Node通过redis cluster bus进行互联互通,实现自动发现Node、(通过心跳包)监测Node、失败恢复时提升slave为master
等。redis cluster bus包括TCP和binary协议。
Node不支持请求重定向,所以返回重定向ERROR,让client自行重定向。
可接受的写安全
redis cluster采用[master和slave间异步复制]与[最近容错为准]两种方式进行隐式数据合并。
最近容错为准,理解为最近选举为master的slave上的数据集会覆盖其他副本,未复制的数据将丢失。
采用时间窗口NODE_TIMEOUT,允许master node在该期间恢复,否则master失联,进行故障切换,丢失未复制的写操作。
数据丢失的场景
- master接收到client的写操作,之后向client发送返回结果,但是异步复制还未送达到slave,此时master故障,且在NOTE_TIMEOUT后仍不可达,则选举一个slave作为master
- slave升为master后,client由于未更新哈希槽和node关系,仍向原master写。这种情况不可能发生,因为原master将不接受写。
时间窗口的作用
- 允许master node在该期间恢复
- 判断master node是否失联
一定程度的可用性
多数master可达且少数不可达master至少有一个slave,则经过(NODE_TIMEOUT+1至2秒)后集群再次可用。
仅当集群中少量master node不可达时,才可维持可用性。
可用的可能性
- N个master且各有一个slave,可用的可能性为1 - 1/(N*2-1)
- 一个master不可达,则其slave升为master,则之后该master不可达的可能性为1/(N*2-1),集群可用的可能性为1 - 1/(N*2 -1)
- 3个master,3个slave,则可用可能性为1 - 1/(3*2 -1) = 80%
副本迁移replicas migration特性,提升了集群可用性。每次故障切换后,自动对slave重新分配,达到每个master都有slave的目标。
性能
node不会代理命令执行,而是将用户重定向正确的node。client端收到重定向后,会更新slot-node map,之后client直接向正确的node发送命令。
异步复制,不会等待其他node的ack。
仅支持相同node上的多key操作,除非重分片否则数据不会在node间交换。
线性的性能扩展,具有N个master的集群,性能扩展为redis单例的N倍。
对redis cluster,性能和扩展性是其次,写安全和可用性才是重点。
主要组件
key分布式模型
- key空间被分为16384个槽,理论上允许扩展为16384个master,但是实际建议最多1000个node。
- 每个node负责部分槽,不进行重分片时,集群是稳定的。
- key定位槽:HASH_SLOT = CRC16(key) mod 16384
key 哈希tag
确保多key被定位到同一个哈希槽。仅使用key中第一个{和第一个}之间的字符数大于1的字符串去匹配哈希槽。
举例
- {user1000}.following和{user1000}.followers,均使用user1000进行计算,所以确定同一个哈希槽
- foo{}{bar} 第一个{和第一个}之间没有字符,所以使用 foo{}{bar}去计算
- foo{{bar}}zap 使用{bar去计算
- foo{bar}{zap} 使用bar计算
node属性
关于自身节点
- nodeID
- IP:Port
- 当前node的配置详情
关于其他节点
- node id, address:port, flags, last ping sent, last pong received, configuration epoch, link state, slots
- 在任一台node上执行:redis-cli cluster nodes
cluster bus
-
每个node需要提供一个TCP port接收来自其他node的连接。
-
bus port = 处理client请求的port+10000
-
node间交互需用通过cluster bus和cluster bus protocl。该协议没有公开,因为不需要外界软件与node进行交流。
重定向
node接收到client操作命令后,会验证key对应slot是否由其负责,如果不是则返回MOVED error,携带key对应slot和node坐标。
如-MOVED 3999 127.0.0.1:6381
client收到MOVED返回后,应直接更新slot-node路由(jedis cluster是如此实现的)
运行时reconfiguration
支持运行时增减node,抽象为将slot从一个node移动到另一个node。一个哈希槽对应很多key,所以重分片过程做的主要事情就是移动key-val。
提供的命令,具体作用可参见官网或后续的命令详解
CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
CLUSTER SETSLOT slot NODE node
CLUSTER SETSLOT slot MIGRATING node
CLUSTER SETSLOT slot IMPORTING node
容错
Heartbeat
node间发送PING和PONG包统称为心跳包,两种包具有相同的结构,都携带主要配置信息,只是type field不同。
一般node发送ping包,要求接收者发送pong包并携带其配置信息。
node可直接发送pong包给其他node并携带自己的配置信息,如广播新配置。
node如何选择需要ping的用户
- 没有发送过ping
- 超过半个NODE_TIMEOUT后,仍没有收到pong
- 举例
- 100个node,NODE_TIMEOUT为60s,则每个node,在30s内发送99个ping包,即每秒3.3,则100个node,每秒发330个ping包。
包结构
- header部分
- 包含node基本信息
- nodeId
- currentEpoch and configEpoch 与版本递增算法有关,用于判断心跳包是否最新、slave选举和提升。
- node flag 标记slave或master
- 该node负责的哈希槽的bitmap,对于slave,bitmap与master相同
- prot 服务port,可推算出bus port
- state 从该node视图观察的集群状态
- 对于slave,携带master nodeId
- gossip section闲聊部分
- 由发送node了解的其他node的信息,用于失败监测和node发现。
- Node ID.
- IP and port of the node.
- Node flags.
失败监测
当集群中大部分node都认为某node不可达,则提升slave为master,若不存在slave,则整个集群设置为error状态且不再接受client请求。
通过node flag判断node状态
- PFAIL flag
- 无论master或slave,当发往某个node的ping,在NODE_TIMEOUT后没有得到回复,均可标记该node为PFAIL
- 当经过半个NOTE_TIMEOUT还未收到PONG,该node会与集群中其他node重建连接,以确定网路无问题。
- FAIL flag
- 心跳包的gossip部分会携带node flag,集群中某些nodeA会统计其他node关于失联nodeB的flag,如果在NOTE_TIMEOUT*FAIL_REPORT_VALIDITY_MULT时间后仍是PFAIL,则将nodeB flag置为FAIL,并广播通知所有node,每个node将nodeB flag置为FAIL。FAIL_REPORT_VALIDITY_MULT默认是2.
消除FAIL flag情况
- slave node 重新可达
- 不负责任何哈希槽的master重新可达
- 经过N个NOTE_TIMEOUT后没有发生slave提升,mater重新可达,则master会重新加入集群。
配置
epoch
- 代表递增版本算法。
- 当收到多个node的信息存在冲突时,用于判断哪个是最新的。
currentEpoch
- 64位的正整数
- 无论master或slave,新建node时,currentEpoch初始为0.
- nodeA每次收到其他node的包后,如果header中epoch大于nodeA epoch,则nodeA更新自己的epoch。
- 集群中node赞同epoch最高的消息,用于slave提升。
configEpoch
- 每个master的PING PONG包中会携带自己的configEpoch
- 集群中新建node时,master中configEpoch重置为0。
- 在salve提升过程中会新建configEpoch。尝试替换失败master的slave,会增加自己的currentEpoch,并尝试得到大部分master的授权。一旦得到授权,salve会新建一大于其他master的configEpoch,并使用该configEpoch将自己转变为master。
- slave会携带其master的configEpoch,用于其他node判断该slave是否需要及时更新,不允许携带旧版configEpoch的slave参数投票。
slave选举与提升
- master参与投票,由slave进行控制的过程。
- slave想将自己提升为master,必须发起一次投票并获胜。FAIL master对应的所有slave均可发起投票,但只有一个slave获胜。
- 可发起选举的slave
- 其master处于FAIL
- 其master负责哈希槽数量大于0
- 复制连接的断开时间必须小于某个时间段(可配置),已保证数据较新
- 选举过程
- slave增加其currentEpoch
- 向所有master发送FAILOVER_AUTH_REQUEST包,要求进行投票
- master收到包后,可回复FAILOVER_AUTH_ACK。针对同一个FAIL master的投票,在NOTE_TIMEOUT*2的时间内,该master不可以重复投票
- slave在发起投票的NOTE_TIME*2的时间内,仅针对currentEpoch大于发起投票的currentEpoch的AUTH_ACK进行计数。避免历史投票的影响。
- 如果得到了大部分master的投票,则获胜。新建并增加configEpoch,大于其他master,并通过PONG广播告知自己成为master。
- 否则只能在NOTE_TIMEOUT*4时间段后重新发起。
slave rank
- 当master处于FAIL后,slave需要等待一段时间才可以发起投票
- DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + SLAVE_RANK * 1000 milliseconds.
- delay的作用:以便集群中所有master都知道FAIL master,避免拒绝投票。
- random dlay的作用:避免slave同时发起投票
- slave_rank 与slave从master复制数据的数量有关
- 当master FAIL后,slave会互相通信已确定各自的rank,数据最新的slave其rank为0,次新的slave其rank为1,依次设置。
- 因此具有较新数据的slave可优先发起投票。
node重新加入集群
- 之前的master A重新加入集群,向其他节点发送心跳;
- 接收节点发现A表示负责slot1和2,但是具有更高configEpoch的nodeB也负责slot1和2,接收节点会向A发送UPDATE信息,告知新配置;
- A收到信息后,将自己作为B的slave。
副本迁移Replica migration
- 用于提升系统的可用性
- 问题场景
- master A具有一个slave A1
- A FAIL,A1成为master
- 一段时间后,A1 FAI
- 集群无法提供服务
- 一种缓解问题的方式,需要为每个master提供多个slave,这样必然引起资源浪费。
- 另一种缓解方式-副本迁移,非对称的master-slave布局,让程序自动调整master-slave关系。
- 副本迁移举例
- master A和B,各自具有一个slave A1和B1;master C,具有两个slave C1,C2
- A FAIL,A1提升
- 副本迁移,将C2前一位A1的slave
- A1 FAIL,C2提升
- 集群继续提供服务
- cluster-migration-barrier 迁移slave前,slave对应的master最少必须具有的slave数量。
node reset
- node可以通过命令进行重置,之后用于不同角色或不同集群。
- 命令须在待重置的node上执行
- 命令
- CLUSTER RESET SOFT
- CLUSTER RESET HARD
- 影响
- Soft and hard reset
- 如果是slave,则重置为master,且之前的dataset全部丢弃。
- 如果是master且持有key-val,则命令不生效。
- 所有负责的slot被释放,失败状态被重置
- 删除该node记录的其他node信息
- Hard reset only
- currentEpoch, configEpoch, and lastVoteEpoch重置为0
- nodeId重置为新的随机ID
node remove
- 可以先重分片后shutdown方式删除node
- 如果其他node一直记录nodeID,则需要在每个node上强制清除
- CLUSTER FORGET
- 该命令做两件事
- 从node表中删除该nodeID
- 60秒内,阻止具有相同nodeId的node重新进入集群
图示与思考
client端存储与路由,server分片。
为什么是16384?
作者出于心跳包大小、最多1000master节点、离散性的考虑,所以使用CRC16的16位输出的低14位,所以是16384。
为什么选用CRC16?
从离散角度考虑:CRC可生成固定位的散列值;
从数据大小考虑:作者只用14位,所以选择CRC16,而不是CRC32等