摘要:一次由fork引起的时延抖动问题。
华为云数据库GaussDB(for Redis) 是一款基于计算存储分离架构,兼容Redis生态的云原生NoSQL数据库;它依靠共享存储池实现了强一致,支持持久化落盘存储,保证数据的安全可靠。其核心特色是:存算分离、强一致、低成本、超大容量。html
GaussDB(for Redis)服务团队在支撑某客户业务上云的过程当中,发现一次由fork引起的时延抖动问题,本着对客户负责任的态度,咱们详细探究了fork这个系统调用的性能影响,而且在最新的GaussDB(for Redis)版本已解决了这个抖动问题,清零了内部的fork使用,与原生Redis相比,完全解决了fork的性能隐患。redis
某客户业务接入GaussDB(for Redis)压测发现,每5分钟系统出现一次规律性的时延抖动:数据库
下图是从系统慢日志中捕获到的发生抖动的消息样例(对敏感信息进行了遮掩):segmentfault
1)因为故障的时间分布很是规律,首先排除定时任务的影响,主要包括:安全
屏蔽上述2类定时任务后,抖动依然存在。数据结构
2)排除法未果后,决定回到正向定位的路上来。经过对数据访问路径增长分段耗时统计,最终发现抖动时刻内存操做(包括allocate、memcpy等)的耗时显著变长;基本上长出来的时延,都是阻塞在了内存操做上。架构
(截图为相关日志,单位是微秒)性能
3)既然定位到是系统级操做的抖动,那么下一步的思路就是捕获抖动时刻系统是否有异常。咱们采起的方法是,经过脚本定时抓取top信息,分析系统变化。运气比较好,脚本部署后一下就抓到了一个关键信息:每次在抖动的时刻,系统中会出现一个frm-timer进程;该进程为GaussDB(for Redis)进程的子进程,且为瞬时进程,持续1-2s后退出。测试
4)为了确认该进程的影响,咱们又抓取了perf信息,发如今该进程出现时刻,Kmalloc, memset_sse,memcopy_sse等内核系统调用增多。从上述信息推断,frm-timer进程应该是被fork出来的,抖动源基本可锁定在fork frm-timer这个动做上。spa
1)分析frm-timer的来历是下一步的关键。由于这个标识符不在咱们的代码中,因此就须要拉通给咱们提供类库的兄弟部门联合分析了。通过你们联合排查,确认frm-timer是日志库liblog中的一个定时器处理线程。若是这个线程fork了一个匿名的子进程,就会复用父进程的线程名,表现为Redis进程建立出1个名为frm-timer的子进程的现象。
2)因为frm-timer负责处理liblog中全部模块的定时器任务,到底是哪一个模块触发了上述fork?这里咱们采起了一个比较巧妙的方法,咱们在定时器处理逻辑中增长了一段代码:若是处理耗时超过30ms,则调用std:: abort()退出,以生成core栈。
3)经过分析core栈,并结合代码排查,最终确认引起抖动的代码以下:
上述代码是用来周期性归档日志的,它每5分钟会执行1次 system系统调用来运行相关脚本,完成归档日志的操做。而Linux system系统调用的源码以下,其实是一个先fork子进程,再调用execl的过程。
4)分析至此,咱们还须要回答最后一个问题:到底是fork致使的抖动,仍是脚本内容致使的抖动?为此,咱们设计了一组测试用例:
最终的验证结果:
用例1结果代表抖动和脚本内容无关;用例二、三、4的结果代表调用system引起抖动的根因是由于其中执行了fork操做;用例5的结果进一步佐证了抖动的根因就是由于fork操做。最终的故障缘由示意图以下:
1)众所周知,fork是Linux(严格说是POSIX接口)建立子进程的系统调用,历史上看,主流观点大多对其赞誉有加;但近年间随着技术演进,也陆续出现了反对的声音:有人认为fork是上个时代遗留的产物,在现代操做系统中已通过时,有不少害处。激进的观点甚至认为它应该被完全弃用。(参见附录1,2)
2)fork当前被诟病的主要问题之一是它的性能。你们对fork一般的理解是其采用copy-on-wirte写时复制策略,所以对其的性能影响不甚敏感。但实际上,虽然fork时可共享的数据内容不须要复制,但其相关的内核数据结构(包括页目录、页表、vm_area_struc等)的复制开销也是不容忽视的。附录一、2中的文章对fork开销有详细介绍,咱们这回遇到的问题也是一个鲜活的案例:对于Redis这样的时延敏感型应用,1次fork就可能致使消息时延出现100倍的抖动,这对于应用来讲无疑是不可接受的。
1)数据备份
备份时须要生成RDB文件,所以Redis须要触发一次fork。
2)主从同步
全量复制场景(包括初次复制或其余堆积严重的状况),主节点须要产生RDB文件来加速同步,一样须要触发fork。
3)AOF重写
当AOF文件较大,须要合并重写时,也会产生一次fork。
1)业务抖动
原生Redis采用单线程架构,若是在电商大促、热点事件等业务高峰时发生上述fork,会致使Redis阻塞,进而对业务形成雪崩的影响。
2)内存利用率只有50%
Fork时子进程须要拷贝父进程的内存空间,虽然是COW,但也要预留足够空间以防不测,所以内存利用率只有50%,也使得成本高了一倍。
3)容量规模影响
为减少fork的影响,生产环境上原生Redis单个进程的最大内存量,一般控制在5G之内,致使原生Redis实例的容量大大受限,没法支撑海量数据。
注:GaussDB(for Redis)由华为云基于存算分离架构自主开发,所以不存在原生Redis的fork调用的场景。
本文经过分析GaussDB(for Redis)的一次由fork引起的时延抖动问题,探究了fork这个系统调用的性能影响。最新的GaussDB(for Redis)版本已解决了这个抖动问题,并清零了内部的fork使用,与原生Redis相比,完全解决了fork的性能隐患。但愿经过这个问题的分析,可以带给你们一些启发,方便你们更好的选型。
1.[是时候淘汰对操做系统的 fork() 调用了]
https://www.infoq.cn/article/BYGiWI-fxHTNvSohEUNW
2.[Linux fork那些隐藏的开销]
https://www.mdeditor.tw/pl/29L0
3.[Redis官方文档]
https://redis.io/topics/latency
4.[Redis的一些坑]
https://www.jianshu.com/p/03df6fd516eb
5.[Redis 常见问题之-fork操做]
https://blog.csdn.net/longgeqiaojie304/article/details/89407214
6.[GaussDB(for Redis)官网连接]
https://www.huaweicloud.com/product/gaussdbforredis.html
本文做者:华为云数据库GaussDB(for Redis)团队