文章采集组合工具( 一个日志采集的Agent简单来看其实(组图)数据 )
优采云 发布时间: 2021-12-25 15:01文章采集组合工具(
一个日志采集的Agent简单来看其实(组图)数据
)
概述
日志已经从面向人类演变为面向机器。一开始,日志的主要消费者是软件工程师,他们通过阅读日志来排查问题。如今,大量机器日夜处理日志数据,生成可读的报告,帮助人类做出决策。在这个过渡过程中,日志采集
代理扮演着重要的角色。
作为日志采集
代理,简单来说,它其实就是一个将数据从源头传送到目的地的程序。通常目的地是具有数据订阅功能的集中式存储。这样做的目的其实是为了分析和存储日志。耦合,同一个日志可能对不同的消费者感兴趣,获取日志后的处理方式也会有所不同。将数据存储和数据分析解耦后,不同的消费者可以订阅自己感兴趣的Log,选择对应的分析工具进行分析。这种具有数据订阅功能的集中式存储在业界比较流行的是Kafka,对应阿里巴巴内部的DataHub和阿里云的LogHub。数据源大致可以分为三类,一类是普通文本文件,另一个是通过网络接收的日志数据,最后一个是通过共享内存。本文将只谈第一类。. 一个日志采集
代理的核心功能大致是这样的。在此基础上,可以引入日志过滤、日志格式化、路由等功能,看起来像一个生产车间。从日志交付的角度来看,日志采集可以分为推送模式和拉取模式。本文主要分析推送模式的日志采集
。从日志交付的角度来看,日志采集可以分为推送模式和拉取模式。本文主要分析推送模式的日志采集
。从日志交付的角度来看,日志采集可以分为推送模式和拉取模式。本文主要分析推送模式的日志采集
。
推模式是指日志采集代理主动从源端获取数据并发送给目的地,拉模式是指目的地主动从日志采集代理获取源数据。
行业现状
目前业界最流行的日志采集主要有Fluentd、Logstash、Flume、scribe等,阿里巴巴内部是LogAgent,阿里云是LogTail。在这些产品中,Fluentd占据绝对优势,成功进入CNCF阵营。它提出了统一的日志层(Unified Logging Layer),大大降低了整个日志采集
和分析的复杂度。Fluentd 认为,现有的日志格式大多结构薄弱,这要归功于人类对日志数据的出色解析能力,因为日志数据本来就是以人为导向的,而人类是日志数据的主要消费者。为此,Fluentd 希望通过统一日志存储格式来降低整个日志采集
和访问的复杂度。假设输入的日志数据有M种格式,例如,而日志采集代理后端连接N种存储,那么每个存储系统都需要实现解析M种日志格式的功能。总复杂度为 M*N。如果日志采集
代理统一了日志格式,总复杂度就变成了M+N。这是Fluentd的核心思想,其插件机制也是值得称赞的地方。Logstash 和 Fluentd 类似于 ELK 技术栈,在业界也有广泛的应用。两者的对比可以参考这篇Fluentd vs. Logstash:日志采集
器的比较:这是Fluentd的核心思想,其插件机制也是值得称赞的地方。Logstash 和 Fluentd 类似于 ELK 技术栈,在业界也有广泛的应用。两者的对比可以参考这篇Fluentd vs. Logstash:日志采集
器的比较:这是Fluentd的核心思想,其插件机制也是值得称赞的地方。Logstash 和 Fluentd 类似于 ELK 技术栈,在业界也有广泛的应用。两者的对比可以参考这篇Fluentd vs. Logstash:日志采集
器的比较:
从头开始写一个日志采集
代理
作为一个日志采集
代理,在大多数人的眼里,它可能是一个数据“搬运工”,他们经常抱怨这个“搬运工”占用了太多的机器资源。简单的说就是tail -f命令,比较合适。,对应Fluentd中的in_tail插件。作为亲身实践过日志采集代理的开发者,希望通过本文来普及一下日志采集代理开发过程中的一些技术难题。为了让整篇文章连贯,作者试图通过“从头写一个日志采集
代理”的主题来描述整个开发过程中遇到的问题。
如何查找文件?
当我们开始编写日志采集
代理时,遇到的第一个问题就是如何查找文件。最简单的方法是用户直接在配置文件中列出需要采集的文件,然后日志采集代理读取配置文件。找到要采集
的文件列表,最后打开这些文件进行采集
。这可能是最简单的。但是,在大多数情况下,日志是动态生成的,并且会在日志采集
过程中动态创建。提前在配置文件中列出来太麻烦了。一般情况下,用户只需要配置日志采集目录和文件名匹配规则即可。比如Nginx日志放在/var/www/log目录下,日志文件名是access.log,access.log -2018-01-10..... 与这种形式类似,为了描述此类文件,可以使用通配符或正则表达式来匹配此类文件。例如:access.log(-[0-9]{4}-[0 -9]{2}-[0-9]{2})? 有了这样的描述规则,日志采集代理就可以知道哪些文件需要采集,哪些文件不需要采集。您将遇到的下一个问题是如何发现新创建的日志文件?定期轮询目录可能是一个不错的方式,但是轮询周期太长,实时性不够,太短又会消耗CPU。我不希望你的采集
Agent被别人占用太多CPU。日志采集
代理可以知道哪些文件需要采集
,哪些文件不需要采集
。您将遇到的下一个问题是如何发现新创建的日志文件?定期轮询目录可能是一个不错的方式,但是轮询周期太长,实时性不够,太短又会消耗CPU。我不希望你的采集
Agent被别人占用太多CPU。日志采集
代理可以知道哪些文件需要采集
,哪些文件不需要采集
。您将遇到的下一个问题是如何发现新创建的日志文件?定期轮询目录可能是一个不错的方式,但是轮询周期太长,实时性不够,太短又会消耗CPU。我不希望你的采集
Agent被别人占用太多CPU。
Linux 内核为我们提供了高效的 Inotify 机制。内核监控目录中文件的变化,然后通过事件通知用户。但不要高兴得太早。Inotify 没有我们想象的那么好。它有一些问题。首先,并非所有文件系统都支持 Inotify。此外,它不支持递归目录监控。比如我们监控A目录,但是如果在A目录下创建了B目录,然后马上创建了C文件,那么我们只能得到B目录创建的事件,C文件创建的事件会丢失,最终导致文件不被发现和采集
。Inotify 也与现有文件无关。Inotify 只能实时发现新创建的文件。Inotify 联机帮助页描述了有关 Inotify 的限制和错误的更多信息。如果要保证不漏过采集,最好的方案就是Inotify+polling的组合。更大的轮询周期用于检测丢失的文件和历史文件,并使用Inotify确保在大多数情况下可以实时发现新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集
代理可以找到该文件,接下来我们需要打开该文件,然后进行采集
。然而,也有意想不到的情况。机器在我们采集
期间坠毁。我们如何确保采集
到的数据不再采集
,并且可以在上次未采集
的地方继续?最好的解决方案是 Inotify+polling 的组合。更大的轮询周期用于检测丢失的文件和历史文件,并使用Inotify确保在大多数情况下可以实时发现新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集
代理可以找到该文件,接下来我们需要打开该文件,然后进行采集
。然而,也有意想不到的情况。机器在我们采集
期间坠毁。我们如何确保采集
到的数据不再采集
,并且可以在上次未采集
的地方继续?最好的解决方案是 Inotify+polling 的组合。更大的轮询周期用于检测丢失的文件和历史文件,并使用Inotify确保在大多数情况下可以实时发现新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集
代理可以找到该文件,接下来我们需要打开该文件,然后进行采集
。然而,也有意想不到的情况。机器在我们采集
期间坠毁。我们如何确保采集
到的数据不再采集
,并且可以在上次未采集
的地方继续?而 Inotify 用于确保在大多数情况下可以实时发现新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集
代理可以找到该文件,接下来我们需要打开该文件,然后进行采集
。然而,也有意想不到的情况。机器在我们采集
期间坠毁。我们如何确保采集
到的数据不再采集
,并且可以在上次未采集
的地方继续?而 Inotify 用于确保在大多数情况下可以实时发现新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集
代理可以找到该文件,接下来我们需要打开该文件,然后进行采集
。然而,也有意想不到的情况。机器在我们采集
期间坠毁。我们如何确保采集
到的数据不再采集
,并且可以在上次未采集
的地方继续?
基于轮询的方式的优点是保证文件不会被遗漏,除非文件系统出现bug,通过增加轮询周期来避免浪费CPU,但实时性不够。Inotify虽然效率很高,实时性也不错,但不能保证100%的事件丢失。因此,通过 polling 和 Inotify 的结合,可以互相借鉴对方的长处。
点文件高可用
点文件?是的,就是通过点文件记录文件名和对应的采集位置。那么如何保证这个点文件能够可靠的写入呢?因为在写入文件的那一刻机器死机,导致点数据丢失或数据混乱。解决这个问题,需要保证文件写入成功或失败,不能有一半的写入。Linux 内核为我们提供了原子重命名。一个文件可以自动重命名为另一个文件。使用该特性可以保证点文件的高可用性。假设我们已经有一个名为offset的点文件,我们每秒更新这个点文件,并实时记录采集到的位置。整个更新过程如下:
将点数据写入磁盘上的offset.bak文件
fdatasync 确保数据写入磁盘
重命名 offset.bak 以通过 rename 系统调用进行偏移
通过这种方式,可以随时保证点文件是正常的,因为每次写入都会先保证对临时文件的写入成功,然后再进行原子替换。这可确保偏移文件始终可用。在极端情况下,1秒内的积分不会及时更新。日志采集代理启动后,会再次采集1秒内的数据进行重传,基本满足需求。
但是,文件名和相应的采集
位置记录在点文件中。这会带来另一个问题。Crash过程中文件被重命名怎么办?那么启动后就找不到对应的采集位置了。向上。在这种日志场景中,文件名其实是很不靠谱的。文件的重命名、删除、软链接等,会导致同一个文件名在不同时间实际指向不同的文件,整个文件路径都保存在内存中。它非常消耗内存。Linux内核提供了可以作为文件标识信息的inode,同时保证inode不会重复,这样就可以通过在point中记录文件的inode和采集
的位置来解决上述问题文件。日志采集
代理启动后,通过文件发现找到需要采集
的文件,获取Inode,然后从点文件中找到对应的采集
位置,然后继续采集
。那么即使文件被重命名,它的Inode也不会改变,所以你仍然可以从点文件中找到对应的集合位置。但是对 Inode 有什么限制吗?当然,世界上没有免费的午餐。不同的文件系统 Inode 会重复。一台机器可以安装多个文件系统,所以我们需要进一步通过dev(设备号)来区分,所以指向文件中需要记录的就是dev、inode、offset这三元组。至此,我们的采集代理可以正常采集日志,即使崩溃重启,依然可以继续采集。但是有一天我们发现两个文件实际上是同一个 Inode。Linux内核不是保证它们不会同时重复吗?它是内核错误吗?请注意,我使用了“同一时刻”,并且内核只能保证在同一时间。任何时候都不会重复。这是什么意思?这是日志采集
代理遇到的一个比较大的技术挑战,如何准确识别一个文件。
如何识别文件?
如何识别文件被认为是日志采集
代理中一个更具挑战性的技术问题。我们首先通过文件名来识别它。后来发现文件名不靠谱,还消耗资源。后来我们改成dev+inode,但是发现inode只能保证inode不会同时重复。这句话是什么意思?假设文件 Inode 在时间 T1 为 1。我们发现并开始采集
。过一会,这个文件就被删除了,Linux内核的Inode就会被释放。创建新文件后,Linux 内核会将新发布的 Inode 分配给新文件。发现这个新文件后,它会查询上次采集
它的点文件。这样一来,就会找到上一个文件记录的点,导致从错误的位置采集
新文件。如果可以在每个文件上放一个唯一的标识符,这个问题就可以解决。幸运的是,Linux 内核为文件系统提供了扩展属性 xattr。我们可以为每个文件生成一个唯一的标识符,并将其记录在点文件中。如果文件被删除,则创建一个新文件。即使Inode相同,但文件ID不同,Log 采集
Agent也能识别出这是两个文件。但问题是并非所有文件系统都支持 xattr 扩展属性。所以扩展属性只能解决部分问题。或许我们可以通过文件的内容来解决这个问题,它可以读取文件的前N个字节作为文件标识符。这也是一个解,但是这个N有多大呢?同样越大概率越小,不被识别的概率越小。真正做到100%识别的万能解还有待研究,假设80%的问题都解决了。接下来,您可以安心地进行日志采集
。日志采集
其实就是读取文件。读取文件的过程需要注意尽量按顺序读取。充分利用Linux系统缓存。如有必要,您可以使用 posix_fadvise 采集
日志。文件后清空页面缓存,主动释放系统资源。那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?真正做到100%识别的万能解还有待研究,假设80%的问题都解决了。接下来,您可以安心地进行日志采集
。日志采集
其实就是读取文件。读取文件的过程需要注意尽量按顺序读取。充分利用Linux系统缓存。如有必要,您可以使用 posix_fadvise 采集
日志。文件后清空页面缓存,主动释放系统资源。那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?真正做到100%识别的万能解还有待研究,假设80%的问题都解决了。接下来,您可以安心地进行日志采集
。日志采集
其实就是读取文件。读取文件的过程需要注意尽量按顺序读取。充分利用Linux系统缓存。如有必要,您可以使用 posix_fadvise 采集
日志。文件后清空页面缓存,主动释放系统资源。那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?让我们假设这里解决了 80% 的问题。接下来,您可以安心地进行日志采集
。日志采集
其实就是读取文件。读取文件的过程需要注意尽量按顺序读取。充分利用Linux系统缓存。如有必要,您可以使用 posix_fadvise 采集
日志。文件后清空页面缓存,主动释放系统资源。那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?让我们假设这里解决了 80% 的问题。接下来,您可以安心地进行日志采集
。日志采集
其实就是读取文件。读取文件的过程需要注意尽量按顺序读取。充分利用Linux系统缓存。如有必要,您可以使用 posix_fadvise 采集
日志。文件后清空页面缓存,主动释放系统资源。那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?读取文件的过程需要注意尽量按顺序读取。充分利用Linux系统缓存。如有必要,您可以使用 posix_fadvise 采集
日志。文件后清空页面缓存,主动释放系统资源。那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?读取文件的过程需要注意尽量按顺序读取。充分利用Linux系统缓存。如有必要,您可以使用 posix_fadvise 采集
日志。文件后清空页面缓存,主动释放系统资源。那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?那么什么时候统计一个文件的集合呢?当它在采集
结束时返回到 EOF 时,采集
就完成了。但是过一会日志文件里会有新的内容,怎么知道有新的数据,然后继续采集
呢?
我如何知道文件的内容已更新?
Inotify可以解决这个问题,通过Inotify监控一个文件,那么只要文件有新数据就会触发一个事件,获取到事件后就可以继续采集了。但是这种方案存在一个问题,就是写入大量文件时事件队列会溢出。例如,如果用户连续写入日志 N 次,则会生成 N 个事件。其实只要日志采集
代理知道内容更新就够了。更新多少次无关紧要,因为每个集合实际上都会继续读取文件,直到 EOF。只要用户不断地写日志,采集
就会继续。此外,Intofy 可以监控的文件数量是有上限的。所以,这里最简单最常见的解决方法就是轮询查询要采集的文件的stat信息。如果文件内容被更新,它将被采集
。采集
完成后会触发下一次轮询,简单通用。通过这些方法,日志采集
代理最终可以不间断地采集
日志。由于日志总是会被删除,如果在我们的采集
过程中将它们删除会怎样?你可以放心,Linux中有文件。引用计数,即使打开的文件被删除,引用计数减1。只要有进程引用,就可以继续读取内容,所以日志采集
代理可以安心的继续读取日志介意,然后释放文件的fd,让系统真正删除文件。但是你怎么知道这个集合是完整的呢?废话,上面没有说采集到文件的结尾或者采集完成,但是如果此时还有另一个进程也打开了文件,那么在你采集完所有的内容后,你会添加一个一块内容吧。而你此时已经释放了fd,文件已经不在文件系统上了,也没有办法通过文件发现找到文件,打开读取数据,怎么办?
如何安全释放文件句柄?
Fluentd 的做法是将这部分责任转移给用户,让用户配置一个时间。文件删除后,如果在指定的时间范围内没有新的数据,则释放fd。实际上,这是一种间接的倾销行为。这个时间配置太小会增加数据丢失的概率。这个时间配置太多会导致fd和磁盘空间一直被占用,造成短期免费浪费的假象。这个问题的本质是我们不知道还有谁在引用这个文件。如果其他人正在引用此文件,则可能会写入数据。这时候即使你释放了fd资源,它还是被占用了。最好不要释放它。如果没有人引用这个文件,你实际上可以立即释放 fd。我怎么知道谁在引用这个文件?我必须使用 lsof -f 来列出系统中进程打开的文件。该工具扫描每个进程的 /proc/PID/fd/ 目录中的所有文件描述符。可以通过readlink查看这个描述符对应的文件路径,如下例:
tianqian-zyf@ubuntu:~$ sudo ls -al /proc/22686/fd
total 0
dr-x------ 2 tianqian-zyf tianqian-zyf 0 May 27 12:25 .
dr-xr-xr-x 9 tianqian-zyf tianqian-zyf 0 May 27 12:25 ..
lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 0 -> /dev/pts/19
lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 1 -> /dev/pts/19
lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 2 -> /dev/pts/19
lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 4 -> /home/tianqian-zyf/.post.lua.swp
22686进程打开一个文件,fd为4,对应的文件路径为/home/tianqian-zyf/.post.lua.swp。通过该方法可以查询文件的引用计数。如果引用计数为1,即只引用当前进程,那么基本上可以放心的释放fd,不会丢失数据,但问题是开销有点高,需要遍历所有进程才能查看他们打开的文件表并一一比较。复杂度是 O(n)。这个问题如果能实现O(1)就可以完美解决了。我通过搜索相关资料发现这个在用户态几乎没有办法做到,Linux内核没有暴露相关的API,只能Kernel解决了,比如加了个API,通过fd获取文件引用计数,这个在kernel中比较容易,要做到这一点,每个进程保存打开的文件。在内核中,它是struct文件结构。通过这个结构体,可以找到这个文件对应的struct inode对象。引用计数值保存在此对象内。期待后续的Linux内核可以提供相关的API来完美解决这个问题。
总结
至此,基于文件的集合Agen涉及的核心技术点已经介绍完毕。这涉及到很多文件系统和Linux相关的知识。只有掌握了这些知识,才能更好地控制日志采集
。如果你想写一个可靠的日志采集
代理来保证数据不丢失,那么复杂性和挑战是不能忽视的。希望通过本文能让读者对日志采集有更全面的了解!