实现redis运行时加载rdb文件

对redis比较了解的人应该知道redis提供rdb持久化机制。rdb文件其实就是redis在某一时间的一个快照,redis在重启时,可以加载这个快照,从而恢复状态。然而redis没有暴露加载rdb这个接口,因此没有办法在运行时手动地导入rdb快照。

其实,在阅读replicate.c后发现,redis的从节点在接收了rdb全量同步文件后,直接调用了rdbload函数——这就意味着:运行时动态加载rdb文件完全可行!毕竟redis自己也是这么做的。但是redis不肯暴露这个接口给用户。

我们上一篇文章要进行redis异地数据中心同步,同样需要加载rdb文件,如果只能重启redis来加载rdb文件,多少有点不舒服。因此探讨一下redis动态加载rdb文件的实现总归有好处的,事实证明我做到了。

实现

参考了修改redis-3.0.7源码以增加该功能的博客

我自己修改了redis-4.0.1的部分代码,暴露出原版redis不想暴露的接口。修改后的redis项目在redis-4.0.1-feature-online-loadrdb。所有重要的修改都在第二次提交。请一定要访问这个链接,git diff能够直观地展示我在redis源码上增加了哪些东西,所以我就不贴代码了。

效果是redis-cli中可以使用loadrdb <dumpfile>命令来热加载rdb文件。该命令的tcp报文是这样的:loadrdb filename\r\n。效果展示请看“测试”章节

测试

  1. clone上面的项目,进入项目文件夹
  2. 执行 make MALLOC=libc,进行编译
  3. 执行src/redis-server以默认配置(rdb开启)启动redis
  4. 启动另一个bash,执行以下命令:
src/redis-cli set a test       #设置a
src/redis-cli BGSAVE           #进行rdb持久化
src/redis-cli get a            #获取a,此时为test
sleep 10                       #睡10秒,等待rdb持久化完成
mv -f dump.rdb dump            #移动该dump.rdb文件到新文件dump
src/redis-cli flushall         #删除所有key
src/redis-cli get a            #此时a为nil
src/redis-cli loadrdb dump     #调用loadrdb指令热加载dump文件
# 等同于 (printf "loadrdb dump\r\n";sleep 1)|nc localhost 6379 
src/redis-cli get a            #此时a为test

以上测试先写了一个a,然后调用BGSAVE导出rdb文件,然后flushall删除所有key,然后使用loadrdb动态导入之前的rdb文件,最后检查a的value。结果能拿到之前设置的a的值,说明loadrdb工作正常!

尚未解决的问题:loadrdb指定的文件不能是redis.conf指定的rdbfilename,尚未增加该校验。如果是同一文件,则我们在loadrdb该文件,而同时redis可能正在往这个文件写rdb,这会导致redis崩溃。——以后会增加这个校验的,要查一下c语言字符串比较。。。我忘记了