一、详解redis cluster数据迁移过程
1. 节点基本结构定义
redis集群提供16384个slot,我们可以按需分配给节点上,后续进行键值对存储时,我们就可以按照算法将键值对存到对应slot上的redis服务器上:
集群节点本质就是通过slots这个数组记录当前节点的所管理的情况,这里我们可以看到slots是一个char 数组,长度为REDIS_CLUSTER_SLOTS(16384)除8,这样做的原因是因为:
- char占1个字节,每个字节8位。
- 每个char可以记录8个slot的情况,如果是自己的slot则对应char的某一个位置记录为1:
我们以node-1为例,因为它负责0-5460的节点,所以它的slots0-5460都为1,对应的图解如下所示,可以看到笔者这里省略了后半部分,仅仅表示了0-15位置为1:
对此我们也给出这段redis中节点的定义,即位于cluster.h中的clusterNode这个结构体中,可以看slots这段定义:
2. 设置slot后续节点迁移
以本文示例为例,我们希望后续节点2的数据全部存到节点1中,那么我们首先需要键入如下两条配置:
这两条指最终都会被各自的服务端解析,并调用clusterCommand执行,我们以节点1导入为例,假设我们执行clusterCommand解析到setslot 关键字和importing关键字,即知晓要导入其他节点的数据。对应的节点1就会通过importing_slots_from数组标记自己将导入这个slot的数据,而节点2也会通过migrating_slots_to数组标记自己要将数据导出给其他节点的slot:
对此我们给出clusterCommand的执行流程,可以看到该函数解析出migrating或者importing关键字时就会将对的migrating_slots_to或者importing_slots_from数组对应slot位置的索引位置设置为当前上述命令传入的node id:
3. 请求重定向问题
后续的我们假设还是将set key value请求发送到节点2,因为上述命令的原因,节点会返回move/ask告知客户端这个键值对现在要存到节点1上。对应节点1收到这个key请求时,通过key计算得slot正是自己,它就会将这个键值对存储到自己的数据库中:
这里我们以节点1的角度查看这个问题,当客户端收到move指令后,继续向节点1发送指令,节点1通过收到指令调用processCommand,其内部调用getNodeByQuery获取当前key对应的slot,发现是自己则直接存储数据到当前节点的内存数据库中:
我们以节点的视角再次直接步入getNodeByQuery查看这段逻辑,可以看到其内部会基于key计算slot然后将得到对应的node,然后进行如下判断:
- 如果本次客户端请求是一个批量的请求,且第一个key定位不到响应的slot,直接返回错误。
- 如果key的slot属于当前节点,且当前节点正在迁出并且当前节点查不到这个key,则响应一个ask标识告知客户端到迁出的节点询问一下是否有数据。
- 如果是key属于当前节点且正在进行导入,且key定位不到则响应异常,反之说明当前节点导入成功,直接返回当前节点信息。
- 如果定位到的slot属于别的节点,则响应一个move告知客户端到别的节点获取键值对。
对应的我们给出这段代码函数getNodeByQuery,对应的逻辑和笔者上述给出的核心分支一致:
4. 完成节点迁移
上述操作仅仅针对新节点的告知要处理的新的slot,对于旧的节点的旧有slot数据我们就需要通过节点2键入CLUSTER GETKEYSINSLOT slot count要迁移的旧的key的slot,然后通过MIGRATE host port key dbid timeout [COPY | REPLACE]将数据迁移到节点1上。 这里我们补充一下MIGRATE 中copy和replace的区别,前者是遇到重复直接报错,后者是迁移时直接覆盖。 最终这条指令回基于要迁移的key而生成一条RESTORE-ASKING key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency]指令发送给导入的节点,以本文例子来说就是节点1:
这里我们给出MIGRATE 指令对应的处理函数migrateCommand,逻辑和我上文说的差不多,基于指令解析出replace或者copy等信息,然后用argv[3]即我们的key得出这个键值对的信息生成RESTORE指令将键值对转存给节点1:
5. 最后调整
最后我们只需在节点1和2都执行CLUSTER SETSLOT <SLOT> NODE <NODE ID> 完成slot指派,这指令最终就会走到clusterCommand中,节点1和节点2各自的处理逻辑为:
- 节点2看看迁移的key的数量未0且migrating_slots_to数据不为空,若符合要求,则说明本次迁移完成但状态未修改,直接将migrating_slots_to置空完成指派最后调整。
- 节点1查看节点id是否是自己,且importing_slots_from是否有数据,若有则说明节点导入完成,直接将importing_slots_from置空。
二、小结
自此我们将redis集群中的所有核心设计都分析完成,我们来简单小结一下整体过程:
- 通过CLUSTER IMPORTING/MIGRATING 进行slot迁入或迁出,redis服务端通过一个数组维护迁入和迁出的slot的信息。
- 后续客户端发起请求获取对应的slot的信息时,会通过上述两个数组获知节点迁移情况已做出结果响应。
- 通过步骤1能够告知对应的slot的新数据的存储指向,对于旧数据我们还是需要通过指令完成迁移,其本质就是服务端定位到对应slot上的key然后生成RESP规范的协议指令通知到迁移的节点上。
- 以迁出节点为例,看到自己对应slot的key为0且迁出数组非空,则说明迁出完成。
由此完成一次集群的迁移。