切片集群
如果redis要存储的数据量特别大,那么进行RDB持久化时会使主线程的写请求因为要申请内存而变慢。因此只是提升单台机器的CPU、mem是不能解决问题的。要考虑切片集群(多个redis实例组成一个集群)
切片集群面临问题
- 数据切片后,在多个实例之间如何分布?
- 数据端怎么确定想要访问的数据在那个实例上?
一、数据切片和实例的对应分布关系
Redis 3.0 开始,官方提供了 Redis Cluster 的方案,用于实现切片集群。
Redis Cluster 方案采用哈希槽(Hash Slot),一个切片集群共有 16384 个哈希槽(数据分区),每个键值对都会根据它的key,被映射到一个哈希槽中。
- 根据键值对的key,按照CRC16算法计算一个16bit 的值
- 对这个16bit值对 16384 取模,得到 0 - 16383 范围内的模数,每个模数代表一个相应编号的哈希槽
场景:切片集群中 redis 实例的内存不一,希望分配的哈希槽数量不一。
1 | 手动分配哈希槽 |
注意:在手动分配哈希槽时,需要把 16384 个槽都分配完,否则,redis集群无法正常工作
二、客户端如何定位数据
客户端通过键值对的key 获得哈希槽,但如何通过哈希槽找到对应的实例呢?
Redis实例会把自己的哈希槽信息发给和它相连接的其他实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。客户端收到的哈希槽信息后,会把哈希槽信息缓存在本地。
问题:实例和哈希槽的对应关系会改变?比如:
- 在集群中,有新实例新增或者删除,Redis需要重新分配哈希槽
- 为了负载均衡,Redis需要把哈希槽在所有实例上重新分布一遍
重定向机制
Redis 实例之间可以通过互相传递消息,获取更新的哈希槽分配信息。
客户端访问时,如果访问的实例没有这个键值对映射的哈希槽,则这个实例会给客户端返回 MOVED 命令响应结果
1
2GET hello:key
(error) MOVED 13320 172.16.19.5:6379如果客户端访问某个哈希槽时,Redis 实例之间并没有迁移完这个哈希槽的数据,就会返回 ASK 错误,并附带新实例ip
1
2GET hello:key
(error) ASK 13320 172.16.19.5:6379ASK有两层意思:1. 表明哈希槽数据还在迁移中 2. ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时客户端需要给新实例发送 ASKING 命令,让这个实例允许客户端接下来发送的命令。然后再发送操作命令。
注意:ASK 和 MOVED 命令不同,ASK命令并不会更新客户端缓存的哈希槽分配信息。MOVED命令会改变客户端本地缓存。
三、问题
Redis Cluster 方案通过哈希槽的方式将键值对分配到不同的实例上,这个过程需要客户端做计算,这样做有什么好处?为什么不直接记录键值对的key与实例的对应关系?
- 整个集群存储的key数量非常大时,key-实例映射表特别大,无论存储在服务端或客户端都占有非常大的内存空间
- Redis Cluster 采用无中心化(无proxy,客户端和服务端直连),后端的 redis 实例之间交换“key-实例”信息的开销会非常大,每个节点需要额外存储其他节点的路由表,内存占用过大造成资源浪费
- 当集群在扩容、缩容、数据均衡时,节点之间会发生数据迁移,迁移时需要修改每个key的映射关系,维护成本高。使用哈希槽为基本单位,简化了难度,便于集群的维护和管理。
- 而在中间增加一层哈希槽,可以把数据和节点解耦,相当于消耗了很少的CPU资源,不但让数据分布更均匀,还可以让这个映射表变得很小,利用客户端和服务端保存,节点之间交换信息时也变得轻量。