cluster集群通信

添加链接描述
为什么使用hash一致性算法
hash 算法,以及弊端(如果有一个宕机了,大部分的数据都可能错位) 。不推荐使用 。
hash一致性算法 , 落到那个区间 , 就顺时针找最近的节点最为落点,如果这个落点挂了,不会影像到其他的落点 。会有一个热点问题,某一个落点上的数据过多,导致性能下降 。
使用虚拟节点,解决热落点的问题,使每个节点的数据都均匀 。
hash sloat算法 , 将下线的上的数据均匀分配到周围节点 。宕机,slave节点顶上来
详解
Redis则采用的是虚拟槽分区算法 。其中提到了槽(Slot)的概念 。这个槽是用来存放缓存信息的单位 , 在 Redis 中将存储空间分成了 16384 个槽,也就是说 Redis槽的范围是 0 -16383(2^4 * 2^10) 。
缓存信息通常是用 Key-Value 的方式来存放的,在存储信息的时候,集群会对 Key 进行 CRC16 校验并对 16384 取模(slot = CRC16(key)383) 。
得到的结果就是 Key-Value 所放入的槽,从而实现自动分割数据到不同的节点上 。然后再将这些槽分配到不同的缓存节点中保存 。
如图1 所示,假设有三个缓存节点分别是 1、2、3 。Redis将存放缓存数据的槽(Slot)分别放入这三个节点中:
缓存节点 1 存放的是(0-5000)Slot 的数据 。缓存节点 2 存放的是(5001-10000)Slot 的数据 。缓存节点 3 存放的是(10000-16383)Slot 的数据 。
此时 Redis需要根据一个 Key 获取对应的 Value 的数据,首先通过 CRC16(key)383 计算出 Slot 的值,假设计算的结果是 5002 。
将这个数据传送给 Redis ,集群接受到以后会到一个对照表中查找这个 Slot=5002 属于那个缓存节点 。
发现属于“缓存节点 2”,于是顺着红线的方向调用缓存节点 2 中存放的 Key-Value 的内容并且返回给 Redis。
分布式缓存节点之间的通讯
如果说 Redis的虚拟槽算法解决的是数据拆分和存放的问题,那么存放缓存数据的节点之间是如何通讯的 , 就是接下来我们要讨论的 。
缓存节点中存放着缓存的数据,在 Redis的分布式部署下,缓存节点会被分配到一台或者多台服务器上 。
新上线的缓存节点 2 和缓存节点 1 进行通讯

cluster集群通信

文章插图
缓存节点的数目也有可能根据缓存数据量和支持的并发进行扩展 。如图 2 所示,假设 Redis中存在“缓存节点 1”,此时由于业务扩展新增了“缓存节点 2” 。
新加入的节点会通过协议向老节点 , 发出一个“Meet 消息” 。收到消息以后“缓存节点 1”,会礼貌地回复一个“Pong 消息” 。
此后“缓存节点 2”会定期发送给“缓存节点 1” 一个“Ping 消息”,同样的“缓存节点 1”每次都会回复“Pong 消息” 。
已上线的节点之间的通信
节点之间通过协议不断相互交互这些信息,就好像一群人在一起八卦一样 , 没有多久每个节点就知道其他所有节点的情况了,这个情况就是节点的元数据 。
整个传输过程大致分为以下几点:
Redis的每个缓存节点都会开通一个独立的 TCP 通道,用于和其他节点通讯 。有一个节点定时任务 , 每隔一段时间会从系统中选出“发送节点” 。这个“发送节点”按照一定频率,例如:每秒 5 次,随机向最久没有通讯的节点发起 Ping 消息 。接受到 Ping 消息的节点会使用 Pong 消息向“发送节点”进行回复 。不断重复上面行为,让所有节点保持通讯 。
在 Redis中缓存节点之间是通过协议进行通讯的 。
从类型上来说其分为了四种消息 , 分别是:
Meet 消息,用于通知新节点加入 。就好像上面例子中提到的新节点上线会给老节点发送 Meet 消息,表示有“新成员”加入 。Ping 消息,这个消息使用得最为频繁,该消息中封装了自身节点和其他节点的状态数据 , 有规律地发给其他节点 。Pong 消息,在接受到 Meet 和 Ping 消息以后,也将自己的数据状态发给对方 。同时也可以对集群中所有的节点发起广播,告知大家的自身状态 。Fail 消息 , 如果一个节点下线或者挂掉了,会向集群中广播这个消息 。
图 3: 协议结构
协议的结构如图 3 所示,有其中 type 定义了消息的类型,例如:Meet、Ping、Pong、Fail 等消息 。
另外有一个的数组定义了节点负责的槽信息 。每个节点发送协议给其他节点最重要的就是将该信息告诉其他节点 。另外,消息体通过对象传递消息征文 。
其实节点之间通讯的目的是为了维护节点之间的元数据信息 。这个元数据就是每个节点包含哪些数据,是否出现故障 。
请求分布式缓存的路由
对内,分布式缓存的节点通过协议互相发送消息,为了保证节点之间了解对方的情况 。
那么对外来说,一个 Redis 客户端如何通过分布式节点获取缓存数据,就是分布式缓存路由要解决的问题了 。
上文提到了协议会将每个节点管理的槽信息发送给其他节点,其中用到了char [/8] 这样一个数组存放每个节点的槽信息 。
属性是一个二进制位数组(bit array),其中为 16384 。
这个数组的长度为 16384/8=2048 个字节,由于每个字节包含 8 个 bit 位(二进制位),所以共包含 16384 个 bit,也就是 16384 个二进制位 。
每个节点用 bit 来标识自己是否拥有某个槽的数据 。如图 4 所示 , 假设这个图表示节点 A 所管理槽的情况 。
图 4:通过二进制数组存放槽信息
cluster集群通信

