哪怕是作为C/C++开发人员,内存泄漏这个现象也是极其容易碰到的问题当中的一个,这是因为C/C++语言本身所具有的特性而引发的,大家都清楚,开源的那种被称作时序数据库的Time Series Database也就是TDengine OSS,它是运用C语言来进行底层部分的自主研发工作的,所以呢,针对内存产生泄露这类问题,我们负责研发的那些小伙伴也开展了许多方面的研究以及进行了不少的思考。在这篇文章里头,我们会以GitHub上一个有关内存泄漏的issue为起始点,跟大家一块儿探讨致使内存泄漏的缘由,还有怎样去避免以及定位内存泄漏 。
issue 链接:
这是一个链接,链接内容为https://github.com/taosdata/TDengine/issues/18276 。
可以从上述issue详尽描述里看出,这是一个疑似内存泄漏的问题,该用户运用TDengine OSS,从3.0.1.6版本起,持续升级测试直至3.0.2.2版本,内存泄漏问题始终存在。这个问题简化概括就是:在仅有一个简单查询(比如select count(*) from子表)且不断重复进行查询的情形下,taosd内存持续上升。测试之时,taosd内存占用自400MB起,能够持续涨至24GB以上。那期间,另外有其他用户也评论反馈碰到相同问题,于内存较小的情形下,最终taosd会出现OOM 。
问题定位
碰到这种疑似内存泄漏的问题之际,首先应当先用工具去运行,在运用常用工具Valgrind、Address sanitizer进行尝试过后,结果均报告不存在内存泄漏,这样的状况在之前的2.x版本也曾出现过,那时研发人员怀疑glibc的内存管理器存在问题(并非完善),进而切换到jemalloc或者tcmalloc,然而究竟是不是真的由于glibc存在BUG或者有内存空洞问题所导致的呢?我们需要找寻证据。
问题分析
动手开始之前,我们得先弄明白概念,究竟啥是内存泄漏呢?我们都清楚内存泄漏最大害处在于致使程序最终出现OOM,在此之前能够观察到的现象乃是进程内存使用量持续上升,就问是不是只要进程OOM或者内存持续上涨就是存在内存泄漏呢?并非如此。换言之而言,不能再被使用下去的内存倘若未被释放,这就必定会造成内存持续不断地上升直至出现 OOM 的状况,不过并非仅仅只有内存泄漏才会引发内存持续上涨以及出现 OOM 的情况,像上述讲到的内存空洞的相关问题或者缓存同样会致使相同的结果后果出现。因而确切地讲,上述所提及的 issue 所遭遇面临处理的是内存持续上涨或者 OOM 的问题难题,并不一定绝对肯定就是内存泄漏。然而不管是由哪一种情形状况所造成导致的,其后果结果都是很严重严峻的,研发工作人员人员都需要去寻找到找出问题并将其解决处理掉它。
持续致使内存上涨的常见问题包含内存泄漏、内存空洞以及缓存这三类,我们通常所使用的Valgrind、Address sanitizer能够察觉并且解决的仅仅是内存泄漏问题,然而对于内存空洞以及缓存问题而言却没办法进行检测,这便是为何在很多情形下会涌现内存在增长但工具却检测不出问题的状况出现。但要去说服用户这属于空洞问题并非那么简单容易,纯粹的内存空洞问题一般仅仅会致使内存占用多的状况发生,空洞的那部分是能够用来重复利用的,这意味着通常不会引发内存持续增长的问题,只是在一些极端的使用场景当中有可能会出现持续增长这种情况。要是工具可靠并且能够将内存空洞问题排除掉,那么大概率就是缓存问题了,然而 taosd 在单个查询重复执行的场景之下又不存在明显的缓存问题。理论分析再度陷入困境,我们需要一种能够发现并且解决这三类问题的方法以及工具。
虽是三类问题,然而这些问题存在着共同点,这个共同点便是,这些问题都是由于内存的分配以及释放而导致的,要是能够寻觅到并且记录下每一个内存分配以及释放的点,那么就能够分析属于哪一种状况了:
已经有了思路之后,接下来要思考的便是如何去实现,核心问题在于怎样找到并且记录下每个内存分配以及释放的点,开发代码能够记录每一个 taosd 自身的内存分配与释放,然而开发工作量不小,短时间内很难完成,更为关键的原因是 taosd 的进程空间里,除了我们自行开发的代码之外,还有第三方库,当中包含 glibc 的代码,虽说出问题的概率比较小,可是要是我们的使用方式存在问题,也是有出问题的可能性的,这些代码里出现的问题该如何处理呢?我的答案是朝着下方去寻找接口,也就是在系统进行调用的那种层面之上捕获住内存的被分配以及被释放这两种情况 。
背景知识
定位步骤
有一个变量ret,它被定义为整型,此ret被赋值为mallopt函数的返回值,该mallopt函数的参数是M_MMAP_THRESHOLD以及0。
if (0 == ret) {
}
运行strace程序,带有 -TttFf参数,设置 -e write参数的值为0,1,2,3,通过 `pidof taosd`命令获取进程ID并附加到 -p参数后发起追踪,最后将输出导向至strace_log.txt文件 。
1230673 12:56:10.273506 <... futex resumed>) = 0 <0.001681>
1230741,在 12:56:10.273535 这个时刻,进行了 write(3) 这个操作,写入的内容是 "01/13 12:56:10.273516 01230741 Q"。
将1230673,在12:56:10秒并且273547微秒时,对名为futex中的0x7ff766f4d01c利用FUTEX_WAIT_BITSET_PRIVATE以及FUTEX_CLOCK_REALTIME这种方式,等待值为3,在无属性列表为空的情况下,依据FUTEX_BITSET_MATCH_ANY的规则来操作,。
1230741 12:56:10.273566 <... write resumed>) = 129 <0.000022>
此内容似乎并非一个完整可改写的句子形式呀,请你提供更合适的句子以便进行改写。|0000030312f31332031323a35363a31302。
这里有一串字符,分别是竖线,00010,37,33,35,31,36,20,30,31,32,33,30,37,34,31,20,51,73516,01230741。
有一串字符,分别是00020 ,52 ,59 ,20 ,51 ,49 ,44。接着是3a ,30 。然后是78 ,65 ,33 ,39 ,37 ,66 ,65 ,37。
有这样一串字符,其中包含数字00030,还有63,33,65 ,30,38 ,(停顿过长)38,36 ,63 ,30 ,2c ,54 49 44 \u3000 \u30003。
零,零零零四零,六三,三三,三二,三四,二c,四五,四九,四四,三a,三零,七四,六一,七三,六b,二零,c三二四,EID为零,任务,。
这似乎并不是一个正常的可理解的句子内容呀,你可以检查一下并给我提供准确清晰的内容以便我按照要求改写 。
将从“EXECUTING”转变为,00060,72,6f,6d,20,45,58,45,43,55,54,49,4e,47,20,74,6f, 。
存在这样一种情况,有一串字符序列,其中包含特定组合,有一个数字编号为00070,接着是20、50、41、52、54、49、41、4c、5f、53、55、43、43、45、45、44,之后出现。
不太明确你提供的这个内容是以何种目的让改写,你可以补充更明确的要求或说明,以便我能更准确地按照规则进行改写 。
一二三一七四一,十二点五十六分十秒二七三六零三,福泰克斯(零点七ff七六六f四d零一c,富泰克斯叫醒私密地带,一)等于一 。<0.000027>
1230749 12:56:10.273644 <... futex resumed>) = 0 <0.001744>
1230741,12点56分10秒273655,进行mmap操作,参数为NULL,4096,PROT_READ与PROT_WRITE,MAP_PRIVATE和MAP_ANONYMOUS,-1,0 ,这里是一个逗号,这里又是一个逗号,这里还是一个逗号,这里同样是一个逗号,这里依旧是一个逗号,这里仍然是一个逗号,这里。
在时刻 3,于 1230749 的 12:56:10.273669 进行书写,所写内容为“01/13 12:56:10.271877 01230749 U”等,字数。
1230741 12:56:10.273681 <... mmap resumed>) = 0x7ff50f4c8000 <0.000020>
对"strace_log.txt"文件,用"egrep"命令查找包含"mmap"或者"mremap"的内容,接着用"grep"命令排除包含"unfinished"的行,再用"awk"命令以"="为分隔符提取第二列内容,然后又用"awk"命令提取第一列内容,最后将结果输出到"map.txt"文件 。
grep -v resumed,通过egrep "munmap|mremap" 对strace_log.txt进行查找,使用awk -F "("'{print $2}',再用awk -F ","'{print $1}',最后输出到unmap.txt 。
说明
#include "stdlib.h"
#include "stdio.h"
#include
#include
#include
char in1[16] = {0};
char, in2, 500乘以1048576的结果,有16个元素,初始化为0,这样的。
main()
{
FILE* fd1,它等于 fopen,参数是 "map.txt" 以及 "r",用于打开文件 。
FILE*这个玩意儿,被命名为fd2,它通过fopen这个动作,以"unmap.txt"作为文件名,并且是以"r"这种读取的方式。
int i等于0,n等于0,found等于0,m等于0,minIdx等于0,non0等于0 。

处于这样一种情况:当通过fgets函数,依据in2数组中首个元素的大小规模,从文件描述符fd2中读取内容到in2中数组下标为i的位置时,如果所读取的内容并不为空指针。
{
要是(处于这种情况:当in2[i]里的第14个字符等于'\\n'时),那么 ,。
in2[i][14] = 0;
}
i++;
}
打印,百分号,整数格式,记录数量,从未映射文本文件中读取,换行,变量i的值,句号。
当从文件描述符fd1读取数据到in1中,读取的字节数为in1的大小,且结果不为空指针时,执行后续操作 。
{
if (in1[14] = '\n') {
in1[14] = 0;
}
m++;
non0 = 0;
for(n=minIdx;n=100)
// break;
}
if (m > (minIdx+10000)) {
minIdx++;
}
}
}
定位结果
通过使用上面介绍的方法,我们最终定位到了两个问题:
atexit(cleanupRefPool);
阐释:我们于进行每个查询子任务创建之际,皆径直调用了上面所述的那个语句,它会在每一次缓存一个函数地址,最终于进程退出之时又将全部予以释放,所以并不属于内存泄漏,Valgrind以及Address sanitizer均检测不出,这便是致使查询内存不断增长的缘由。
总结与后续
自3.0.0.0版本起存在那个“内存泄漏”问题,任何查询都有此问题,直至3.0.2.5版本出现后文可称taosd终于没“内存泄漏”问题了。本文借助一种无需额外代码开发的办法,在传统内存泄漏检测工具能力范围外,一站式定位解决进程内存占用持续增长或OOM问题,使彻底解决这类问题成为可能,。此外,针对这一类问题,当下TDengine OSS已在taosd/taosc增添了在线开闭内存调试模式,能够随时于现场定位内存增长方面的问题,无需安装工具,也无需编译ASAN版本,特别适宜用于解决Valgrind/ASAN发觉不了的内存增长问题。