Redis之Cluster

Cluster

Redis Cluster 是官方提供的集群方案,集群由多个Redis节点组成,每个节点负责整个集群中的一部分数据,每个节点负责的数据多少可能会不一样,

这些节点相互连接组成一个对等的集群,它们之间通过一种特殊的二进制协议交互集群信息。

Redis之Cluster

Redis Cluster 共有 16384 个槽位,Codis 默认只有 1024(可调整) 个,每个节点负责一部分的槽位,

与 Codis 需要第三方分布式存储(ZK、Etcd)相比,Cluster 则是将槽位信息保存在每个节点内,不再需要其他的中间件。

当客户端与集群建立连接时,也会得到一份槽位信息,并缓存在客户端本地,这样客户端在本地就可以定位到 key 所在的节点,这点与Codis不同,客户端需要将 key 给到Codis,由Codis转发给真正的节点,

同时可能会存在客户端与服务端槽位信息不一致的情况,所以还需要一个纠正机制保证双方槽位信息的一致。

注意:Redis Cluster中的每个节点会将集群的配置信息持久化到配置文件中,因此要保证配置文件是可写的,并且尽量不要人为的修改配置文件。

槽位定位算法

RC 通过 CRC16 算法对 key 进行 hash,得到一个整数值,随后用该值与 16384 进行取模得到具体的槽位,这点与 Codis 类似,

如果想将特定的 key 挂到特定的槽位上,可以在key字符串嵌入 hash tag 标记,那么在进行计算时,将对 hash tag 中的数据进行计算,而不是整个key,hash tag使用如下,

如果想让同一个用户的信息和订单在一个redis实例中,那么就可用用 {} 大括号,包含用户的id,那么计算时,就会使用用户id去计算,最终得出的槽位信息是一样的,就挂到了同一个实例上。

set user:info{1}
set user:order{1}

错误跳转 MOVED

客户端想服务端发送一个指令时,如果服务端发现该指令的 key 所在槽位并不归自己管理,这时会向客户端返回一个 MOVED 指令,该指令同时携带key所在的槽位,以及正确的节点地址,让客户端去正确的节点上获取。

-MOVED 3967 192.168.0.200:6379

MOVED指令第一个参数是key对应的槽位号,后面是目标节点地址,MOVED前面的减号代表其是一个错误消息,

同时客户端在收到一个MOVED指令后,还需要纠正本地的槽位映射表,后续所有的key将使用最新的槽位映射表。

迁移

RC提供了一个叫 redis-trib 的工具可以让运维人员手动调整槽位的分配情况,可以将一个槽位中的数据由老节点迁移到新节点中,

RC以槽作为单位进行迁移,迁移一个槽前,该槽在源节点的状态将变为 migrating , 目标节点上的槽状态为 importing,表示该槽位的数据正在由源节点迁移到目标节点,

