前言:任何一家互联网公司都需要日志采集器,在微服务集群达到上万台机器规模的时候,如何做到日志采集可扩展、高性能、低资源占用率变的更加有挑战性。
日志采集开源解决方案
日志数据源端大致可以分为三类:
- 普通的文本文件
- 网络接收到的日志数据
- 共享内存的方式
本文只会谈及第一类,一个文件日志采集Agent最为核心的功能大致就是这个样子了。
①监听文件->②读取文件->③文本处理->④组装批次->⑤发送给存储
在这个基础上进一步又可以引入日志过滤、日志格式化、路由等功能,看起来就好像是一个生产车间。目前有比较多的开源日志解析器,可以从便利性、性能、生态几个角度对比流行的几个产品。
名称 | 便利性 | 性能 | 生态 |
---|---|---|---|
logstash | 包含input、filter、output三个配置,比较灵活,最新版本可以使用filebeat作为输入,Filebeat 也会和 Logstash 一样记住上次读取的偏移 | 3000qps+,主要是比较耗内存在1G左右 | 官方一直在维护,性能在提升,但是定制化不是很便利 |
Apache Flume | 包含source、channel(可以配置interceptor清洗数据)、sink,配置比较灵活,可以自定义扩展,通过file channel记录位点 | 最多每分钟处理400M,高性能模式cpu消耗大 | 属于Hadoop生态,Flume 1.x 更新频率高 |
scribe | 设计简单,包含scribe agent、scribe、存储系统,当存储系统出问题会写本地容错 | 基于thrift高吞吐 | 资料很少,没有维护了 |
fluentd | 云原生、star数10k+,统一了日志格式,插件丰富、自带web ui控制台 | 使用Ruby语言,由于它的内存占用很小(30~40MB),您可以按比例节省大量内存 | 生态很活跃 |
logkit | 七牛云出品、基本的数据发送功能,logkit还有容错、并发、监控、删除等功能,提供Web支持、插件式架构,集成了多种类型的Readers、Senders | GO 语言编写,性能优良,资源消耗低,跨平台支持 | 社区活跃度一般,主要是官方维护 |
Vector | 包含Sources、Transforms、Sinks组件,端到端的日志收集平台,文档非常丰富,操作简单,使用lua脚本可以自定义转换,动态重新加载配置 | 性能之王76.7mib/s | 社区非常活跃,支持度高,使用的公司也比较多 |
LogAgent | 目前只支持File收集,部署支持ECS、容器DaemonSet模式、可以动态统一配置所有agent | 单线程20M/s | – |
单看性能的话,Vector无疑是当之无愧的冠军,所以官方也特别列出了性能对比表格,这里引用一下:
Test | Vector | Filebeat | FluentBit | FluentD | Logstash | SplunkUF | SplunkHF |
---|---|---|---|---|---|---|---|
TCP to Blackhole | 86mib/s | n/a | 64.4mib/s | 27.7mib/s | 40.6mib/s | n/a | n/a |
File to TCP | 76.7mib/s | 7.8mib/s | 35mib/s | 26.1mib/s | 3.1mib/s | 40.1mib/s | 39mib/s |
Regex Parsing | 13.2mib/s | n/a | 20.5mib/s | 2.6mib/s | 4.6mib/s | n/a | 7.8mib/s |
TCP to HTTP | 26.7mib/s | n/a | 19.6mib/s | <1mib/s | 2.7mib/s | n/a | n/a |
TCP to TCP | 69.9mib/s | 5mib/s | 67.1mib/s | 3.9mib/s | 10mib/s | 70.4mib/s | 7.6mib/s |
LogAgent实践
从灵活性、性能、可靠性方面出发,哈啰选择类似阿里云LogTail插件的模式部署agent程序收集日志。本章节从几个方面出发详细说明LogAgent设计思路以及遇到的一些技术挑战。
如何发现新文件
对于实现一个日志采集器,第一步就是要实现文件监听功能,有新产生的文件都获取到事件读取日志文件。那么有哪些方法可以获取文件变动呢?
如果是Linux系统通过Inotify实现,Inotify是一种文件变化通知机制,Linux内核从2.6.13开始引入。在BSD和Mac OS系统中比较有名的是kqueue,它可以高效地实时跟踪Linux文件系统的变化。但是不要高兴太早,Inotify只能监听单层目录变化,不能监听子目录的变化,而且也有可能一些异常情况导致事件触发失败,如果是嵌套目录,则需要递归监听子目录,成本也比较高,如果目录被删除了,事件也失败了。那么除了通过Inotify还有什么方法监听发现新文件,最笨的方法就是轮询目录,需要消耗cpu如果开启的线程比较多的话,cpu占用会比较高,理想的话设置一个睡眠时间,接收一定时间的延迟,对于日志系统来说,新文件有几秒钟的延迟还是可以接收的,目前配置的是2s。在采集日志的时候,通过inode判断是否是同一个文件,一个文件对应一个文件内容线程监听滚动,如果文件滚动了,则会停掉文件内容读取线程。
对比两种方式各自优缺点:
名称 | 优点 | 缺点 |
---|---|---|
Inotify | 高效,实时性很好 | 只有linux有这个功能,不能保证100%不丢事件,嵌套目录事件风暴问题 |
目录轮询 | 可以保证不会漏掉文件 | 实时性不高、浪费轮询线程cpu |
因此通过结合轮询和Inotify可以相互取长补短。
目前LogAgent使用了最简单的目录轮询。
点位做到高可用
点位就是记录当前采集到了哪一行日志了,如果agent采集程序重启可以做到继续采集,不丢日志。
点位一般是记录到offset文件,怎么保证记录offset文件记录的原子性,可以使用Linux的rename原子性,操作步骤如下:
- 将点位数据写入到磁盘的offset.bak文件中
- fdatasync确保数据写入到磁盘
- 通过 rename 系统调用将offset.bak更名为 offset
offset的一般不保存文件名称,因为文件滚动、重名、删除等都会导致一个文件不同时刻指向的不是一个文件,Linux内核提供了inode可以作为文件的标识信息,而且保证同一时刻Inode是不会重复的,在点位文件中记录文件的inode和采集的位置即可。
如果不考虑多系统、多文件分区格式,保存二元组【inode、offset】即可。
如何获取inode:
Files.getAttribute(file.toPath(), "unix:ino")
inode有个问题,只能保证同一时刻不重复,如果是一个文件删除了,立马新建一个文件可能inode会重复,如果要严格保证唯一可以考虑添加文件扩展属性xattr的信息。
目前LogAgent做法是从最新点位收集,重启LogAgent会丢日志。
如何读取文件新增内容
最简单通用的方案就是轮询去查询要采集文件的stat信息,发现文件内容有更新就采集,采集完成后再触发下一次的轮询,既简单又通用。因为我们使用inode记录文件,就算文件被删除了,我们还是可以继续读取文件内容,Linux中的文件是有引用计数的,已经打开的文件即使被删除也只是引用计数减1,只要有进程引用就可以继续读内容的,所以日志采集Agent可以安心的继续把日志读完,然后释放文件的fd,让系统真正的删除文件。
但是如何知道采集完了呢?
废话,上面不是说了采集到文件末尾就是采集完了啊,可是如果此刻还有另外一个进程也打开了这个文件,在你采集完所有内容后又追加了一段内容进去,而你此时已经释放了fd了,在文件系统上这个文件已经不在了,再也没办法通过文件发现找到这个文件,打开并读取数据了,这该怎么办? 要么设置一个多久没有写就释放文件句柄,一种是轮询文件目录发现文件不存在了,既可释放文件句柄。
目前LogAgent的做法是一直读取内容,读取不到内容sleep 200ms防止抢占cpu,一直到文件被删除。
如何安全的释放文件句柄
目前做法是需要SRE的清理脚本配合处理,清理脚本会删除7天以外的日志,所以7天以外的日志会安全的释放文件句柄,同文件名称,因为是不同的inode也会自动释放句柄。
如果出现文件句柄数不断增大,可以通过命令获取:
扫描指定进程的文件句柄:
$ sudo ls -al /proc/22686/fd |grep log l-wx------ 1 deploy deploy 64 Apr 20 19:46 1 -> /workspace/carkey/AppLogAgent/logs/jvm_std.log lr-x------ 1 deploy deploy 64 Apr 20 19:46 159 -> /workspace/carkey/?/logs/soa-event.log lr-x------ 1 deploy deploy 64 Apr 20 19:46 160 -> /workspace/carkey/?/logs/soa-error.log ...
获取一个文件所有引用的进程号:
$ lsof /workspace/carkey/?/logs/soa-server-detail.log COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 7996 deploy 391r REG 253,1 40559401 1573185 /workspace/carkey/?/logs/soa-server-detail.log java 27491 deploy 343w REG 253,1 40559401 1573185 /workspace/carkey/?/logs/soa-server-detail.log
容器环境下如何采集日志
容器环境下,采集日志有几个问题需要在设计上注意:
- 容器pod销毁如何保证不漏日志
- 如何保证日志采集性能,并减少资源占用
- 如何清理日志
为了解决上面三个问题,LogAgent部署在DaemonSet中(目前规划是一个物理机最多部署20个pod),相对sidecar,这样性能高也不占用pod的资源,应用使用 HostPath 挂载日志目录,也避免了容器销毁采集不到日志的问题,LogAgent做了一些适配 Kubernetes 的开发,通过docker接口获取当前所有pod的日志目录,删除1天过期且销毁的pod目录。
总结
分析上面几个问题,想做到完美采集日志还是挑战很大,这里涉及到文件系统、Linux相关知识等,未来LogAgent的发展有以下几个点可以再深挖:
- 搭建LogAgent监控系统,避免假死、cpu占用过高盲点
- 添加日志埋点和告警,在源头收集埋点数据
- 更高的性能和压缩率
留言