问题背景
很多朋友在做采集的时候会用到Telegraf,它工作时首先会加载配置文件。 在部署联调时突然发现由于Telegraf加载配置文件时物理内存瞬时升至6G以上,由于Go的垃圾回收机制,读取配置信息后,不会及时释放掉内存,得等待GC时才可以根据GC策略逐步释放。部署生产环境时一台物理机或虚机上可能会部署几个服务。那么Telegraf读取配置文件就瞬时的吃掉了这么多内存,给整体资源带来了不必要的麻烦。故,需要想一个办法把这个问题解决掉。
思考过程
-
思路
-
问题是否可复现
通过实际操作,的确发现这个问题是容易复现的。只要运行Telegraf,刚开始的阶段问题就出现了。
-
客观佐证
遇到这种问题的时候,首先往往需要先验证到底是否我们最初的判断有问题。比如我们怀疑了是读取配置文件环节发生的问题,那么就要从客观使用一技术方法来佐证我们的怀疑点。
-
解决思路
遇到问题不难,解决思路可能也有很多种,难点在于哪种解法更适合我们当前的资源?(技术可行性、影响范围、项目进度、工作量、交付价值)这些问题是需要和团队一起思考、共识的。
-
-
方法
1、手动在Telegraf读配置文件程序的前、后分别植入内存监控程序,观察读前、读后的内存使用情况和增长量。目的是通过这样的技术手段来佐证我们最初的观点。
2、针对该问题点,进行技术攻关,实现一个Demo版本的解法,在本地可跑通。
3、把Demo版本的解法与团队进行共识,从解法思路、技术实现、交付价值来一起评审Demo的价值。
4、把Demo版本的解法同步集成到Telegraf项目中来,观察是否真正解决了问题。
-
共识
以上的思考模式需要和团队负责人进行充分沟通,反复推敲打磨思路,当达成共识的时候,即可开展后续工作。
具体排查
-
观察程序执行步骤
1、启动时,Telegraf会调用LoadConfig()加载配置文件。

LoadConfig中有2个关键动作:
a、通过loadConfig()方法,把配置文件通过ioutil类读进来,转换为byte[]数组。

b、调用generateConfig()方法,生成Config配置对象。那么下面我详细给大家剖析下这个方法:)
2、generateConfig()方法内部的实现逻辑是相对稍微复杂的。咱们一步一步来。
a、首先会调用pasreConfig()方法解析配置文件的流,返回ast.Table对象。我们来看一下ast.Table的结构。


b、第二个关键步骤是循环遍历Table对象,取出Field中的键值对,然后通过key标签再拿到val,这里的val也是Table对象,叫做subTable。这一步如果文件大的话会比较耗时,吃内存。那么为什么吃内存呢?我们先看一下这里的 toml.UnmarshalTable()方法内部实现。


c、这里会对subTable继续进行解析,大量引用反射一个一个属性进行构造,那么如果subTable本身属性值非常多的话,假设subTable是n个属性,假设解析方法的单位时间是O(1),那么解析单个subTable所需要的时间复杂度就是O(n)。
d、我们再往上层看,上层对table对象进行遍历,每个元素都会生成一个subTable。那么假设table对象有m个subTable,最终的这个环节的时间复杂度就是O(m*n)。如果table对象非常大(配置文件)的话,这里复杂度可想而知:)不过这个只是第一个步骤。
e、如果我们分析到了这里,不难想象,这里的复杂度,是与table对象大小紧密相关的,也就是m系数。我在这里先抛一个砖:)
f、generateConfig()方法中的第二个核心步骤,请跟着我继续往下看。

g、在这里,又是一个循环,这个循环体内部通过代码观察不难发现,这里是把刚刚解析好的subTable中的Field取出来,然后正式的添加到telegraf的input插件中。想添加到插件也是需要进一步的解析才能完成。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPof0Qm9-1615015471108)(/Users/Squall/Library/Application Support/typora-user-images/image-20210306120443570.png)]](/https://i-blog.csdnimg.cn/blog_migrate/520f430585a32de69442d385f362a454.png)
h、在这里的时间复杂度大家都能看出来了吧:)这里我就不再赘述了。业务逻辑简单说就是循环+属性解析,那么这里的时间复杂度也是与subTable的属性数量多少有线性关系的。
i、ok,到此为止,Telegraf的启动时加载配置文件的关键步骤我们已经探索过了。
-
识别问题
经过第一轮的排查,得出了结论
a、加载配置文件的时间复杂度和配置文件中的标签数量是有关的。
b、大体的解析流程为
读configFile -> bytes[]字节流转换 -> Table -> SubTable -> addInput()

c、配置文件的解析过程相对繁琐
-
佐证观点
a、尝试对配置文件加载前后分别植入内存监控程序,观察读前、读后的内存使用情况和增长量,结果如下:

本机环境(mac i5 8g)下观察得到的内存增长量,不难发现已经达到了惊人的7个G
b、基于以上的技术探索和手动植入监控程序,我们不难发现如果Telegraf配置文件标签内容较多的话,那么其加载时的解析、转换等运算量是阶乘式的增长。内存增长也就不言而喻。
改造思路
-
将大配置文件按照一定容量进行切割,尝试循环逐个加载。观察CPU、内存占用量。 -
改造点
通过阅读框架源码,发现了Telegraf加载的时候是可以通过目录加载的。下图中,左侧的conf文件是做过切片后的toml配置文件,我这里暂定每个切片大小为10M。右侧是直接使用Telegraf提供的LoadDirectory()方法,批量载入配置文件,循环读取加载。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uARjwM3x-1615015471111)(/Users/Squall/Library/Application Support/typora-user-images/image-20210306144912164.png)]](/https://i-blog.csdnimg.cn/blog_migrate/7e8723c8165c833d869de28838d65fbb.png)
改造验证
- 单切片标签数量

- 单切片的subTables结构

最终,通过集成测试,本机环境测试(mac i5 8g),内存占用量的确从7G+ 降低到了1G-。

-
总结
这种场景是比较典型的由于量变引起质变的情况。
Telegraf解析配置文件的流程相对复杂,在解析的时候需要对配置文件中的每层中的每个属性都要进行逐层逐个顺序解析,底层实现大量运用了循环和反射。故配置文件太大的话(属性量上亿级),如此大规模的顺序解析动作就会持续占用CPU和内存,导致系统瞬时卡死甚至出现雪崩。从Telegraf的底层设计来尝试推测,也是不希望一次性加载大配置文件的。像这种场景,我们不妨可以尝试把大的配置文件拆分成小的文件分片来进行逐个加载。
本文探讨了Telegraf在加载大型配置文件时内存飙升的问题,通过内存监控和源码分析揭示了解决方案,包括配置文件切片加载和性能优化策略。

3096

被折叠的 条评论
为什么被折叠?



