写点什么

一场由 fork 引发的超时,让我们重新探讨了 Redis 的抖动问题

发布于: 2021 年 03 月 02 日

摘要:高斯 Redis,彻底解决原生 Redis 的 fork 抖动问题!


背景介绍


在一次支撑客户业务上云的过程中,发现一次由 fork 引发的时延抖动问题,通过详细探究了 fork 这个系统调用的性能影响,并且在最新的 GaussDB(for Redis)版本已解决了这个抖动问题,清零了内部的 fork 使用,与原生 Redis 相比,彻底解决了 fork 的性能隐患。


问题焦点


· 华为云 GaussDB(for Redis)服务在某客户上云线调测过程中发现,系统上量后规律性的出现每 5 分钟 1 次的时延抖动问题。


· 华为云 GaussDB(for Redis)团队经过攻关,最终确认抖动原因是 fork 导致并解决了这个问题。而 fork 是开源 Redis 的一个重要依赖,希望通过本文的分享,能够帮助大家在使用开源 Redis 的时候,充分认识 fork 的影响,从而选择更优的方案。


问题现象


客户业务接入 GaussDB(for Redis)压测发现,每 5 分钟系统出现一次规律性的时延抖动:


· 正常情况消息时延在 1-3ms,抖动时刻时延达到 300ms 左右。


· 通常是压测一段时间后开始出现抖动;抖动一旦出现后就非常规律的保持在每 5 分钟 1 次;每次抖动的持续时长在 10ms 以内。


下图是从系统慢日志中捕获到的发生抖动的消息样例(对敏感信息进行了遮掩):



问题分析


1)排查抖动源:


· 由于故障的时间分布非常规律,首先排除定时任务的影响,主要包括:


· agent:和管控对接的周期性统计信息上报任务


· 内核:执行引擎(Redis 协议解析)和存储引擎(rocksdb)的周期性操作(包括 rocskdb 统计,wal 清理等)


屏蔽上述 2 类定时任务后,抖动依然存在。


· 排除法未果后,决定回到正向定位的路上来。通过对数据访问路径增加分段耗时统计,最终发现抖动时刻内存操作(包括 allocate、memcpy 等)的耗时显著变长;基本上长出来的时延,都是阻塞在了内存操作上。



(截图为相关日志,单位是微秒)


· 既然定位到是系统级操作的抖动,那么下一步的思路就是捕获抖动时刻系统是否有异常。我们采取的方法是,通过脚本定时抓取 top 信息,分析系统变化。运气比较好,脚本部署后一下就抓到了一个关键信息:每次在抖动的时刻,系统中会出现一个 frm-timer 进程;该进程为 GaussDB(for Redis)进程的子进程,且为瞬时进程,持续 1-2s 后退出。




· 为了确认该进程的影响,我们又抓取了 perf 信息,发现在该进程出现时刻,Kmalloc, memset_sse,memcopy_sse 等内核系统调用增多。从上述信息推断,frm-timer 进程应该是被 fork 出来的,抖动源基本可锁定在 fork frm-timer 这个动作上。


2)确定引发抖动的代码:


· 分析 frm-timer 的来历是下一步的关键。因为这个标识符不在我们的代码中,所以就需要拉通给我们提供类库的兄弟部门联合分析了。经过大家联合排查,确认 frm-timer 是日志库 liblog 中的一个定时器处理线程。如果这个线程 fork 了一个匿名的子进程,就会复用父进程的线程名,表现为 Redis 进程创建出 1 个名为 frm-timer 的子进程的现象。


· 由于 frm-timer 负责处理 liblog 中所有模块的定时器任务,究竟是哪个模块触发了上述 fork?这里我们采取了一个比较巧妙的方法,我们在定时器处理逻辑中增加了一段代码:如果处理耗时超过 30ms,则调用 std:: abort()退出,以生成 core 栈。


· 通过分析 core 栈,并结合代码排查,最终确认引发抖动的代码如下:



上述代码是用来周期性归档日志的,它每 5 分钟会执行 1 次 system 系统调用来运行相关脚本,完成归档日志的操作。而 Linux system 系统调用的源码如下,实际上是一个先 fork 子进程,再调用 execl 的过程。



· 分析至此,我们还需要回答最后一个问题:究竟是 fork 导致的抖动,还是脚本内容导致的抖动?为此,我们设计了一组测试用例:


· 用例 1:将脚本内容改为最简单的 echo 操作


· 用例 2:在 Redis 进程里模拟 1 个类似 frm-timer 的线程,通过命令触发该线程执行 fork 操作


· 用例 3:在 Redis 进程里模拟 1 个类似 frm-timer 的线程,通过命令触发该线程执行先 fork,再 excel 的操作


· 用例 4:在 Redis 进程里模拟 1 个类似 frm-timer 的线程,通过命令触发该线程执行 system 的操作


