Redis 数据迁移至 Codis 集群方案

随着公司项目的发展,单台redis的性能逐渐达到瓶颈,为了保证业务的正常运行,必须对单台redis进行扩展,组建redis的集群。在这次集群组建的过程中我们采用了豌豆荚开源的codis集群来承接业务需求,通过再开多个实例的方式来分担redis的业务压力。具体的codis集群搭建的过程就不在此赘述,本文主要记录线上redis数据迁移到codis中的过程。

在迁移数据前,我们对redis中现有数据量做了统计,其aof持久化文件大小达到22G左右(新重写的aof文件大小)。在迁移前我们准备了三种方案来完成数据迁移工作:

「第一种」是停止线上业务,对redis数据完全持久化后再使用持久化文件做数据的导入工作。此种方式是最为稳妥也最简单的导入方式,但是缺点在于必须停止线上业务进行操作,数据持久化和导入期间不允许线上业务对Redis进行操作,停服务时间包括持久化时间,数据导入时间,预估时间在30-60分钟。因为会停服时间过长,此方案最终被放弃。

「第二种」是通过Redis数据迁移工具来完成迁移操作,现在市面上主要的迁移工具有codis官方提供的迁移工具redis-port以及唯品会开源的redis-migrate-tool,这两个工具在原理上都是一样的,模拟一个redis的slave,然后从源redis中同步数据到新的集群,这两种工具都支持数据的热同步,可以不停线上服务的同时同步数据,然后做一次闪断将业务切换到新的集群就可以了。但是这两个工具都有一些不支持的redis命令,比如redis-migrate-tool不支持RPOPLPUSH命令,而且对于不支持的命令其会直接丢弃,并不会修改数据(我也是醉了,写操作命令不支持,居然敢说支持热同步,谁给的唯品会勇气)。所以这种方案最终也被放弃。

「第三种」是通过利用reids的appendonly文件来导入数据,由于redis采用aof的方式持久化时会把所有的操作按时间记录到持久化文件中,这就意味着如果我们能记录下在某个时间点这个文件内容的,然后就可以进行增量的备份,导入等操作,但是如何记录某个时间点上这个文件的内容呢?其实很简单,我们可以手动对redis进行aof文件的重写,然后利用redis info信息中aof_base_size和aof_current_size这两个值来做增量数据的导入就可以了。其中我们还需要利用head和tail命令的两个功能,就是按字节截取文件内容,具体的方案如下:

1、关闭自动重写aof,避免在我们操作appendonly文件时redis自动重写,从而导致我们记录的字节点发生变化。

127.0.0.1:6379> CONFIG SET auto-aof-rewrite-percentage 0

2、手动执行自动重写操作,创建一份完整的appendonly.aof文件,这可以提高我们数据导入的速度,待执行完成后,我们要获取一下当前的 aof_base_size 值,使用这个值来导入数据到codis。

127.0.0.1:6379> BGREWRITEAOF    #开始重写aof文件
127.0.0.1:6379> info
……
aof_enabled:1
aof_rewrite_in_progress:0        #当前是否有重写进程在执行,1表示正在重写aof,0表示当前没有重写操作
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:417    #上次重写操作使用的时间,单位是秒
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok     #上次重写操作的执行结果,ok表示正常
aof_last_write_status:ok
aof_current_size:26501437420     #aof文件当前的字节数,做增量数据导入是会用到
aof_base_size:22704815550        #aof文件重写完成时的字节数
aof_pending_rewrite:0
aof_buffer_length:0
aof_rewrite_buffer_length:0
aof_pending_bio_fsync:0
aof_delayed_fsync:57
……

此时我们可以看到,aof_base_size这个参数的值就是redis在执行完一次完整的aof文件重写后那一时刻的aof文件的字节数,aof_current_size这个参数的值就是从aof重写完成时间点到当前时间点的aof文件的字节数,其中两者的差值就是这段时间的增量数据,利用这个字节数就可以做增量数据的导入了。

3、导入重写完成时的redis数据,通过head命令截取了appendonly.aof文件的前22704815550个字节,导入codis中

head -c 22704815550 /var/lib/redis/6379/appendonly.aof | redis-cli -h codis地址 -p codis端口 --pipe

4、停止业务,禁用源redis端口,保证在切换过程中没有新的redis操作进行,此时可以通过iptables封掉redis端口的方式,或者直接停止redis服务来控制,本文采用iptables的方式来封锁端口。

5、确保没有新的redis操作后,我们查看info信息中当前 aof_current_size 参数的值,准备做增量数据的导入。利用tail命令和aof_current_size – aof_base_size的差值来进行增量数据的导入。

tail -c 3796621870 /var/lib/redis/6379/appendonly.aof | redis-cli -h codis地址 -p codis端口 --pipe

6、修改业务代码中redis的地址到codis的地址,启动服务,此时原redis的访问端口依然被封闭,待服务正常后就可以关闭原redis服务了。

至此,reids中的数据已经完全导入到新的codis集群中了。在本方案中,线上业务需要停止服务,但是停服时间已经得到了大幅度的减少。在第一次导入数据时aof文件有22G,耗时大概10分钟左右,但此时线上的业务并没有停止,在第二次导入增量数据的时候,经过实测1G左右的增量数据导入时间大概在1分钟左右,加上封端口等操作,基本可以控制在5分钟内完成。上述步骤还可以通过脚本来完成,可以进一步缩减停服时间到两分钟之内,这个时间对于公司当前的场景是可以接受的。