Redis同步迁移数据

概述

        Redis集群版采用hash slot的方式来决定key存在哪个slot中。它总共有16384个slot,通过CLUSTER SLOTS或者CLUSTER NODES可获取slot分布情况。每个key只能存储于一个slot里面,具体一个key存储于哪个slot是通过crc16( key)%16384计算得来。也可通过命令CLUSTER KEYSLOT获取某个key位于哪个slot,一般情况下,集群中每个分片平均分配slot。比如3个分片。那每个分片的slot分别为0-5460,5461-10922,10923-16383。但这些slot并不是固定的,可以动态调整。下面则主要介绍它的调整方式及存在的问题及风险。

slot迁移流程

迁移一个slot大体可简化为3步,流程如下图所示:

Redis迁移流程

Redis迁移流程

  1. 标记迁移状态

标记迁移目标分片的待迁移slot为IMPORTING状态,然后再标记源分片待迁移slot为MIGRATING状态。这里标记状态一定不能置换,否则会导致在该slot上的源分片及目标分片都无法写入新数据。

  1. 迁移slot中的所有数据

依次获取slot中的key,然后将这些key迁移到目标分片,直到所有的key迁移完成。

  1. 标记slot归属权

标记迁移完的slot归属权为迁移的目标分片,然后再标记源分片迁移完的slot归属于目标分片。这里标记状态一定不能置换,否则同 样会导致在该slot上的源分片及目标分片都无法写入新数据。

模拟搬迁过程

首先创建一个3主3从的集群,其初始化的集群分片信息如下,其中红色标记了分片slot信息,其第一列为分片ID。

32fe95bc56611ced95553ba90bc7add1e3bef3ad 9.134.240.17:2777@12777 master – 0 1581226004825 4 connected 0-5460

ea63f5b7a787f2f12c74669814db487b1b18a887 9.134.240.102:3307@13307 slave c6e02ef185bd9d641b8a50fd82781f0aeb5eb618 0 1581226005827 5 connected

952c500a85976e818ce0da15e036f96f0076382c 9.134.240.214:3074@13074 myself,slave 32fe95bc56611ced95553ba90bc7add1e3bef3ad 0 1581225986000 3 connected

c6e02ef185bd9d641b8a50fd82781f0aeb5eb618 9.134.240.85:1637@11637 master – 0 1581226003824 5 connected 10923-16383

5a7c0f4a005ba70c8aa5097424d85dc07eb19c6e 9.134.241.18:3447@13447 master – 0 1581226002822 2 connected 5461-10922

a2574a5a2f54db80e4e401f8a1cc900b2da035aa 9.134.240.75:4219@14219 slave 5a7c0f4a005ba70c8aa5097424d85dc07eb19c6e 0 1581226006828 2 connected

首先写入一个testmigrate到集群中,通过cluster keyslot testmigrate可算出它位于4470号slot,那么待迁移的源分片为32fe95bc56611ced95553ba90bc7add1e3bef3ad,在迁移前,向源分片写入set testmigrate 1。我们将这个分片迁移到目标分片c6e02ef185bd9d641b8a50fd82781f0aeb5eb618。

  1. 连接目标分片,标记迁移目标分片的待迁移slot为IMPORTING状态。(cluster setslot 4470 importing 32fe95bc56611ced95553ba90bc7add1e3bef3ad),在标志后,目标分片信息为:c6e02ef185bd9d641b8a50fd82781f0aeb5eb618 9.134.240.85:1637@11637 myself,master – 0 1581228243000 5 connected 10923-16383 [4470-<-32fe95bc56611ced95553ba90bc7add1e3bef3ad]
  2. 连接源分片,标记源分片待迁移slot为MIGRATING状态。(cluster setslot 4470 migrating c6e02ef185bd9d641b8a50fd82781f0aeb5eb618)标志后目标分片信息为:32fe95bc56611ced95553ba90bc7add1e3bef3ad 9.134.240.17:2777@12777 myself,master – 0 1581228385000 4 connected 0-5460 [4470->-c6e02ef185bd9d641b8a50fd82781f0aeb5eb618]
  3. 模拟数据写入(操作流程可跳过这个步骤,这步用于解释方案存在的问题)
    1. 写入或者修改未迁移的数据testmigrate,此时返回成功,并无影响。
    2. 写入已经迁移完的key或者新的key,此时会返回(error) ASK 4470 9.134.240.85:1637错误,这个时候我们需要连接到迁移的目标分片,然后先执行asking,再写入已经迁移完的key或者新的key,这个也是同步扩容的关键点,它能保证数据一定可以迁移成功,与业务的写入速度无直接关系,这个属于同步方案的最大优点。但这点上缺陷也比较明显,如果用户通过mset或者mget这类命令写入或者查询多个key的时候,如果这些key在源分片不能完全找到,它会返回ASK错误,此时需要转向目标分片执行写入和查询,但执行时并不能保证所有的key已经迁移到目标分片,这个时候会返回(error) TRYAGAIN Multiple keys request during rehashing of slot错误,这样就导致key无法正常写入。这种情况可以通过client或者proxy将mset/mget在写入redis之前进行拆分,这样相当于set/get命令,此时便不会出现无法写入的情况,但性能会有影响。
  4. 批量从源分片获取KEY ,(CLUSTER GETKEYSINSLOT 4470 10),命令一次读取10个key,但由于迁移前只写入了一个key,这里只会返回一个key。testmigrate
  5. 迁移上步获取这些KEY (migrate 9.134.240.85 1637 "" 0 10000 auth password keys testmigrate),这个命令会迁移上步获取 key。
  6. 重复前面两步至到迁移完后
  7. 连接目标分片,标记迁移完的slot归属权为迁移的目标分片(cluster setslot 4470 node c6e02ef185bd9d641b8a50fd82781f0aeb5eb618)。
  8. 连接源分片,然后再标记源分片迁移完的slot为目标分片。(cluster setslot 4470 node c6e02ef185bd9d641b8a50fd82781f0aeb5eb618)