· 用例 5:在 Redis 进程里模拟 1 个类似 frm-timer 的线程,通过命令触发该线程执行先 vfork,再 excel 的操作


最终的验证结果:


· 用例 1:有抖动。


· 用例 2:有抖动。


· 用例 3:有抖动。


· 用例 4:有抖动。


· 用例 5:无抖动。


用例 1 结果表明抖动和脚本内容无关;用例 2、3、4 的结果表明调用 system 引发抖动的根因是因为其中执行了 fork 操作;用例 5 的结果进一步佐证了抖动的根因就是因为 fork 操作。最终的故障原因示意图如下:



3)进一步探究 fork 的影响:


· 众所周知,fork 是 Linux(严格说是 POSIX 接口)创建子进程的系统调用,历史上看,主流观点大多对其赞誉有加;但近年间随着技术演进,也陆续出现了反对的声音:有人认为 fork 是上个时代遗留的产物,在现代操作系统中已经过时,有很多害处。激进的观点甚至认为它应该被彻底弃用。(参见附录 1,2)


· fork 当前被诟病的主要问题之一是它的性能。大家对 fork 通常的理解是其采用 copy-on-wirte 写时复制策略,因此对其的性能影响不甚敏感。但实际上,虽然 fork 时可共享的数据内容不需要复制,但其相关的内核数据结构(包括页目录、页表、vm_area_struc 等)的复制开销也是不容忽视的。附录 1、2 中的文章对 fork 开销有详细介绍,我们这回遇到的问题也是一个鲜活的案例:对于 Redis 这样的时延敏感型应用,1 次 fork 就可能导致消息时延出现 100 倍的抖动,这对于应用来说无疑是不可接受的。


4)原生 Redis 的 fork 问题:


4.1 原生 Redis 同样被 fork 问题困扰(参见附录 3,4,5),具体包括如下场景:


1)数据备份


备份时需要生成 RDB 文件,因此 Redis 需要触发一次 fork。


2)主从同步


全量复制场景(包括初次复制或其他堆积严重的情况),主节点需要产生 RDB 文件来加速同步,同样需要触发 fork。


3)AOF 重写


当 AOF 文件较大,需要合并重写时,也会产生一次 fork。


4.2 上述 fork 问题对原生 Redis 的影响如下:


1)业务抖动


原生 Redis 采用单线程架构,如果在电商大促、热点事件等业务高峰时发生上述 fork,会导致 Redis 阻塞,进而对业务造成雪崩的影响。


2)内存利用率只有 50%


Fork 时子进程需要拷贝父进程的内存空间,虽然是 COW,但也要预留足够空间以防不测,因此内存利用率只有 50%,也使得成本高了一倍。


3)容量规模影响


为减小 fork 的影响,生产环境上原生 Redis 单个进程的最大内存量,通常控制在 5G 以内,导致原生 Redis 实例的容量大大受限,无法支撑海量数据。


解决方法


  1. 修改日志库 liblog 中的周期性归档逻辑,不再 fork 子进程。

  2. 系统排查并整改 GaussDB(for Redis)代码(包括使用的类库代码)中的 fork 调用。

  3. 最终排查结果,实际只有本次的这个问题点涉及 fork。当前修改后即可确保 GaussDB(for Redis)的时延保持稳定,不再受 fork 性能影响。


注:GaussDB(for Redis)由华为云基于存算分离架构自主开发,因此不存在原生 Redis 的 fork 调用的场景。


总结


本文通过分析 GaussDB(for Redis)的一次由 fork 引发的时延抖动问题,探究了 fork 这个系统调用的性能影响。最新的 GaussDB(for Redis)版本已解决了这个抖动问题,并清零了内部的 fork 使用,与原生 Redis 相比,彻底解决了 fork 的性能隐患。希望通过这个问题的分析,能够带给大家一些启发,方便大家更好的选型。


附:


1.[是时候淘汰对操作系统的 fork() 调用了]


https://www.infoq.cn/article/...


2.[Linux fork 那些隐藏的开销]


https://www.mdeditor.tw/pl/29L0


3.[Redis 官方文档]


https://redis.io/topics/latency


4.[Redis 的一些坑]


https://www.jianshu.com/p/03d...


5.[Redis 常见问题之-fork 操作]


https://blog.csdn.net/longgeqiaojie304/article/details/89407214


6.[GaussDB(for Redis)官网链接]


https://www.huaweicloud.com/product/gaussdbforredis.html


本文分享自华为云社区《华为云 PB 级数据库 GaussDB(for Redis)揭秘第三期:一场由 fork 引发的超时,让我们重新探讨了 Redis 的抖动问题》,原文作者:高斯 Redis 官方博客 。


点击关注,第一时间了解华为云新鲜技术~


发布于: 2021 年 03 月 02 日阅读数: 27
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
一场由fork引发的超时,让我们重新探讨了Redis的抖动问题