Redis - Cluster - 分布式存储
Redis Cluster 是 Redis 的分布式解决方案,解决了存储空间、性能扩展,也用优雅的解决了负载均衡和高可用的问题,完全可以取代哨兵机制。
先说说 cluster 的分布式存储。分布式存储的两大问题:数据分区以及数据路由。看看 redis 是怎么选择的。
分区策略
分区策略即将数据集按照一定的分区规则映射到多个节点上,常见的分区规则有顺序分区和哈希分区。
总的来说,是要离散还是要顺序,这是个需求问题。RedisCluster 选择的是哈希分区,因为其侧重点还是要数据均匀分布,顺序访问的需求并不强烈。
常见的哈希分区有几种:
1、节点取余分区
根据特定的数据,比如 Key 或者用户 ID,计算出其哈希值,再对节点数量 N 求余,即可决定数据映射到哪个节点。
该种策略简单,但是节点扩容或者收缩时,数据和节点的映射关系需要重新计算,数据需要在节点间迁移。使用该策略时,一般采用预分区的方式,提前根据数据量规划分区树,满足未来一段时间的需要,需要扩容时,再翻倍扩容(只需要大概 50%需要迁移),避免全体数据迁移。
2、一致性哈希
将 0~2^32 的哈希地址空间(哈希值一般是一个 32 位的无符号整形)形成一个环形,称为哈希环,将节点(IP 或者其他 ID)哈希后对 2^32 取余落在环上,多个节点将环分为若干部分。对数据 Key 哈希后,也对 2^32 取余落在环上,顺时针找到的第一个节点即为映射节点。
一致性哈希在节点伸缩时可以保证只有紧邻的一个节点数据会受到影响,但在这么大的哈希地址空间内,容易导致节点数据不均衡。
可以使用虚拟节点的方式解决数据不均衡的问题,比如可以设置 10000 个虚拟节点,每 200 个虚拟节点属于 1 个实际节点,即 1 个实际节点负责 200 个虚拟节点的数据,让数据变得更均衡。
3、虚拟槽分区
也使用哈希空间,再细分为槽,每个槽负责映射一部分数据,每个节点再负责一定数量的槽。实际上就是用虚拟节点解决数据不均衡的一致性哈希,这里的槽就相当于虚拟节点。
节点映射槽、槽映射数据,将数据和节点的映射关系解耦,能够更为高效的对数据进行管理。
RedisCluster 即采用该种分区策略,槽的范围固定为 0~16383。
具体过程:事先分配每个节点负责哪些槽,在对数据 Key 哈希对 16384 取余映射到槽,再映射到节点。
怎么确定的是 16384 个槽呢?
RedisCluster 中的节点都会记录并交换槽-节点映射信息,记录在位变量 myslots[CLUSTER_SLOTS/8]中,16384/8 = 2048 / 1024 = 2KB。16384 个槽即会占用 2KB 的空间,再大则会占用更大的地址空间,对网络带宽会造成较大负担。
另外一方面,RedisCluster 建议节点不超过 1000 个,那么当最大节点数为 1000 时,每个节点大约负责 16 个槽,如果槽总量变少,每个节点负责的槽变少,则可能导致数据倾斜。
综上,选定了 16384 个槽。
路由规则
说说相关的负载均衡策略以及路由规则。
负载均衡
Redis 集群由多个节点组成,数据均衡的分布在不同的节点上,那怎么实现负载均衡呢?
负载均衡大致上有两条路:
1、客户端负载均衡:由客户端与各节点直接建立连接,根据一定的策略,将某请求发送到某节点
2、服务端负载均衡:服务端存在一个代理,客户端只跟代理交互,由代理负载负载均衡。
Redis 集群采用了客户端负载均衡方式。
路由规则
从服务端节点的行为来说,当一个节点接收到请求,首先计算 key 对应的槽,再找出对应节点,如果节点是自身,则处理命令,否则回复 MOVED 重定向错误,通知客户端请求正确的节点。
重定向信息包含了 key 对应的槽以及负责该槽的节点地址(IP 和端口),客户端收到该信息即可重新向正确节点发起请求。
还有另外一种特殊的重定向的场景:ASK 重定向。这种重定向是专门用于槽迁移时的特殊处理。
槽迁移时,槽中的数据可能一半还未开始迁移,仍在源节点,一半已经迁移到了目的节点。比如 k1 在源节点,k2 在目的节点。此时该槽对应的节点信息在不同的节点或者客户端看来是不一样的:
1、源节点
槽在源节点,但该槽正在迁移至目的节点(称为正在迁出槽,在节点内部通过变量记录,下同)
2、目的节点
槽在源节点,但该槽正在迁入目的节点(称为正在迁入槽)
3、其他节点
槽在源节点
4、客户端
槽在源节点
此时,执行命令的不同情况如下:
1、向源节点发送请求
执行 k1 命令,则源节点处理
执行 k2 命令,发现已经没有该数据了,已经迁移至目的节点,则返回 ASK 重定向,告诉客户端,该槽正在迁移且该 key 已经迁移走了。
2、向目的节点发送请求
执行 k1 命令,则返回 MOVED 重定向
执行 k2 命令,此时有两种情况:
1)第 1 步中返回 ASK 重定向后,客户端知道正在迁移,则会先发送 asking 命令告知目的节点,我想查正在迁入槽里面是否包含该槽,知会目的节点后,下一条执行命令 k2 发送给目的节点,目的节点会计算 k2 对应的槽,在正在迁入槽里面查询,如果包含则处理,否则返回 MOVED 重定向。
asking 知会,只能起效一次,只会对下一次命令有效。
2)不发送 asking 知会,直接发送命令给目的节点,则不会查询正在迁入槽,正常槽里面没有就直接返回 MOVED 重定向。
智能客户端
作为生产使用的客户端,不能傻傻的每次随便找个节点发送请求试一下,再找到正确节点发送请求,指不定得试多少次,所以是不可行的。
一般来说客户端的做法如下:
1、设置几个种子节点
2、客户端初始化的时候选择一个节点,发送 cluster slots 命令查询到所有槽和节点的对应关系并缓存起来
3、执行命令时,先计算槽,在从缓存中找到对应的节点,发送请求
4、如果能执行就执行,如果返回重定向响应,那就刷新缓存并在此发送请求
评论