同步迁移存在问题及解决办法

  1. 迁移时长限制

Redis请求处理为单线程,所有命令都只能串形执行,如果在我们迁移过程中有一个大key,那么在迁移过程用户及集群gossip请求处理都会阻塞,在我们的迁移测试中,迁移600MB的list,4千万个整形key,整个迁移时间为7.42s。那在这个7.42s的过程中,用户是无法有请求响应。由于我们分片cluster-node-timeout为默认的15s,所以在整个迁移过程中如果超过15s分片会被判死,从而导致主从切换。为了减少对业务的影响,同时避免主从切换,那就需要控制迁移时间。为了控制每次Key的搬迁时长,我们引入了迁移评估流程,就是在迁移前,检查是否满足迁移条件,总共需要检查的有两个点,一是每个key的搬移时长不能超过指定时长,我们目前定的是3s,那转换为key的大小,大体为200MB,所以我们在迁移过程中是限制单Key不能超过200MB,

  1. 迁移容量评估

除迁移时长外,还一个就是迁移容量,迁移过程中一定要保证目标分片一定有足够的容量容下带迁移的key。Redis获取key的容量使用的是MEMORY USAGE key samples count方法,但这个方法需要借助管控去查询所有待迁移的key,并且分slot统计需要迁移的容量,这种方式最主要的问题就是速度太慢。为了减少评估时间,我们在Redis中新增了评估命令,该命令返回slot的容量及其中最大Key的容量来解决迁移评估。如果在一个缩容流程中,它的数据迁移流程如下:

扩缩容流程

扩缩容流程

  1. 在两个分片中同一个slot都存在部分key时的访问问题。

在上节3b步骤中提过其存在的问题,这里不在重复。除了一些批量执行命令外,在lua执行中也可能出现执行报错。

  1. 迁移一个slot到全新分片时lua无法迁移的问题。

由于在新分片无lua相关脚本 ,如果通过EVALSHA执行则会报错。 但在实际中,一般比较少的情况下只使用evalsha命令,因为lua脚本一旦变了,那么脚本的sha也会改变。

  1. 迁移过程中主从切换导致集群无法写入及多份数据的问题

源分片发生主从切换,此时集群在目标分片不认可源分片对迁移分片的归属权,从而导致该分片认为集群fail,此时该分片则无法写入数据。但由于源分片可以写入,此时可能存在两个不同分片上面存在二份不同版本的数据,同时读取的时候由于部分数据已经迁移,也会导致部分数据无法读取。

  1. 对业务存在很小的性能影响

在迁移过程中会在源分片dump数据,然后在目标分片restore数据,会一定程度增加一定的写入量,但这个可以根据并发迁移key个数及加入一定迁移间隔来减少对业务影响。

总结

Redis同步迁移有着简单,迁移不受写入速度的限制,但也存在一些无法规避的问题,特别是迁移大key影响业务及集群、lua无法迁移到新分片的问题,同步迁移都无法很好的支持,并且迁移过程中存在状态,也增加了一定迁移风险。在redis5.0中redis-cli直接集成了cluster相关的工具,比如slot均衡,slot扩容状态修复等,也简化了常用运维操作,但本身并没有解决其存在的问题,我们在实际的生产环境中改动redis源码来加强迁移稳定性,但还是无法消除同步迁移方法的不足。而最终解决只能通过异步迁移来解决同步迁移的问题,目前公有云最新的迁移已经切换到异步方案。可参考https://cloud.tencent.com/developer/article/1598700。