记一次日志采集链路的验证及优化
搭建基础环境
日志源: 测试用例模拟
生成服务器flume:本机启动
kafka: 运维提供的测试环境的kafka
flume集群:本机启动
es:运维提供的测试环境的es
验证配置文件发生变更是否重新读取
- 配置文件发生变更,是否重新加载?
验证结果:会重新加载,它启动了一个定时任务,每隔一定的时间(默认30),获取文件的最后修改时间。
如果要修改配置文件,一定要在原文件上修改,不要rm之后,再创建同名新文件。
传输过程可靠性验证
生产服务器上的flume组件配置为 : TailDirSource --> FileChannel --> KafkaSink
消费者组件: kafkaSource --> FileChannel --> ESSink
TailDirSource
主要涉及到三个线程:
FileCheck:检查文件更新时间以及新创建的文件
Reader : read():读取日志 – > process():交付channel --> commit():更新已经读取的文件偏移量
PositionWrite : 定时任务,负责把已经读取的文件的偏移量持久化到磁盘
flume重新启动的时候,只要没有设置skipToEnd,就会通过持久化到磁盘的已读文件信息,断点续传。
但是,Reader 和 PositionWrite 之间并没有同步机制,commit仅仅是修改内存,不会主动调用PositionWrite刷新到磁盘,所以理论上:TailDirSource 可以做到不漏数据,但无法做到不重复。
FileChannel
在MemberChannel的基础上,增加了写入日志文件的步骤(NIO的channel顺序写)。写入成功之后返回文件编号以及偏移量,放入内存队列中,take操作也会记录日志。如果意外挂掉,重新起来时会根据日志进行回放。
KafkaSink/kafkaSource/ESSink
基本是都是用了官方的sdk
正常情况下的实验:
模拟程序通过logback写入100W条日志。
结果:日志最终能够正常流入es,并且没有发生丢失和重复的情况。不过es的延迟是一个陡峭的上升曲线(如下图),说明日志堆积很严重。用户此时可能查询不到最新日志。
flume-taildir读取一批(50条)日志的耗时在1-2毫秒之间
kafka的性能也很好,基本是没感受到压力。
ES的写入性能是最终的瓶颈,观察es的机器(3cpu),发现load已经到10了
模拟程序 | flume-taildir | kafka-sink | kafka-source | es-sink | |
---|---|---|---|---|---|
耗时(秒) | 91 | 53 | 79 | 71 | 12.8分钟 |
延迟(毫秒) | 0 | 500 | 未采集 | 1200 | 10.5分钟 |
模拟消费者组件(kafkaSource --> FileChannel --> ESSink)挂掉重启的情况
总共kill然后重启消费者组件两次,最后收集到的日志数量多了1000条,而ESSink设置的批次量正好是500. 显然,幂等性要自己实现。
通过生成UID作为es的id实现幂等
ES支持索引的时候指定ID,如果根据每条日志的特征,生成唯一ID,就不会存在数据重复现象。
UID = ip(最后一节) + inode(linux文件标识) + dd(月份中的天) + pos(字节偏移量)
UID的生成放在flume读取日志的taildir模块
经过多次试验,采用自生成的uid作为key,是可以避免数据重复的。
通过下图的表格中的数据也可以看出,使用自生成的uid,写入es的性能会下降6%左右,这是因为ES需要确认这个id是否存在。
模拟程序 | flume-taildir | kafka-sink | kafka-source | es-sink |
---|---|---|---|---|
91 | 55 | 75 | 69 | 13.5分钟 |
0 | 500 | 未采集 | 1119 | 11.9分钟 |
日志传输过程中实现去重(没能实现)
ES写入作为性能最终的瓶颈,再把去重工作交给它,整个链路的性能会再次下降。有没有办法在传输过程中实现去重呢?布隆过滤器:牺牲一定的最准确redis存储键值对:功能上能够实现,但是这样会占用大量的内存空间(key为string,value为int)
redis 的bitmap能够在性能和内存占用上满足需求,不过如果采用字节偏移量作为bitmap的offset,也会存在大量的内存浪费。所以我们应该采用类似于行号这种连续数据,并且一定要随着flume的position文件保存到磁盘,下次启动时能够连续。
这个方案最终没能实现,问题的关键是去重操作只能放在ESSink模块中,即使提高了ES写入的性能,数据准备阶段耗时增加了,也没有任何意义。
模拟生产者组件(TailDirSource --> FileChannel --> KafkaSink)挂掉重启的情况
有点出乎意料,居然还是发生了数据丢失现象,而且只读取到了649297,丢失了2/5。利用kafka数据回放的能力,换了一个groupID 进行消费,依然只读取到了649297,可以确实是生产端没有投递。
丢失的数据太多,不太可能是交付的时候没有成功就被kill掉引起的,比较怀疑是日志文件滚动引起的。首先把日志文件调大,不让它滚动进行验证。
可以看到投递了1019040条记录,而ES中之索引了1000000条,重复的日志已经去掉。
还有一个滚动日志的坑,最后发现是因为我配置的flume收集只指定了plugin.log,滚动生成的日志没有覆盖,配成plugin.*就好了。又想到了另一个问题,logback支持gz,这种情况下会出问题吗?试了下果然有坑。。。。这个后面在解决,先知道就好
ES写入性能优化
__refresh_interval __
新索引的数据,不会被立即查询到,只有等把内存中的数据,刷新到磁盘,建立一个新的segment,才能被搜索到该。该属性就是控制刷新到磁盘的频率,默认为1s,增大该属性可以有效降低IO,并且减少后期合并segment的压力。
修改为10s,写入性能显著提升,最终只用了七分钟,而且es的写入延迟也只有五分钟左右了。显然,这个值设的越大,es的写入性能就会越高,最终设为多少,等到了生产环境观察ES的写入延迟,只要小于写入延迟就是合理的。
JVM
es默认的jvm内存为1G,增加到3G,测试之后写入性能几乎没有提升,估计在查询性能上会有比较明显的提升吧,比较多了很多内存作为cache。
LoadBalancing Sink Processor
使用SinkProcessor配置两个个sink,多线程写入,最终时间为3.5分钟,延迟也只有3分钟了,显然写入时间还没有优化到极限,等到生成环境在测试最佳的写入线程比例。
待验证特性
Kafka Channel : flume支持使用kafka作为channel,这样比kafka作为sink减少了一步,理论上性能更高,待验证
Kafka降级: 当kafka出现故障时,是否选择跳过kafka,直接放给flume处理集群?有没有机制保障不引发雪崩?
ES索引创建策略: 当前每个应用每天都会创建一个新的索引,观察线上发现有的索引很大(40G),有的很小只有几KB。很显然这并不合理,创建过多的小索引对性能也有影响,所以后面的索引创建逻辑打算改成:
先查看下之前索引大小—>大于20G,则创建新的 —>小于20G,则只创建新的别名指向之前的。
创建别名是为了方便业务开发查看日志