Redis Cluster
Redis Cluster 是 Redis3.0 之后,官方提供的 Redis 集群解决方案,由 Redis
官方团队来实现。
在 3.0 之前为了解决容量高可用用方面的需求基本上只能通过客户端分片 + Redis sentinel 或者代理(twemproxy、codis)方案解决。
Redis Cluster 最大的特性,就是对 Redis 集群提供了水平扩展的能力(horizential scalibility),即当整个 Redis 集群出现存储容量或者性能 bottleneck 时,使用 Redis Cluster 可以通过增加新的 master Redis node 从而快速解决 bottleneck。
除此之外,Redis Cluster 提供了强大的高可用机制(即 failure failover)。即当集群中任何一个 master Redis node 无法正常工作时(比如因为底层依赖的硬件故障、网络问题),它对应的 slave Redis node 就会自动代替它(当然,这里有一个前提,是我们设置了 slave Redis node)。
与 Codis 和 Twemproxy 不同的是:Redis Cluster 并非使用 Porxy 模式来连接集群节点,而是使用无中心节点的模式来组建集群(即没有 coordinator)。
Redis Cluster 实现在多个节点之间进行数据共享,即使部分节点失效或者无法进行通讯时,Cluster 仍然可以继续处理请求。若每个主节点都有一个从节点支持,在主节点下线或者无法与集群的大多数节点进行通讯的情况下, 从节点提升为主节点,并提供服务,保证 Cluster 正常运行。
Redis Cluster 数据分片(Data Sharding)
Redis Cluster 的数据分片是通过哈希槽(hash slot)实现的,而不是使用一致性哈希,即 consistent hashing。
There are 16384 hash slots in Redis Cluster, and to compute what is the hash slot of a given key, we simply take the CRC16 of the key modulo 16384.
Every node in a Redis Cluster is responsible for a subset of the hash slots, so for example you may have a cluster with 3 nodes, where:
- Node A contains hash slots from 0 to 5500.
- Node B contains hash slots from 5501 to 11000.
- Node C contains hash slots from 11001 to 16383.
每个 key 存放在这 16384(0~16383) 个哈希槽中的其中一个,每个 Redis node 存储一部分哈希槽。
This allows to add and remove nodes in the cluster easily. For example if I want to add a new node D, I need to move some hash slot from nodes A, B, C to D. Similarly if I want to remove node A from the cluster I can just move the hash slots served by A to B and C. When the node A will be empty I can remove it from the cluster completely.
Because moving hash slots from a node to another does not require to stop operations, adding and removing nodes, or changing the percentage of hash slots hold by nodes, does not require any downtime.
Redis Cluster supports multiple key operations as long as all the keys involved into a single command execution (or whole transaction, or Lua script execution) all belong to the same hash slot. The user can force multiple keys to be part of the same hash slot by using a concept called hash tags.
概念特点
- 去中心、去中间件,各节点平等,保存各自数据和集群状态,节点间活跃互连。
- 传统用一致性哈希分配数据,集群用哈希槽(hash slot)分配。 算法为 CRC16。
- 默认分配 16384 个 slot, 用 CRC16 算法取模
CRC16(key)%16384
计算所属 slot。 - 每个主节点存储一部分哈希槽
- 最少 3 个主节点
优点和不足
优点
- 官方解决方案
- 可以在线水平扩展(Twemproxy 的一大弊端就是不支持在线扩容节点)
- 客户端直连,系统瓶颈更少
- 无中心架构
- 支持数据分片
Redis Cluster 集群现实存在的问题
尽管属于无中心化架构一类的分布式系统,但不同产品的细节实现和代码质量还是有不少差异的,就比如 Redis Cluster 有些地方的设计看起来就有一些 “奇葩” 和简陋:
- 不能自动发现:无 Auto Discovery 功能。集群建立时以及运行中新增结点时,都要通过手动执行 MEET 命令或 redis-trib.rb 脚本添加到集群中
- 不能自动 Resharding:不仅不自动,连 Resharding 算法都没有,要自己计算从哪些结点上迁移多少 Slot,然后还是得通过 redis-trib.rb 操作
- 严重依赖外部 redis-trib:如上所述,像集群健康状况检查、结点加入、Resharding 等等功能全都抽离到一个 Ruby 脚本中了。还不清楚上面提到的缺失功能未来是要继续加到这个脚本里还是会集成到集群结点中?redis-trib 也许要变成 Codis 中 Dashboard 的角色
- 无监控管理 UI:即便未来加了 UI,像迁移进度这种信息在无中心化设计中很难得到
- 只保证最终一致性:写 Master 成功后立即返回,如需强一致性,自行通过 WAIT 命令实现。但对于 “脑裂” 问题,目前 Redis 没提供网络恢复后的 Merge 功能,“脑裂” 期间的更新可能丢失
注意,如果设置 Redis Cluster 的数据冗余是 1 的话,至少要 3 个 Master 和 3 个 Slave。
Redis Cluster 容错机制 - failover
failover 是 redis cluster 的容错机制,是 redis cluster 最核心功能之一;它允许在某些节点失效情况下,集群还能正常提供服务。
redis cluster 采用主从架构,任何时候只有主节点提供服务,从节点进行热备份,故其容错机制是主从切换机制,即主节点失效后,选取一个从节点作为新的主节点。在实现上也复用了旧版本的主从同步机制。
从纵向看,redis cluster 是一层架构,节点分为主节点和从节点。从节点挂掉或失效,不需要进行 failover,redis cluster 能正常提供服务;主节点挂掉或失效需要进行 failover。另外,redis cluster 还支持 manual failover,即人工进行 failover,将从节点变为主节点,即使主节点还活着。
下面将介绍这两种类型的 failover。
主节点失效产生的 failover
1 (主)节点失效检测
一般地,集群中的节点会向其他节点发送 PING 数据包,同时也总是应答(accept)来自集群连接端口的连接请求,并对接收到的 PING 数据包进行回复。当一个节点向另一个节点发 PING 命令,但是目标节点未能在给定的时限(node timeout)内回复时,那么发送命令的节点会将目标节点标记为 PFAIL(possible failure)。
由于节点间的交互总是伴随着信息传播的功能,此时每次当节点对其他节点发送 PING 命令的时候,就会告知目标节点此时集群中已经被标记为 PFAIL 或者 FAIL 标记的节点。相应的,当节点接收到其他节点发来的信息时, 它会记下那些被其他节点标记为失效的节点。 这称为失效报告(failure report)。
如果节点已经将某个节点标记为 PFAIL,并且根据节点所收到的失效报告显式,集群中的大部分其他主节点(n/2+1)也认为那个节点进入了失效状态,那么节点会将那个 PFAIL 节点的状态标记为 FAIL。
一旦某个节点被标记为 FAIL,关于这个节点已失效的信息就会被广播到整个集群,所有接收到这条信息的节点都会将失效节点标记为 FAIL。
2 选举主节点
一旦某个主节点进入 FAIL 状态, 集群变为 FAIL 状态,同时会触发 failover。failover 的目的是从从节点中选举出新的主节点,使得集群恢复正常继续提供服务。
整个主节点选举的过程可分为申请、授权、升级、同步四个阶段:
1 申请
新的主节点由原已失效的主节点属下的所有从节点中自行选举产生,从节点的选举遵循以下条件:
- 这个节点是已下线主节点的从节点;
- 已下线主节点负责处理的哈希槽数量非空;
- c、主从节点之间的复制连接的断线时长有限,不超过 ((node-timeout * slave-validity-factor) + repl-ping-slave-period )。
如果一个从节点满足了以上的所有条件,那么这个从节点将向集群中的其他主节点发送授权请求,询问它们是否允许自己升级为新的主节点。
从节点发送授权请求的时机会根据各从节点与主节点的数据偏差来进行排序,让偏差小的从节点优先发起授权请求。
2 授权
其他主节点会遵信以下三点标准来进行判断:
- 发送授权请求的是从节点,而且它所属的主节点处于 FAIL 状态 ;
- 从节点的 currentEpoch〉自身的 currentEpoch,从节点的 configEpoch>= 自身保存的该从节点的 configEpoch;
- 这个从节点处于正常的运行状态,没有被标记为 FAIL 或 PFAIL 状态;
如果发送授权请求的从节点满足以上标准,那么主节点将同意从节点的升级要求,向从节点返回 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 授权。
3 升级
一旦某个从节点在给定的时限内得到大部分主节点(n/2+1)的授权,它就会接管所有由已下线主节点负责处理的哈希槽,并主动向其他节点发送一个 PONG 数据包,包含以下内容:
- 告知其他节点自己现在是主节点了
- 告知其他节点自己是一个 ROMOTED SLAVE,即已升级的从节点;
- c、告知其他节点都根据自己新的节点属性信息对配置进行相应的更新
4 同步
其他节点在接收到 ROMOTED SLAVE 的告知后,会根据新的主节点对配置进行相应的更新。特别地,其他从节点会将新的主节点设为自己的主节点,从而与新的主节点进行数据同步。
至此,failover 结束,集群恢复正常状态。
此时,如果原主节点恢复正常,但由于其的 configEpoch 小于其他节点保存的 configEpoch(failover 了产生较大的 configEpoch),故其配置会被更新为最新配置,并将自己设新主节点的从节点。
另外,在 failover 过程中,如果原主节点恢复正常,failover 中止,不会产生新的主节点。
Manual Failover
Manual Failover 是一种运维功能,允许手动设置从节点为新的主节点,即使主节点还活着。
Manual Failover 与上面介绍的 Failover 流程大都相同,除了下面两点不同:
- 触发机制不同,Manual Failover 是通过客户端发送 cluster failover 触发,而且发送对象只能是从节点;
- 申请条件不同,Manual Failover 不需要主节点失效,failover 有效时长固定为 5 秒,而且只有收到命令的从节点才会发起申请。
另外,Manual Failover 分 force 和非 force,区别在于:非 force 需要等从节点完全同步完主节点的数据后才进行 failover,保证不丢失数据,在这过程中,原主节点停止写操作;而 force 不进行进行数据完整同步,直接进行 failover。
集群状态检测
集群有 OK 和 FAIL 两种状态,可以通过 CLUSTER INFO 命令查看。当集群发生配置变化时, 集群中的每个节点都会对它所知道的节点进行扫描,只要集群中至少有一个哈希槽不可用(即负责该哈希槽的主节点失效),集群就会进入 FAIL 状态,停止处理任何命令。
另外,当大部分主节点都进入 PFAIL 状态时,集群也会进入 FAIL 状态。这是因为要将一个节点从 PFAIL 状态改变为 FAIL 状态,必须要有大部分主节点(n/2+1)认可,当集群中的大部分主节点都进入 PFAIL 时,单凭少数节点是没有办法将一个节点标记为 FAIL 状态的。 然而集群中的大部分主节点 (n/2+1) 进入了下线状态,让集群变为 FAIL,是为了防止少数存着主节点继续处理用户请求,这解决了出现网络分区时,一个可能被两个主节点负责的哈希槽,同时被用户进行读写操作(通过禁掉其中少数派读写操作,证保只有一个读写操作),造成数据丢失数据问题。
说明:上面 n/2+1 的 n 是指集群里有负责哈希槽的主节点个数。
扩容 & 缩容
扩容
当集群出现容量限制或者其他一些原因需要扩容时,redis cluster 提供了比较优雅的集群扩容方案。
首先将新节点加入到集群中,可以通过在集群中任何一个客户端执行 cluster meet 新节点 ip: 端口,或者通过 redis-trib add node 添加,新添加的节点默认在集群中都是主节点。
迁移数据
迁移数据的大致流程是,首先需要确定哪些槽需要被迁移到目标节点,然后获取槽中 key,将槽中的 key 全部迁移到目标节点,然后向集群所有主节点广播槽(数据)全部迁移到了目标节点。直接通过 redis-trib 工具做数据迁移很方便。 现在假设将节点 A 的槽 10 迁移到 B 节点,过程如下:
1 | B:cluster setslot 10 importing A.nodeId |
循环获取槽中 key,将 key 迁移到 B 节点
1 | A:cluster getkeysinslot 10 100 |
向集群广播槽已经迁移到 B 节点
1 | cluster setslot 10 node B.nodeId |