将槽的过渡状态设置好后,就开始对槽中的key开始迁移,通过 keysinslot 指令获取到槽位中的所有key列表,再挨个对每个key进行迁移(migrate指令: http://doc.redisfans.com/key/migrate.html)

migrate 迁移过程如下:

  1. 源节点对key执行dump指令得到序列化内容,然后向目标节点发送 restore 指令并携带序列化内容
  2. 目标节点收到该指令后,再反序列化到自己的内存中,目标节点回复源节点OK
  3. 源节点收到回复OK后,将自己内存中的key删除,此时一个key迁移完毕,接着对下一个key进行迁移

每个key在迁移过程是同步的,源节点会阻塞到key被成功删除位置,因此如果出现比较大的key,那么迁移过程中,会造成redis的卡顿,所以业务上尽量避免较大的key。

如果迁移过程中发生故障,一个槽只迁移了部分,那么下次迁移工具连上时,会提示用户继续迁移。

迁移时的访问

与Codis类似,在迁移过程中,新旧两个节点的槽位都存在部分key数据,客户端继续访问旧节点,如果恰巧数据还没有被迁移到新节点中,那么旧节点正常处理(这里Codis是强制迁移,然后访问新节点的数据),

如果旧节点中没有这个数据,那么此时可能有两种情况,一种是这个key压根不存在,另一种是已经迁移到新节点中了,此时旧节点不知道处于哪种情况,因此会返回给客户端一个 -ASK targetNodeAddr 的重定向指令,这个 targetNodeAddr 就是该key迁移后的新节点地址,

客户端收到这个指令后,向 targetNodeAddr 节点发送一个 ASKING 指令,然后向目标节点发送一个和刚刚在旧节点一样的指令,此时如果目标节点做出相应的处理,流程完毕。

为什么要先执行 ASKING

因为迁移过程中,该槽位虽然即将被新节点接管,但是此时还没有正式接收完数据,因此实际还是处于旧节点的管理状态下,如果这个时候发送了一个不属于新节点管理的key,那么他并不认,

继而返回MOVED指令给客户端,那客户端继续去旧节点访问,旧节点继续返回其 ASK,这样就形成了重定向循环,因此 ASKING 是告诉目标节点,下一条指令必须接收处理。

由此得出,迁移会影响服务效率,一条指令操作的key如果处于迁移中,平时一个ttl就可以处理结束,此时需要3个ttl才可以处理结束。

Redis之Cluster

容错

RC可以为每个主节点设置多个从节点,主节点故障时,将选择其中一个从节点作为主节点,如果没有可用的从节点,发生故障时,集群处于不可用状态,

Redis提供了 cluster-require-full-coverage 参数允许集群中的部分节点发生故障时,其他节点还可以继续提供访问,此时只有故障的节点存储的数据不可用。

网络抖动

在网络出现波动的情况下,集群中的节点将会断开连接,随后又恢复连接,如果每波动一次,就发生一次主从故障切换,就会因为假故障导致集群频繁切换,影响效率。

RC提供了 cluster-node-timeout(毫秒为单位) 参数,当某个节点断连时间持续这个timeout时间时,就认为该节点发生故障,需要主从切换。

发生主从切换时,可能一些从节点与主节点也发生过较长的断连,这种节点可能有数据还和主节点保持一直的情况,

RC还提供了 cluster-slave-validity-factor 参数,这个参数大于0的时候,主从切换时,

如果一个从节点与主节点发生的断连时间超过 cluster-slave-validity-factor x cluster-node-timeout 的时间,例如 10 x 5 = 50 毫秒,

那么这个从节点是没资格升级为主节点的,如果没有一个从节点满足这个条件,那么此时集群将发生故障,等待主节点恢复正常时,才能恢复正常工作。

可能下线与确定下线

RC是去中心化的,所以当A节点认为B节点失联,并不代表其他节点也认为B节点失联,这里采用了大多数机制,

RC采用Gossip协议向其他节点发送B节点失联(PFail 即 Possibly Fail)的消息,当大多数节点(PFail Count)都认为B节点失联后,即可标记B节点状态为下线状态(Fail),

然后强迫少部分节点接受B节点下线的事实,并立即对B节点进行主从切换。

槽位迁移感知

如果Cluster中的槽位信息发生了变化,那么客户端中缓存的槽位信息也需要相应改变,这种改变是被动的。

MOVED

当客户端向服务端发送一条指令,收到了一个MOVED错误时,此时客户端就知道槽位发生了变化,则即刻刷新本地的槽位信息,然后向目标节点重新发送该指令。

ASKING

ASKING是在迁移过程中,旧节点发现这里没有想要的数据临时告诉客户端的,客户端收到该响应后,临时去新的节点上执行该指令,但该指令并不会让客户端刷新本地的槽位信息,后续的指令依然按照之前的槽位来。

重试两次

如果客户端向A节点发送一条指令,A节点发现这个指令不在自己这里,返回一个MOVED B节点 给客户端,客户端去 B 节点请求,

此时B节点发生了迁移操作,返回给客户端 ASKING C节点,那么客户端又要向 C 节点发送指令,此时就出现了客户端重试两次的情况。

重试多次

如果客户端运气不好,凑巧赶上集群对槽位在不停的切换迁移,导致每次本地的槽位信息和服务端最新的不一致,那么每次重试都会收到MOVED。

一般客户端中都会设置一个最大重试次数,如果不停的重试发送指令达到最大重试次数,将直接抛出异常。

集群变更感知

如果Redis节点发生变更,例如主从切换或者运维移除节点,那么客户端也需要知道并及时刷新本地的节点关系表。

  1. 客户端在发送指令时,如果目标节点挂掉,客户端会抛出一个ConnectionError连接异常,然后再随机选另一个节点重试,此时重试的节点会返回MOVED指令,客户端将刷新本地槽位信息。
  2. 如果运维修改了集群信息,将主节点切换到其他节点,并将旧的主节点移除集群,这时候客户端向其发送指令将收到 ClusterDown 的错误,告知其所在集群不可用,客户端此时收到这个错误后会关闭所有连接,清空槽位映射关系,抛出异常,等待下一条指令,然后尝试重新初始化节点信息。
上一篇:Redis之Stream


下一篇:Redis之布隆过滤器