Redis源码剖析和注释(二十六)--- Redis 集群伸缩原理源码剖析

Redis Cluster 集群伸缩原理源码剖析

1. Redis 集群伸缩教程

Redis提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以对下线节点进行缩容。

如何进行Redis Cluster的伸缩,请参考 Redis Cluster 集群扩容与收缩 本篇教程。本文详细分别使用手动命令和redis-trib.rb工具来执行Redis集群的扩容和收缩操作。

本篇文章根据Redis源码深入剖析集群伸缩的原理。Redis Cluster文件详细注释

2. 集群扩容原理剖析

集群扩容的步骤如下:

  • 准备新节点
  • 加入集群
  • 迁移槽和数据

我们根据步骤一步一步分析。

2.1 准备新节点

本步骤就是准备新的节点和配置启动文件。具体参考 Redis Cluster 载入配置文件、节点握手、分配槽源码剖析 一文的载入配置一部分,该部分从Redis服务器的main函数开始分析,一直到服务器启动成功。

2.2 加入集群

将新节点加入集群,就是发送CLUSTER MEET命令,将准备的新节点加入到已搭建好的集群,让所有的集群中的节点“认识”新的节点。

可以参考 Redis Cluster 载入配置文件、节点握手、分配槽源码剖析 一文中的节点握手部分,该部分根据源码分析了节点握手的三过程:发送MEET消息,回复PONG消息,发送PING消息,最后讲解了Gossip协议在Redis中是如何使用的。

2.3 迁移槽和数据

集群扩容的前两步和搭建集群很像,最后一步则是将集群节点中的槽和数据迁移到新的节点中,而不是为新的节点分配槽位。因此我们重点分析这一过程。Redis Cluster文件详细注释

源节点的槽位和数据迁移到目标节点中,迁移单个槽步骤如下:

  1. 对目标节点发送CLUSTER SETSLOT <slot> importing <source_name>,在目标节点中将<slot>设置为导入状态(importing)
  2. 对源节点发送CLUSTER SETSLOT <slot> migrating <target_name>,在源节点中将<slot>设置为导出状态(migrating)
  3. 对源节点发送CLUSTER GETKEYSINSLOT <slot> <count>命令,获取<count>个属于<slot>的键,这些键要发送给目标节点。
  4. 对于第三步获得的每个键,发送MIGRATE host port "" dbid timeout [COPY | REPLACE] KEYS key1 key2 ... keyN命令,将选中的键从源节点迁移到目标节点。
    • Redis 3.0.7以后的版本支持了MIGRATE命令的批量迁移操作。
    • 如果不支持批量迁移,那么会发送MIGRATE host port key dbid timeout [COPY | REPLACE]命令将一个键从源节点迁移到目标节点。
    • 当一次无法迁移完成时,会循环执行第三步和第四步,直到<count>个键全部迁移完成。
  5. 向集群中的任意节点发送CLUSTER SETSLOT <slot> node <target_name>,将<slot>指派给目标节点。指派信息会通过消息发送到整个集群中,然后最终所有的节点都会知道<slot>已经指派给了目标节点。

我们就根据这些步骤逐步分析:

2.3.1 目标节点中,将槽设置为导入状态

Redis Cluster文件详细注释

客户端连接上目标节点,并发送该命令给目标节点服务器,目标节点会调用clusterCommand()函数来执行该命令,该函数是一个通用函数,能用于执行CLUSTER开头的所有命令。该函数会判断SETSLOT选项,但是SETSLOT选项对应4种不同的函数分别是:

SETSLOT 10 MIGRATING <node ID>     //设置10号槽处于MIGRATING状态,迁移到<node ID>指定的节点
SETSLOT 10 IMPORTING <node ID>     //设置10号槽处于IMPORTING状态,将<node ID>指定的节点的槽导入到myself中
SETSLOT 10 STABLE                  //取消10号槽的MIGRATING/IMPORTING状态
SETSLOT 10 NODE <node ID>          //将10号槽绑定到NODE节点上

