概要
这篇文章主要介绍redis cluster可用性和一致性相关的特性,值得一提的是,redis cluster需要高于等于redis3.0版本。
Redis Cluster 101
Redis Cluster提供了一种数据自动分片到不同Redis实例中的解决方案,Redis Cluster在一定程度上提高了Redis系统的可用性,即当一些节点挂掉之后,其他节点还可以继续提供服务。而当大部分的master节点都挂掉的时候,Redis Cluster会停止服务。
因此Redis Cluster带来了:
- 自动将数据分散到不同的节点。
- 允许当一些节点挂掉之后剩下的节点仍然可以工作,或者是禁止这些节点继续工作。
Redis Cluster TCP ports
每一个Redis Cluster节点需要开两个TCP端口,一个是用来处理客户端的请求,例如6379端口,另一个端口由第一个端口加上10000之后得到即16379,第二个端口用来作为集群总线,也就是集群之间节点相互进行二进制通信用的。集群总线的用处如下:
- 错误检测
- 配置更新
- failover
- ……
客户端的连接端口和集群总线的连接端口之间的距离10000是固定的。为了让Redis Cluster正常的工作,对于每一个节点:
- 客户端连接端口需要能够和Cluster进行通信,用于keys迁移。
- 所有节点的Cluster端口能够相互通信。
如果这两个端口没有打开,则Redis Cluster不能够正常工作。
Cluster端口之间使用特殊的二进制协议进行通信,这种二进制协议更适合节点之间通信,可以占用更小的带宽和更少的传输时间。
Redis Cluster Docker
当前的Redis Cluster不支持NATted环境,Docker使用端口映射技术,也即运行在Docker容器内部的应用暴露在容器外面的端口和应用暴露的端口不一样,这对于多个应用在Docker中使用同一个端口是非常重要的。为了让Redis Cluster能够在Docker中好好工作,我们需要检查--net=host选项,更多的信息见Docket文档。
Redis Cluster 数据 sharding
Redis Cluster不使用一致性哈希,但是不同形式的sharding,每一个key理论上是hash slot的一部分。在Redis Cluster中有16384个hash slot,为了计算指定key的hash slot,我们简单的使用CRC16算法并对结果进行16384取模。在Redis Cluster中的每一个节点代表着所有hash slot的一个子集,例如我们有三个节点:
- Node A 包括0到5500之间的hash slots
- Node B 包括5501到11000之间的hash slots
- Node C 包括11001到16383之间的hash slots
这使得可以很容易的向Redis Cluster中添加或删除节点。例如,我们想要添加一个节点D,我们需要将一些hash slots从A,B,C节点移给D。假如我们想要移除A节点,我们可以把A节点上原有的hash slots移动到B和C节点。当A节点的hash slots移除完毕之后,就可以把A节点下线了。
将hash slots从一个节点移动到其他节点不需要任何的停机操作。
Redis Cluster支持多key操作,多个key是通过一个命令、或者一个事务、或者一个lua脚本执行的,属于同一个hash slot。用户可以强制多个key属于同一个hash slot,这个概念叫hash tags。
Hash tags有关的介绍在Redis Cluster规范里面有介绍,主旨是当key中包含{}的时候,只有{}括号内的字符串才被用来hash。例如,一个key是this{key},另一个key是that{key},他们会被映射到同一个hash slot,而且可以被用于一批key一个命令的操作。
Redis Cluster master-slave模式
为了让当某一个master挂掉之后,这个master的hash slot仍然可以使用,Redis Cluster使用master-slave模式,因此对于每一批hash slot来说都有N个备份,其中N-1个是slave。在我们的例子中,节点A、B、C,如果B挂了,则整个Redis Cluster就不能用了,因为没有实例类服务5501-11000这部分hash slots。但是如果我们为每个master节点各增加一个slave节点,比如A1,B1,C1节点,如果B挂了,整个集群还是可以正常工作的。
Node B1是B的复制,如果B挂了,Cluster将会选择B1作为新的master,然后继续工作。值得注意的是,如果B和B1同时挂了,那就没辙了。
Redis Cluster一致性保证
Redis Cluster并不保证强一致性,也就是在一定条件下,Redis Cluster可能丢失被确认写入的数据,第一个有可能Redis Cluster丢失数据的原因是因为复制是异步进行的,意味着下面情景有可能发生:
- 客户端将数据写入B
- master B回复客户端写入成功
- master将写命令传播给B1,B2和B3
我们可以看到,在B回复客户端之前不会等到B1,B2,B3的应答。因此如果客户端向B写了数据,B确认了,但是在数据复制到B1,B2,B3之前挂了,那数据对于B1,B2,B3来说就丢失了。和传统的数据库类似,可以强制数据库将日志写入磁盘再告知客户端写成功,但是这么做带来的性能损耗是非常大的,这对于Redis Cluster来说就是同步复制。
所以这就是一致性和可用性之间的权衡,如果确实需要,Redis Cluster是支持同步写的,由WAIT命令实现,这可以保证尽量少的丢失数据,尽管Redis Cluster提供了同步复制的支持,但是Redis Cluster并没有实现强一致性,因此在一些复杂的应用场景中,还是有可能丢失数据的。
另外一个数据会丢失的场景也值得一提,即当出现网络分区的时候,也就是某一个或多个master和其他的master失去了联系,举个例子,我们有六个Redis实例,三个master和三个slave,还有一个client我们把它叫做Z1。当出现网络分区的时候,比如A,C,A1,B1,C1,处于一个分区,另一个分区有B和Z1。
Z1还可以向B写数据,B也接受了Z1的写请求,如果分区只持续一小段时间,Cluster仍然可以继续工作。然而,如果时间足够大,Cluster选举了B1作为master,那么之前Z1写入的数据就丢失了。
值得一提的是在Z1还可以向B写数据的时候,有一个最大的窗口时间,如果过了这个时间,Cluster选取了B1作为master,B就不能够接受写请求了。这段时间是非常重要的,这个时间被叫做node timeout。当node timeout之后,master被认为出问题了,并且可以被其slave代替,然后master不能够处理写请求。
Redis Cluster配置参数
cluster-enabled<yes/no>:如果是yes,则当前redis启动为Redis Cluster的一个实例,否则当前Redis以单机模式启动。
cluster-config-file<filename>:值得注意的是这个配置文件是用户不可编辑的,是由Redis 集群节点自动创建的,每次节点状态有变化的时候都会持久化这个配置文件,也是为了Redis集群节点重启的时候重新读取。配置文件的内容包括集群中的其他节点、他们的状态、持久变量等等。每当接受到消息的时候,文件会被刷新并且写入磁盘。
cluster-node-timeout<milliseconds>:Redis集群节点的超时时间,如果在这个时间内Redis集群节点不可达,则集群将会执行failover策略,将该master的slave节点选为master。这个参数控制Redis集群重要的,值得提的是,如果指定时间当前节点联系不到大多数的master,则会拒绝服务。
cluster-slave-validity-factor<factor>:如果将该项设置为0,不管slave节点和master节点间失联多久都会一直尝试failover(设为正数,失联大于一定时间(factor*节点TimeOut),不再进行FailOver)。比如,如果节点的timeout设置为5秒,该项设置为10,如果master跟slave之间失联超过50秒,slave不会去failover它的master(意思是不会去把master设置为挂起状态,并取代它)。注意:任意非0数值都有可能导致当master挂掉又没有slave去failover它,这样redis集群不可用。在这种情况下只有原来那个master重新回到集群中才能让集群恢复工作。
cluster-migration-barrier<count>:一个master可以拥有的最小slave数量。该项的作用是,当一个master没有任何slave的时候,某些有富余slave的master节点,可以自动的分一个slave给它。
cluster-require-full-coverage<yes/no>:如果该项设置为yes(默认就是yes) 当一定比例的键空间没有被覆盖到(就是某一部分的哈希槽没了,有可能是暂时挂了)集群就停止处理任何查询操作。如果该项设置为no,那么就算请求中只有一部分的键可以被查到,一样可以查询(但是有可能会查不全)