导语
最近,某客户自建redis迁移上云时出现了容量增加25%的现象。这到底是怎么回事呢?
问题背景
某客户通过dts将自建的redis5.0单机版迁移到云上5.0集群版时,dts出现如下图错误。
错误信息显示目标实例某个分片发生OOM,使用容量超过maxmemory了。客户反馈目标实例是一个2G*16总容量为32G的集群版,源实例使用容量才20G。第一印象怀疑源实例存在大key导致分片容量不均,从而导致目标实例OOM。但是客户反馈目标实例的容量远大于源实例,源实例容量接近20G而目标实例容量接近25G,监控如下图。
排除了源实例和目标实例的key数量不一样的这种情况之后,确认这个问题需要进一步分析。
问题分析及初步排查
在源实例和目标实例key数量基本一致的情况下,最初怀疑两个点:
1)源和目标实例的redis版本不一样(不同版本redis内存消耗不一样,但差别达到25%,可能性比较小);
2)源和目标实例的某些配置不一样,导致底层实现数据结构不一样(redis同样的数据类型有不同的底层数据结构实现,这个可由参数控制)。
第一个问题通过查看源实例版本确认,版本是一样的。
如上图所示,源实例和目标实例在控制底层数据结构实现的一些参数上也是一样的。
排除上述两种初步猜想后,这到底是怎么回事呢?开始并没有头绪,就想着在自己测试复现下,结果更加不可思议。
测试条件:
源实例:云redis5.0标准版,容量12G;目标实例:云redis5.0集群版,容量4G*3分片,总共12G
通过redis-benchmark写入了8kw+key
测试结果:
测试结果显示,目标实例和源实例容量基本一致,都在9.5G左右!那这到底是怎么回事呢?
谜底揭开
其实云上测试结果是没问题的,这个后面再解释。
云上测试未复现的情况下,想着直接用我们的内核和社区5.0的内核分别自建测试下。因为这里的差异主要集中在单机版与集群版上,因此通过自建redis测试分别用集群模式和单机模式启动一个节点来对比,启动参数唯一不同的就是cluster-enabled是否开启。结果如下:
第一个图为单机启动模式,第二个图为集群模式启动,都是同一个rdb文件,结果容量使用信息相差了接近50%。通过社区开源内核也同样复现了上述场景。到这里突然意识到云上的5.0标准版其实我们也是用的集群模式,同时还增加了arbitor节点。
刚好通过云上和自建测试对比,可以证明这里的容量差别就是由集群模式和单机模式的区别导致的。
现在问题在于集群模式和单机模式为什么会产生容量的差异?这些差异是怎么带来的。
集群模式和主从模式在存储上有一个比较大的差别在于,集群模式有槽的概念,并且有结构存储key的槽归属信息。
redis通过基数树来存储key的槽归属信息,基数树是在前缀索引树的基础上,做了压缩存储,优点是非常节省空间。
举个例子:
如上图,"()"表示非key节点,"[]"表示key节点,footer和foobar在Tier树和基数树的结构分别如上。可以容量发现,这种结构可以充分利用key的相同部分从而实现高效的空间利用。
那只剩下一个问题:这些容量差异是不是记录key的槽归属信息的基数树带来的呢?
由于redis没有相关的统计信息,无法直接证明上述结论。于是尝试修改源代码来验证结论。
验证方案如上所示:通过一个全局变量dfs_rax_size来记录基数树rax的容量信息,通过对rax的广度搜素来遍历基数树,实现为test_raxRecursiveShow,广搜过程中每次都将当前raxNode的容量信息增加到dfs_rax_size中。在加载完成rdb设置loading状态是调用test_raxRecursiveShow来统计rax的内存容量信息。
通过gdb挂载redis进程打印dfs_rax_size大小,如截图所示:dfs_rax_size=515734880,约500MB左右。回忆前文自建测试发现的单机模式为1G,集群模式约1.5G,结果完全符合之前的测试。自此,dts从主从版迁移到集群版的容量异常问题已经确认清楚。
总结
1.主从版迁移集群版需要预估更大的容量,避免因为集群模式额外的容量导致目标实例容量不够,导致OOM。
2.云上主从版采用集群模式存在浪费内存的问题,并且后续在大版本升级中存在较高的风险,可能触发OOM,这里大版本升级需要考虑这一情况。
3.由于采用的基数树来存储key的槽归属信息,其实额外容量是难以预估的
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。