我在《那些年用过的 Redis 集群架构(含面试解析)》一文里提到过, 现在 Redis 集群架构, Redis cluster 用的会比较多.
如下图所示
对于客户端请求的 key, 根据公式
HASH_SLOT=CRC16(key) mod 16384
, 计算出映射到哪个分片上, 然后 Redis 会去相应的节点进行操作!
那大家思考过, 为什么有 16384 个槽么?
ps:CRC16 算法产生的 hash 值有 16bit, 该算法可以产生 2^16-=65536 个值. 换句话说, 值是分布在 0~65535 之间. 那作者在做 mod 运算的时候, 为什么不 mod65536, 而选择 mod16384?
其实我当初第一次思考这个问题的时候, 我心里是这么想的, 作者应该是觉得 16384 就够了, 然后我就开始查这方面资料.
很幸运的是, 这个问题, 作者是给出了回答的!
地址如下:
https://github.com/antirez/redis/issues/2576
作者原版回答如下:
- The reason is:
- Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with16k slots, but would use a prohibitive 8k of space using 65k slots.
At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.
So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.
因此, 能看懂上面那段话的读者. 这篇文章不用看了, 因为作者讲的很清楚了. 本文只是对上面那段话做一些解释而已.
正文
基础
我们回忆一下 Redis Cluster 的工作原理!
这里要先将节点握手讲清楚. 我们让两个 Redis 节点之间进行通信的时候, 需要在客户端执行下面一个命令
127.0.0.1:7000>cluster meet 127.0.0.1:7001
如下图所示
意思很简单, 让 7000 节点和 7001 节点知道彼此存在!
在握手成功后, 连个节点之间会定期发送 ping/pong 消息, 交换数据信息, 如下图所示.
在这里, 我们需要关注三个重点.
(1)交换什么数据信息
(2)数据信息究竟多大
(3)定期的频率什么样
到底在交换什么数据信息?
交换的数据信息, 由消息体和消息头组成.
消息体无外乎是一些节点标识啊, IP 啊, 端口号啊, 发送时间啊. 这与本文关系不是太大, 我不细说.
我们来看消息头, 结构如下
注意看红框的内容, type 表示消息类型.
另外, 消息头里面有个 myslots 的 char 数组, 长度为 16383/8, 这其实是一个 bitmap, 每一个位代表一个槽, 如果该位为 1, 表示这个槽是属于这个节点的.
到底数据信息究竟多大?
在消息头中, 最占空间的是 myslots[CLUSTER_SLOTS/8]. 这块的大小是:
16384÷8÷1024=2kb
那在消息体中, 会携带一定数量的其他节点信息用于交换.
那这个其他节点的信息, 到底是几个节点的信息呢?
约为集群总节点数量的 1/10, 至少携带 3 个节点的信息.
这里的重点是: 节点数量越多, 消息体内容越大.
消息体大小是 10 个节点的状态信息约 1kb.
那定期的频率是什么样的?
Redis 集群内节点, 每秒都在发 ping 消息. 规律如下
(1)每秒会随机选取 5 个节点, 找出最久没有通信的节点发送 ping 消息
(2)每 100 毫秒 (1 秒 10 次) 都会扫描本地节点列表, 如果发现节点最近一次接受 pong 消息的时间大于 cluster-node-timeout/2 则立刻发送 ping 消息
因此, 每秒单节点发出 ping 消息数量为
数量 = 1+10*num(node.pong_received>cluster_node_timeout/2)
那大致带宽损耗如下所示, 图片来自《Redis 运维与实现》
讲完基础知识以后, 我们可以来看作者的回答了.
回答
(1)如果槽位为 65536, 发送心跳信息的消息头达 8k, 发送的心跳包过于庞大.
如上所述, 在消息头中, 最占空间的是 myslots[CLUSTER_SLOTS/8].
当槽位为 65536 时, 这块的大小是:
65536÷8÷1024=8kb
因为每秒钟, Redis 节点需要发送一定数量的 ping 消息作为心跳包, 如果槽位为 65536, 这个 ping 消息的消息头太大了, 浪费带宽.
(2)Redis 的集群主节点数量基本不可能超过 1000 个.
如上所述, 集群节点越多, 心跳包的消息体内携带的数据越多. 如果节点过 1000 个, 也会导致网络拥堵. 因此 Redis 作者, 不建议 Redis cluster 节点数量超过 1000 个.
那么, 对于节点数在 1000 以内的 Redis cluster 集群, 16384 个槽位够用了. 没有必要拓展到 65536 个.
(3)槽位越小, 节点少的情况下, 压缩比高
Redis 主节点的配置信息中, 它所负责的哈希槽是通过一张 bitmap 的形式来保存的, 在传输过程中, 会对 bitmap 进行压缩, 但是如果 bitmap 的填充率 slots / N 很高的话(N 表示节点数),bitmap 的压缩率就很低.
如果节点数很少, 而哈希槽数量很多的话, bitmap 的压缩率就很低.
ps: 文件压缩率指的是, 文件压缩前后的大小比.
综上所述, 作者决定取 16384 个槽, 不多不少, 刚刚好!
总结
希望大家有所收获!
来源: https://www.cnblogs.com/rjzheng/p/11430592.html