文章插图
0、1、2 三个数组下标就表示 0、1、2 三个槽,如果对应的二进制值是 1 , 表示该节点负责存放 0、1、2 三个槽的数据 。同理,后面的数组下标位 0 就表示该节点不负责存放对应槽的数据 。
用二进制存放的优点是 , 判断的效率高,例如对于编号为 1 的槽,节点只要判断序列的第二位,时间复杂度为 O(1)
图 5:接受节点把节点槽的对应信息保存在本地
如图 5 所示,当收到发送节点的节点槽信息以后,接受节点会将这些信息保存到本地的的结构中,其中 Slots 的数组就是存放每个槽对应哪些节点信息 。
图 6: 结构以及槽与节点的对应
如图 6 所示 ,  中保存的 Slots 数组中每个下标对应一个槽,每个槽信息中对应一个也就是缓存的节点 。
这些节点会对应一个实际存在的 Redis 缓存服务,包括 IP 和 Port 的信息 。
Redis的通讯机制实际上保证了每个节点都有其他节点和槽数据的对应关系 。
Redis 的客户端无论访问集群中的哪个节点都可以路由到对应的节点上,因为每个节点都有一份 ,它记录了所有槽和节点的对应关系 。
Redis 客户端是如何通过路由来调用缓存节点的
图 7:MOVED 重定向请求
如图 7 所示,Redis 客户端通过 CRC16(key)383 计算出 Slot 的值,发现需要找“缓存节点 1”读/写数据,但是由于缓存数据迁移或者其他原因导致这个对应的 Slot 的数据被迁移到了“缓存节点 2”上面 。
那么这个时候 Redis 客户端就无法从“缓存节点 1”中获取数据了 。
但是由于“缓存节点 1”中保存了所有集群中缓存节点的信息,因此它知道这个 Slot 的数据在“缓存节点 2”中保存,因此向 Redis 客户端发送了一个 MOVED 的重定向请求 。
这个请求告诉其应该访问的“缓存节点 2”的地址 。Redis 客户端拿到这个地址,继续访问“缓存节点 2”并且拿到数据 。
上面的例子说明了,数据 Slot 从“缓存节点 1”已经迁移到“缓存节点 2”了,那么客户端可以直接找“缓存节点 2”要数据 。
那么如果两个缓存节点正在做节点的数据迁移,此时客户端请求会如何处理呢?
图 8:ASK 重定向请求
如图 8 所示,Redis 客户端向“缓存节点 1”发出请求,此时“缓存节点 1”正向“缓存节点 2”迁移数据 , 如果没有命中对应的 Slot,它会返回客户端一个 ASK 重定向请求并且告诉“缓存节点 2”的地址 。
【cluster集群通信】客户端向“缓存节点 2”发送命令 , 询问需要的数据是否在“缓存节点 2”上,“缓存节点 2”接到消息以后返回数据是否存在的结果 。