走进Redis-扯扯集群

集群

为什么需要切片集群

已经有了管理主从集群的哨兵,为什么还需要推出切片集群呢?我认为有两个比较重要的原因:

N/2+1

下面来聊聊 Redis cluster 是如何解决这两个问题的。

什么是切片集群

切片集群是一种水平扩展的技术方案,它的主体思想是增加 Redis 实例组成集群,将原来保存在单个实例的上数据切片按照某种算法分散在各个不同的实例上,以减轻单个实例数据过大时同步和持久化时的压力。同时,水平扩展方案和垂直扩展方案相比扩展性更强,受硬件和成本的影响更小。

目前主要的实现方案有官方的 Redis Cluster 和 第三方的 Codis。Codis 在官方的 Redis Cluster 成熟之后已经很久没有更新了,这里就不特别介绍了,主要介绍下 Redis Cluster 的实现。

数据分区

切片集群实际就是一个分布式的数据库,而分布式数据库首先要解决的问题就是如何把整个数据集划分到多个节点上。

Redis Cluster 采用的是虚拟槽的分区技术。Redis Cluster 一共定义了 16384 个槽,数据与槽关联,而不是和实际的节点关联,这样可以很好的将数据和节点解耦,方便数据拆分和集群扩展。通常情况下,Redis 会将槽平均分配到节点上,用户可以使用命令 cluster addslots 来手动分配。需要注意的是,手动分配哈希槽时必须要把 16384 个槽都分配完,否则 Redis 集群会无法工作。

映射到节点的流程有两步:

  1. 根据 key 计算一个 16 bit 的值,然后将这个值对 16384 取模,得到 key 应该落在哪个槽上: crc16(key) % 16384。
  2. Redis Cluster 搭建完成的时候会预先分配每个节点负责哪几个槽的数据,客户端连接到集群的时候会获取到映射关系,然后客户端会将数据发送到对应的节点上。

功能限制

集群功能目前有一些功能限制:

  1. mset、mget 等批量操作、事务操作,lua 脚本只支持对落在同一个 slot 上 key 进行操作。
  2. 集群只能使用一个 db0,而不像单机 Redis 可以支持 16 个 db。
  3. 主从复制不支持联级主从复制,即从库只能从主库同步数据。

哈希标签(hash tag)

为了解决上述的第一个问题,Redis Cluster 提供了哈希标签(hash tag)功能,例如有两条 Redis 命令:

set Hello world
set Hello1 world

这两个操作的 key 可能不会落在同一个槽上。这时候如果将 Hello1 改成 {Hello}1,Redis就会只计算被{}包围的字符串属于那个槽,这样这两个命令的 key 就会落在同一个槽上,就可以使用 mset、事务、lua 脚本来处理了。

可以使用命令 cluster keyslot <key> 来验证两个 key 是否落在同一个槽上。

127.0.0.1:30001> cluster keyslot hello
(integer) 866
127.0.0.1:30001> cluster keyslot hello1
(integer) 11613
127.0.0.1:30001> cluster keyslot {hello}1
(integer) 866

请求重定向

如果没有使用哈希标签,如果 Redis 命令中 key 计算之后的哈希值不是落在当前节点持有的槽内,节点会返回一个 MOVED 错误,告诉客户端该操作应该在哪个节点上执行:

127.0.0.1:30006> set hello1 world
(error) MOVED 11613 127.0.0.1:30003
127.0.0.1:30006>

节点是不会处理请求转发的功能,我们启动 redis-cli 的时候可以添加一个 -c 参数,这样,redis-cli 就会帮我们转发请求了,而不是返回一个错误:

$ redis-cli -p 30006 -c
127.0.0.1:30006> set hello1 world
-> Redirected to slot [11613] located at 127.0.0.1:30003
OK

ACK重定向

当集群进行伸缩重新分配槽的时候,如果有请求需要处理落在迁移中的槽上,那么 Redis Cluster 会怎么处理呢?

Redis 定义了一个结构 clusterState 来记录本地的集群状态,其中有几个成员来记录槽的信息:

typedef struct clusterState {
    ...
    clusterNode *migrating_slots_to[CLUSTER_SLOTS];   //记录槽转移的目标节点
    clusterNode *importing_slots_from[CLUSTER_SLOTS]; //记录槽转移的源节点
    clusterNode *slots[CLUSTER_SLOTS]; //记录集群中槽所属的节点
    ...
} clusterState;

当开始重新分配槽时,拥有槽的原节点会将目标节点记录到 migrating_slots_to 中,目标节点会将原节点信息记录到 importing_slots_from 中,重分配槽的过程中,槽的拥有者还是原节点。

此时原节点收到操作命令时,如果在本地找不到数据,会在 migrating_slots_to 中找到目标节点信息,然后返回 ACK 重定向来告诉客户端对应的数据正在迁移到目标节点。

$ redis-cli -p 6380 -c get key:test:5028
(error) ASK 4096 127.0.0.1:6380

收到 ACK 之后,不像 MOVED 错误一样直接到对应的节点上执行命令就可以了,首先需要发送一个 ASKING,然后再发送实际的命令。这是因为在重分配槽的过程中,槽的所有者还没有发生改变,如果直接向目标节点发送命令,目标节点会直接返回 MOVED 错误,因为目标节点在本地的 clusterState→slots 中并没有发现 key 所属的槽分配给了自己。

需要注意的是 ASKING 命令是临时的,收到 AKSING 命令后会开启 CLIENT_ASKING(askingCommand 函数),执行完命令后会将 CLIENT_ASKING 状态清除(resetClient函数)。

实验

在搭建好的集群中,插入 3 条数据:

mset key:test:5028 world key:test:68253 world key:test:79212 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值