Redis Cluster 介绍(一)
Contents
本文翻译自 Redis cluster tutorial (https://redis.io/topics/cluster-tutorial)
Redis Cluster 提供了在多个Redis节点上数据自动分片的机制,同时也保证了一定程度的可用性。当一些节点故障或者通信中断时,Redis Cluster 也可以继续进行其操作。但是当遇到集群大规模故障时(例如,大多数master节点不可用),集群将无法继续操作。
所以,在实际应用中,Redis Cluster 提供了如下的能力:
- 数据在多节点之间分片存储;
- 当遇到少数节点故障或者其与剩余节点无法通信时,依旧可以正常运行。
Redis Cluster TCP ports
每个节点都需要开启两个TCP连接,第一个是端口是服务于client端,例如Redis默认的端口号6379;第二个端口号在其基础上加10000,变为16379,作为集群节点之间数据传输的端口号。
第二个端口号被用于集群总线(Cluster bus),这是一个在节点之间使用二进制协议的通信通道。集群总线被Redis节点用于故障探测、配置更新、故障转移(failover authorization)等。Redis Client不应该与集群总线的端口进行通信,而应该总是与Redis的命令端口进行通信。确保在系统防火墙上开启了这两个端口,否则集群中的节点无法正常通信。
命令端口和集群总线端口,这两者之间的差值是固定的,为10000。
为了确保 Redis Cluster 正常工作,每个节点需要满足如下:
- 使客户端通过通信端口(通常是6379),可以访问到集群,也可以增加集群节点;
- 集群总线端口(客户端端口+10000),对于其他所有集群节点必须是可达的。
Redis Cluster and Docker
目前Redis Cluster 不支持NAT环境和IP地址与端口号重新映射的环境。
Docker使用了一种叫做端口映射的技术:运行在Docker容器内的程序会有两个接口,一个是在Docker容器内部程序使用的接口,一个是Docker容器暴露给外界的接口。这种有助于程序在同一个服务器下,在相同的时间内,使用同一个端口在多个容器内运行。
为了使Docker与Redis Cluster更好的适配,需要使用host networking mode
的Docker。更多信息查看 Docker documentation。
Redis Cluster data sharding
Redis Cluster 并不使用一致性哈希,而是使用一种不同的分片方法–哈希槽(hash slot)来概念上划分每一个key。
在Redis Cluster中共有 16348 个哈希槽,使用公式CRC16(key) % 16348
,来计算每一个key对应的哈希槽。
在Redis Cluster中每一个节点存储部分的哈希槽,例如一个有三个节点的集群:
- 节点A包含0~5500的哈希槽;
- 节点B包含5501~11000的哈希槽;
- 节点C包含11001~16383的哈希槽。
这种方式可以很容易的增加或者删除节点。如果要增加一个节点D,需要将节点A、B、C中的一些哈希槽移动到节点D。同样的,如果要删除节点A,就将节点A中的哈希槽移动到节点B、C,当节点A的哈希槽移动完成后,就可以删除节点A。
因为节点之间移动哈希槽不需要集群停止服务,增加或删除节点,或者是改变节点中哈希槽的百分比也不会造成集群不可用。
Redis Cluster 支持在属于同一个哈希槽下使用一个命令(或者是整个事务、Lua脚本)来操作多个key。用户可以通过使用hash tags来强制将多个key指向同一个哈希槽。
有关hash tags,可以查看Redis Cluster Specification。
Redis Cluster master-slave model
为了保证当集群中少部分master节点故障或者无法与大多数节点通信时依旧保持集群的可用性,Redis Cluster 使用了主从模式,每个哈希槽除了自身外,都会有N-1个备份。
在上述例子中,如果节点B故障了,那么集群就处于不可用的状态,因为无法获取5501~11000的哈希槽。
我们可以为集群中的master节点创建其slave节点,这样集群就由A、B、C、A1、B1、C1组成,如果节点B故障的话,集群依旧可以正常运行,这时节点B1会成为master节点。但是如果B、B1同时故障的话,那么集群仍旧会处于不可用状态。
Redis Cluster consistency guarantees
Redis Cluster并不保证强一致性,实际使用中,在某些特定的情况下,集群可能在系统向客户端确认时丢失写操作。
第一个可能原因是集群使用异步备份的方式。比如下列场景:
- 客户端向master节点B发送写操作;
- master节点B向客户端确认成功;
- B向它的从节点B1、B2、B3发送写操作。
正如上面描述的,节点B并不等待所有的从节点写入成功后再返回客户端写入成功的消息,因为会导致Redis集群的响应效率降低。如果客户端发起写操作,节点B确认了写入,但是在B向其从节点发送写操作时,B产生故障,那么其丛节点就会变成主节点,从而导致客户端的写操作丢失。
这与大多数配置了每秒定时写数据到磁盘的数据库非常相似,由于过去的经验只是针对于与传统数据库而不涉及分布式数据库,这是我们能够推断的场景。
This is very similar to what happens with most databases that are configured to flush data to disk every second, so it is a scenario you are already able to reason about because of past experiences with traditional database systems not involving distributed systems.
为了提高一致性,通过强制将将数据写入磁盘后再返回客户端信息,这通常会带来低性能的问题,也会对集群造成相当多的备份。
这里需要对性能和一致性做权衡。
可以看看有关CAP原理介绍,An Illustrated Proof of the CAP Theorem。
Redis Cluster也支持同步的写操作,通过WAIT
命令来实现,这可以尽可能的减少写失败的情况,但是需要注意的是,即使使用了同步写,也不能保证强一致性。因为在复杂的情景下,总会发生一个从节点没有收到写操作,而其就变成了主节点。
假设有6个节点,分别为A、B、C、A1、B1、C1,三个主节点和三个从节点,还有一个客户端,称其为Z1。
当发生分区后,一个分区内有A、C、A1、B1、C1,另一个有Z1、B。
Z1仍然可以向B进行写操作,B也会接受写操作。如果在很短的时间内,分区消失,那么集群还是继续正常工作的。但是如果在一定时间内在具有大多数节点的分区中,B1变成了主节点,那么Z1发送给B的写操作就会丢失。
注意,Z1发送给B的写操作有一个最大的时间窗口:超过一定时间之后,有大多数节点的分区选举了新的主节点,那么每个在少数节点分区中的主节点都会停止接收写操作。
这个最大的时间窗口是Redis Cluster配置中一个重要的概念,叫做node timeout。
当过了这个节点超时时间,那么主节点就被认为失效,让将其从节点变为主节点。同样地,当节点超时时间过去之后,主节点并没有不能检测到其余主节点,那么该主节点就会进入报错状态并停止接收写操作。