SETSLOT选项中先会判断myself节点是否为主节点,如果是从节点则直接返回,然后获取指定的槽号。

    int slot;
    clusterNode *n;

    // 如果myself节点是从节点,回复错误信息
    if (nodeIsSlave(myself)) {
        addReplyError(c,"Please use SETSLOT only with masters.");
        return;
    }
    // 获取槽号
    if ((slot = getSlotOrReply(c,c->argv[2])) == -1) return;

本小节主要看CLUSTER SETSLOT <slot> importing <source_name>命令,在目标节点中,将槽设置为导入状态,处理importing状态的代码如下:

    if (!strcasecmp(c->argv[3]->ptr,"importing") && c->argc == 5) {
    // 如果该槽已经是myself节点负责,那么不进行导入
    if (server.cluster->slots[slot] == myself) {
        addReplyErrorFormat(c,"I'm already the owner of hash slot %u",slot);
        return;
    }
    // 获取导入的目标节点
    if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) {
        addReplyErrorFormat(c,"I don't know about node %s",(char*)c->argv[3]->ptr);
        return;
    }
    // 为该槽设置导入目标
    server.cluster->importing_slots_from[slot] = n;
    // 更新集群状态和保存配置
    clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE);
    addReply(c,shared.ok);

先判断该槽位是否已经是目标节点所负责的,如果是则不需要进行导入,否则继续调用clusterLookupNode()函数,根据<source_name>在当前集群中查找源节点,然后将服务器集群状态的importing_slots_from对应的槽和导入的源节点做映射。当前执行该命令的节点是目标节点,因此在目标节点视角中的集群,该槽已经处于导入状态。

如果执行成功,则在进入下个周期之前更新集群状态和保存配置。

最后返回客户端一个OK

2.3.2 源节点中,将槽设置为导出状态

Redis Cluster文件详细注释

对源节点发送CLUSTER SETSLOT <slot> migrating <target_name>,将槽设置为导出状态,对应的代码如下:

    if (!strcasecmp(c->argv[3]->ptr,"migrating") && c->argc == 5) {
    // 如果该槽不是myself主节点负责,那么就不能进行迁移
    if (server.cluster->slots[slot] != myself) {
        addReplyErrorFormat(c,"I'm not the owner of hash slot %u",slot);
        return;
    }
    // 获取迁移的目标节点
    if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) {
        addReplyErrorFormat(c,"I don't know about node %s",(char*)c->argv[4]->ptr);
        return;
    }
    // 为该槽设置迁移的目标
    server.cluster->migrating_slots_to[slot] = n;
    // 更新集群状态和保存配置
    clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE);
    addReply(c,shared.ok);

在源节点视角的集群中,执行该命令。先会对该槽位的所有权进行判断,如果不属于源节点,那么就无权进行迁移。然后获取要迁移到的目标节点,调用clusterLookupNode()函数查找目标节点。最后将服务器集群状态中的migrating_slots_to对应的槽和导出的目标节点做映射关系。

如果执行成功,则在进入下个周期之前更新集群状态和保存配置。

最后返回客户端一个OK

2.3.3 获取迁移槽中的键

Redis Cluster文件详细注释

这一步要获取迁移槽中的所有键,这些键是要从源节点发送到目标节点。因此对应的选项是getkeysinslot,代码如下:

    if (!strcasecmp(c->argv[1]->ptr,"getkeysinslot") && c->argc == 4) {
        /* CLUSTER GETKEYSINSLOT <slot> <count> */
        long long maxkeys, slot;
        unsigned int numkeys, j;
        robj **keys;
        // 获取槽号
        if (getLongLongFromObjectOrReply(c,c->argv[2],&slot,NULL) != C_OK)
            return;
        // 获取打印键的个数
        if (getLongLongFromObjectOrReply(c,c->argv[3],&maxkeys,NULL)
            != C_OK)
            return;
        // 判断槽号和个数是否非法
        if (slot < 0 || slot >= CLUSTER_SLOTS || maxkeys < 0) {
            addReplyError(c,
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值