Redis 集群
Sentinel 是HA(高可用)解决方案, Cluster与客户端分片是sharding(分布式数据库)方案
一. Sentinel模式
什么是哨兵(Sentinel):
由一个或多个Sentinel实例组成的Sentinel系统可以监视(十秒发送一个INFO心跳)多个主服务器,以及这些主服务器属下的所有从服务器.
在被监视的主服务器进入下线状态时(因为一个主服务器负责本集群的一个Redis分片/一部分槽),哨兵们会从下线的主服务器属下的从服务器中挑选一个升级为新的主服务器.这个过程也叫故障转移操作.- Sentinel本质上是一个运行在特殊模式下的Redis服务器
下线状态检测:
- 主观下线: 当主服务器master连续down-after-milliseconds毫秒都向某个哨兵放回无效回复时,该哨兵就会将master标记为主观下线状态
- 客观下线: 一个哨兵将主服务器判断为主观下线后,向其他同样检测这个服务器的其他哨兵发送询问,若收到的主观下线数量达到阀值,该哨兵就将主服务器判断为客观下线并发送广播.
选举Sentinel领头(RAFT算法):
- 当一个主服务器被判断为客观下线后,监视这个服务器的所有Sentinel会选举出一个领头,由领头对其进行故障转移操作.
所有监视该主服务器的在线Sentinel都有被选举为领头的资格
进行一轮选举后,无论成功与否,所有Sentinel的配置纪元都要自增
配置纪元为一个计数器,用于防止不同轮次选举相互影响所有Sentinel都有一次将除自己之外的某个Sentinel选为设为领头的机会.
每个Sentinel都会要求其他Sentinel将自己设为局部领头.
设置局部领头的规则为先到先得,最先向目标Sentinel发送请求的源Sentinel将会成为模板Sentinel的局部领头.后来的都会被拒绝.
如果存在一个Sentinel被半数以上的Sentinel设为局部领头,则这个Sentinel将成为领头,否则在一段时间后重新进行下一轮选举,直到选出领头为止.
哨兵对主服务器Server的故障转移操作:
- 挑选Server的一个从服务器升级为新的主服务器
- 将Server属下的从服务器成为新主服务器的从服务器
- 监视已下线的Server,重新上线时设置为新主服务器的从服务器
领头Sentinel怎么挑选新的主服务器:
- 根据优先级高, 复制偏移量大(即保存的数据最新), 运行ID小, 依次过滤选出从服务器作为新的主服务器
二. Redis Cluster模式(服务端分片)
集群通过分片来进行数据共享,并提供复制和故障转移功能
集群数据结构:
1. ClusterNode: 每个节点都会用一个clusterNode来记录自己的状态.
并为集群中的所有其他节点都创建一个相应的clusterNode结构来记录其他节点的状态.struct clusterNode{ // 创建节点的时间 mstime_t ctime; // 节点名字 char name[REDIS_CLUSTER_NAMELEN]; // 节点标识: 记录节点角色(主从)与状态(上下线) int flag; // 节点当前的配置纪元 uint64_t configEpoch; // 节点 ip char ip[REDIS_IP_STR_LEN]; // 节点端口 int port; // 二进制数组,用于记录节点处理的槽 unsigned char slots[16384/8]; // 节点处理槽的数量 int numsoltsl; ...... }
2. clusterState: 每个节点都保存着一个clusterState结构,记录当前节点的视角下,集群目前所处的状态:
typeof struct clusterState { // 指向当前节点 clusterNode *myself; // 集群当前的配置纪元 uint64_t currentEpoch; // 集群当前状态: 上下线 int state; // 集群中处理着槽的节点数量 int size; // 集群所有节点名单 // key: 节点名 // value: clusterNode dict *nodes; // 数组的元素用于指向clsuterNode结构的指针 // 代表该槽已经指派给该结构所代表的节点 clusterNode *slots[16384]; ... }
集群添加节点:
节点A将节点B添加到A当前所在集群的握手流程:
- A节点为B节点创建一个clusterNode结构,并将其添加到自己的clusterState.nodes字典中
- 节点A根据客户端命令的IP与端口号,向节点B发送MEET消息
- 节点B收到后为节点A创建一个clusterNode结构,添加到自己的clusterState.nodes中,并返回PONG消息
- 节点A收到PONG后发送PING消息,握手完成
槽的分派:
- 集群的整个数据库被分为16384个槽(solt),数据库中的每个键都属于这16384个槽中的一个,集群中的每个节点可以处理0或最多16384个槽.
- 当每个槽都有节点在处理时,集群处于上线状态; 相反,如果有一个槽没得到处理,集群处于下线状态
客户端与集群的交互:
对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个节点进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该节点上时,Redis会返回转向指令,指向正确的节点.
故障检查:
- 集群中的每个节点都会定期地向集群中的其他节点发送PING消息,检测对方是否在线,如果没有在规定时间回复PONG消息,那么节点就会将其标记为疑似下线.
- 集群中的每个节点都会通过相互发送消息的方式来交换集群中各个节点的状态信息: 在线, 疑似下线, 已经下线.
- 如果有超过半数的处理槽的主节点将某个主节点X标记为疑似下线,则这个节点将被最后一个标记的节点标记为已下线,并发送广播
故障转移:
如果某个节点发现自己的正在复制的主节点已经进入下线状态,开始故障转移操作:
1. 在该下线节点的从节点中选出一个节点称为新的主节点
2. 新主节点撤销下线节点指派的槽,将其指派给自己
3. 向集群发送PONG消息广播,告知自己已经称为主节点并处理哪些槽主节点选举操作(RAFT领头选举算法):
- 集群的配置纪元是一个自增计数器,初始值为0,每经过一次选举,都会增一
- 在一个纪元内,每个负责处理槽的主节点都有一次投票的机会,第一个向主节点要求投票的从节点将获得投票
- 当从节点发现自己的主节点下线后会广播一条消息,要求所有具有投票权的节点向其投票.
- 当一个节点收到超过一半的票数时,该节点升级为主节点.否则进入下一个纪元,重新开始投票,直至选出新主节点
三. Redis sharing(客户端分片, Jedis实现)
采用一致性哈希算法(consistent hashing),将key和节点name同时hashing,然后进行映射匹配,采用的算法是MURMUR_HASH。采用一致性哈希而不是采用简单类似哈希求模映射的主要原因是当增加或减少节点时,不会产生由于重新匹配造成的rehashing。一致性哈希只影响相邻节点key分配,影响量小。
为了避免一致性哈希只影响相邻节点造成节点分配压力,ShardedJedis会对每个Redis节点根据名字(Jedis会赋予缺省名字)会虚拟化出160个虚拟节点进行散列。根据权重weight,也可虚拟化出160倍数的虚拟节点。用虚拟节点做映射匹配,可以在增加或减少Redis节点时,key在各Redis节点移动再分配更均匀,而不是只有相邻节点受影响。