undefined

切片集群

如果redis要存储的数据量特别大,那么进行RDB持久化时会使主线程的写请求因为要申请内存而变慢。因此只是提升单台机器的CPU、mem是不能解决问题的。要考虑切片集群(多个redis实例组成一个集群)

切片集群面临问题

  1. 数据切片后,在多个实例之间如何分布?
  2. 数据端怎么确定想要访问的数据在那个实例上?

一、数据切片和实例的对应分布关系

Redis 3.0 开始,官方提供了 Redis Cluster 的方案,用于实现切片集群。

Redis Cluster 方案采用哈希槽(Hash Slot),一个切片集群共有 16384 个哈希槽(数据分区),每个键值对都会根据它的key,被映射到一个哈希槽中。

  1. 根据键值对的key,按照CRC16算法计算一个16bit 的值
  2. 对这个16bit值对 16384 取模,得到 0 - 16383 范围内的模数,每个模数代表一个相应编号的哈希槽

场景:切片集群中 redis 实例的内存不一,希望分配的哈希槽数量不一。

1
2
3
4
# 手动分配哈希槽
redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4

注意:在手动分配哈希槽时,需要把 16384 个槽都分配完,否则,redis集群无法正常工作

二、客户端如何定位数据

客户端通过键值对的key 获得哈希槽,但如何通过哈希槽找到对应的实例呢?

Redis实例会把自己的哈希槽信息发给和它相连接的其他实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。客户端收到的哈希槽信息后,会把哈希槽信息缓存在本地。

问题:实例和哈希槽的对应关系会改变?比如:

  1. 在集群中,有新实例新增或者删除,Redis需要重新分配哈希槽
  2. 为了负载均衡,Redis需要把哈希槽在所有实例上重新分布一遍

重定向机制

  1. Redis 实例之间可以通过互相传递消息,获取更新的哈希槽分配信息。

  2. 客户端访问时,如果访问的实例没有这个键值对映射的哈希槽,则这个实例会给客户端返回 MOVED 命令响应结果

    1
    2
    GET hello:key
    (error) MOVED 13320 172.16.19.5:6379
  3. 如果客户端访问某个哈希槽时,Redis 实例之间并没有迁移完这个哈希槽的数据,就会返回 ASK 错误,并附带新实例ip

    1
    2
    GET hello:key
    (error) ASK 13320 172.16.19.5:6379

    ASK有两层意思:1. 表明哈希槽数据还在迁移中 2. ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时客户端需要给新实例发送 ASKING 命令,让这个实例允许客户端接下来发送的命令。然后再发送操作命令。

注意:ASK 和 MOVED 命令不同,ASK命令并不会更新客户端缓存的哈希槽分配信息。MOVED命令会改变客户端本地缓存。

三、问题

Redis Cluster 方案通过哈希槽的方式将键值对分配到不同的实例上,这个过程需要客户端做计算,这样做有什么好处?为什么不直接记录键值对的key与实例的对应关系?

  1. 整个集群存储的key数量非常大时,key-实例映射表特别大,无论存储在服务端或客户端都占有非常大的内存空间
  2. Redis Cluster 采用无中心化(无proxy,客户端和服务端直连),后端的 redis 实例之间交换“key-实例”信息的开销会非常大,每个节点需要额外存储其他节点的路由表,内存占用过大造成资源浪费
  3. 当集群在扩容、缩容、数据均衡时,节点之间会发生数据迁移,迁移时需要修改每个key的映射关系,维护成本高。使用哈希槽为基本单位,简化了难度,便于集群的维护和管理。
  4. 而在中间增加一层哈希槽,可以把数据和节点解耦,相当于消耗了很少的CPU资源,不但让数据分布更均匀,还可以让这个映射表变得很小,利用客户端和服务端保存,节点之间交换信息时也变得轻量。