Redis集群中,不同节点存储了不同的key,一个key映射到集群节点的过程分两步:
计算key归属的哈希槽;
根据哈希槽与集群节点的映射关系找到对应节点
所以哈希槽就是一个数组空间,所有的key都可以根据固定规则映射到哈希槽中。
key映射到哈希槽的规则为:
HASH_SLOT = CRC16(key) mod 16384
来源:Redis官网
CRC(16) % ${集群节点数量}
计算key与集群节点的映射关系?Java的HashMap扩容缩容场景相对简单,每次扩容只需要重新计算一半key的位置,且HashMap的数据量并不高,所以直接用key的hash值对哈希桶长度求余获取key在哈希桶的位置的方式是可取的。
但是Redis集群中,一是数据量远超HashMap,二是key与节点的映射关系发生变化的触发点一般是某些节点的意外下线,或节点重新接入集群,导致key的搬迁场景远比HashMap复杂。如果采用CRC16(key) % ${集群节点数量}
计算key与集群节点的映射关系,导致的情况就是:一旦集群节点数量发生变化,不但要搬迁发生变化的节点的数据,而且要搬迁数量巨大的其它正常节点的数据。
但是加上哈希槽作为中间媒介以后,集群节点发生变化后,只需修改哈希槽与集群节点的映射关系即可,且只需要搬迁发生变化的节点的数据。
redis节点需要把所有的哈希槽放到心跳包中,以便让节点知道当前集群信息,虽然CRC16最多可以分配65536(2^16
)个槽位,但是65536个哈希槽的Redis集群,其的心跳包压缩后也有8k大小,开销太大了,且实际使用中,集群节点数量一般不超过1000个,所以权衡之后选择了16384(2^14)个哈希槽。
作者对于16384个哈希槽的解释:why redis-cluster use 16384 slots
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官方推荐是不超过1000个节点。但是既然哈希槽数量为16384个,所以Redis集群的master节点的理论上限是16384个。不过集群节点超过一定数量后(如1000),集群的吞吐量反而会下降,所以16384也只是理论的上限。
Redis Client与Redis集群建立连接后,Redis集群会将哈希槽信息发送给Redis Client
Redis Client请求key时,现在本地计算key对应的哈希槽,然后根据从集群获取的哈希槽信息去请求对应的集群节点
Redis集群提供重定向功能,若Redis Client请求Redis集群时,key的哈希槽不在当前请求的集群节点上,那么当前集群节点便会返回MOVED命令,告诉Redis Client当前key的哈希槽对应的集群节点,Redis Client即可更新哈希槽信息