解决方案:如何设计日志采集系统?

优采云 发布时间: 2022-11-22 09:32

  解决方案:如何设计日志采集系统?

  概述

  日志记录经历了一个巨大的变化,从最初的以人为本的演变到现在的以机器为本。最初,日志的主要消费者是软件工程师,他们阅读日志来解决问题。今天,大量机器日以继夜地处理日志数据以生成可读的报告来帮助人类做出决策。在这个改造过程中,日志采集Agent扮演着重要的角色。

  作为一个日志采集代理,简单来说,它实际上是一个将数据从源头传递到目的端的程序。通常目的地是具有数据订阅功能的集中存储。这样做的目的是统一日志分析和日志存储。耦合,不同的消费者可能对同一个日志感兴趣,获取日志后的处理方式也会不同。数据存储和数据分析解耦后,不同的消费者可以订阅自己的兴趣。,选择相应的分析工具进行分析。这种具有数据订阅功能的中心化存储在业界比Kafka更受欢迎,对应阿里巴巴内部的DataHub和阿里云的LogHub。数据源大致可以分为三类,一类是普通的文本文件,一种是通过网络接收日志数据,最后一种是通过共享内存。本文只谈第一类。这大概就是一个日志采集Agent的核心功能。在此基础上,还可以进一步引入日志过滤、日志格式化、路由等功能,看起来像一个生产车间。从日志投递的角度来看,日志采集可以分为推送模式和拉取模式。本文主要分析推送方式下的日志采集。从日志投递的角度来看,日志采集可以分为推送模式和拉取模式。本文主要分析推送方式下的日志采集。从日志投递的角度来看,日志采集可以分为推送模式和拉取模式。本文主要分析推送方式下的日志采集。

  推送模式是指日志采集代理主动从源获取数据并发送到目的端,而拉模式是指目的从日志采集代理主动从源获取数据。

  行业现状

  目前业界比较流行的日志采集主要有Fluentd、Logstash、Flume、scribe等,阿里内部的LogAgent,阿里云的LogTail。在这些产品中,Fluentd占据绝对优势,成功入驻CNCF阵营。统一的日志层(Unified Logging Layer)大大降低了整个日志采集和分析的复杂度。Fluentd认为,现有的日志格式大多是弱结构化的,这得益于人类对日志数据的解析能力非常出色,因为日志数据本来就是面向人类的,人类是其主要的日志数据消费者。为此,Fluentd希望通过统一日志存储格式来降低整个日志采集和访问的复杂度。假设输入的日志数据有M种格式,而日志采集Agent的后端连接了N种存储,那么每个存储系统需要实现解析M种日志格式的功能,总复杂度为M*N。如果日志采集代理统一了日志格式,那么总的复杂度就变成了M+N。这就是Fluentd的核心思想,其插件机制也是值得称赞的地方。Logstash和Fluentd同样属于ELK技术栈,在业界应用广泛。两者的对比可以参考这篇文章Fluentd vs. Logstash: A Comparison of Log Collectors:这就是Fluentd的核心思想,其插件机制也是值得称赞的地方。Logstash和Fluentd同样属于ELK技术栈,在业界应用广泛。两者的对比可以参考这篇文章Fluentd vs. Logstash: A Comparison of Log Collectors:这就是Fluentd的核心思想,其插件机制也是值得称赞的地方。Logstash和Fluentd同样属于ELK技术栈,在业界应用广泛。两者的对比可以参考这篇文章Fluentd vs. Logstash: A Comparison of Log Collectors:

  从零开始写一个日志采集

Agent

  作为日志采集代理,在大多数人眼里,可能就是一个数据“搬运工”,他们经常抱怨这个“搬运工”占用了太多的机器资源。简单来说就是tail -f命令,比较合适。,对应Fluentd中的in_tail插件。作为一名亲自实践过日志采集Agent的开发者,笔者希望通过本文普及一下日志采集Agent开发过程中的一些技术挑战。为了使整篇文章的脉络连贯,作者试图通过“从零开始写一个日志采集Agent”这个主题来描述整个开发过程中遇到的问题。

  如何发现一个文件?

  

" />

  当我们开始编写日志采集

代理时,我们遇到的第一个问题就是如何找到文件。最简单的方式是用户直接列出要采集

的文件,放到配置文件中,然后日志采集

代理会去读取配置文件,找到要采集

的文件列表可能是最简单的,最后打开这些文件进行采集

。但是,在大多数情况下,日志是动态生成的,并且会在日志采集

过程中动态创建。提前在配置文件中列出来太麻烦了。一般情况下,用户只需要配置日志采集

目录和文件名匹配规则即可。比如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。日志(-[0-9]{4}-[0 -9]{2}-[0-9]{2})?有了这样的描述规则,日志采集代理就可以知道哪些文件需要采集,哪些文件不需要采集。接下来又会遇到一个问题:如何找到新创建的日志文件?定期轮询目录或许是个好办法,但轮询周期太长则不够实时,太短又会消耗CPU。我也不希望你的 采集

Agent 被抱怨占用太多 CPU。日志(-[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 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集Agent可以找到文件,接下来我们需要打开文件进行采集。但是,天有不测风云,在我们采集的过程中机器死机了。怎么保证采集到的数据不会被再次采集,上次没采集到的地方可以继续呢?如果不想错过,那么最好的解决方案就是 Inotify+polling 的组合。使用更大的轮询周期来检测丢失的文件和历史文件,并使用 Inotify 确保在大多数情况下可以实时找到新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集Agent可以找到文件,接下来我们需要打开文件进行采集。但是,天有不测风云,在我们采集的过程中机器死机了。怎么保证采集到的数据不会被再次采集,上次没采集到的地方可以继续呢?如果不想错过,那么最好的解决方案就是 Inotify+polling 的组合。使用更大的轮询周期来检测丢失的文件和历史文件,并使用 Inotify 确保在大多数情况下可以实时找到新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集Agent可以找到文件,接下来我们需要打开文件进行采集。但是,天有不测风云,在我们采集的过程中机器死机了。怎么保证采集到的数据不会被再次采集,上次没采集到的地方可以继续呢?并使用 Inotify 确保在大多数情况下可以实时找到新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集Agent可以找到文件,接下来我们需要打开文件进行采集。但是,天有不测风云,在我们采集的过程中机器死机了。怎么保证采集到的数据不会被再次采集,上次没采集到的地方可以继续呢?并使用 Inotify 确保在大多数情况下可以实时找到新创建的文件。即使在不支持 Inotify 的场景下,也可以单独使用轮询。正常工作。至此我们的日志采集Agent可以找到文件,接下来我们需要打开文件进行采集。但是,天有不测风云,在我们采集的过程中机器死机了。怎么保证采集到的数据不会被再次采集,上次没采集到的地方可以继续呢?

  基于轮询的方法的优点是保证不会遗漏任何文件,除非文件系统有bug,增加轮询周期可以避免CPU的浪费,但实时性不高足够的。Inotify虽然效率很高,实时性也很好,但是不能保证100%不丢失事件。因此,通过结合轮询和Inotify,它们可以相互学习。

  点文件高度可用

  点文件?是的,点文件是用来记录文件名和对应的采集位置的。那么如何保证点文件能够可靠写入呢?因为机器可能会在写入文件的瞬间死机,导致点数据丢失或数据混乱。解决这个问题,需要保证文件写入不是成功就是失败,不能写到一半。Linux 内核为我们提供了原子重命名。一个文件可以自动重命名为另一个文件。使用该特性可以保证点文件的高可用。假设我们已经有了一个名为offset的点文件,我们每秒更新这个点文件,将采集到的位置实时记录在里面。整个更新过程如下:

  将点数据写入磁盘上的 offset.bak 文件。fdatasync 确保数据写入磁盘。通过 rename 系统调用将 offset.bak 重命名为 offset。

  这种方式可以随时保证点文件是正常的,因为每次写入都会先保证对临时文件的写入成功,然后再进行原子替换。这可确保偏移文件始终可用。在极端场景下,1秒内的点将不会及时更新。启动日志采集代理后,会再次采集1秒内的数据进行重传,基本满足要求。

  但是点文件中记录了文件名和对应的采集位置,这会带来另一个问题。Crash过程中文件重命名怎么办?那么启动后就找不到对应的采集位置了。向上。在日志场景下,文件名其实是很不靠谱的。文件重命名、删除、软链接等都会导致同一个文件名在不同的时间指向不同的文件,整个文件路径保存在内存中。非常占用内存。Linux内核提供inode作为文件的标识信息,保证inode不会同时重复,这样就可以通过记录文件的inode和采集

的位置来解决上面的问题点文件。日志采集

代理启动后,通过文件发现找到要采集

的文件,获取inode然后从点文件中找到对应的采集

位置,最后在后面继续采集

。那么即使文件改名了,它的inode也不会改变,所以还是可以从点文件中找到对应的集合位置。但是inode有什么限制吗?当然天下没有免费的午餐,不同的文件系统inode会重复,一台机器可以安装多个文件系统,所以我们需要用dev(设备号)来进一步区分,所以点什么需要文件中记录的是dev、inode、offset的三元组。至此,我们的采集代理可以正常采集日志,即使死机重启,依然可以继续采集日志。但是突然有一天我们发现两个文件其实是同一个inode。Linux内核不是保证不会重复相同的时间吗?它是内核中的错误吗?注意,我用的是“同一时间”,内核只能保证同一时间不会重复。时间不会重复,这是什么意思?这是日志采集Agent遇到的一个比较大的技术挑战,如何准确的识别一个文件。

  如何识别一个文件?

  如何识别一个文件是日志采集Agent中一个具有挑战性的技术问题。我们先确定了文件名,后来发现文件名不靠谱,很耗资源。后来改成了dev+Inode,但是发现Inode只能保证Inode在同一时间不重复,那么这句话是什么意思呢?想象一下,在 T1 时间,有一个 inode 为 1 的文件。我们找到它并开始采集

它。Inode会被释放,新文件创建后,Linux内核会将新释放的Inode分配给新文件。然后发现新文件后,会从点文件中查询上次采集到的位置,结果会找到之前文件中记录的点,导致新文件从错误的位置采集. 如果你能给每个文件一个唯一的标识符,你也许能解决这个问题。幸运的是,Linux内核为文件系统提供了扩展属性xattr。我们可以为每个文件生成一个唯一的标识符,记录在点文件中。如果删除了文件,再新建一个文件,即使inode相同,只是文件ID不同,日志采集Agent也能识别出这是两个文件。但是问题来了,并不是所有的文件系统都支持xattr扩展属性。所以扩展属性只能解决部分问题。或许我们可以通过文件的内容来解决这个问题,读取文件的前N个字节作为文件标识。这也是一个解,但是这个N有多大呢?相同的概率越大,认不出来的概率就越小。要真正实现 100% 识别的通用解决方案还有待研究,假设 80% 的问题都在这里解决了。接下来就可以安心采集

日志了。日志采集

其实就是读取文件。在读取文件的过程中需要注意的是尽量按顺序读取,充分利用Linux系统缓存。必要时可以使用posix_fadvise采集

日志文件,清除后主动释放page cache释放系统资源。那么什么时候认为一个文件已经被采集了呢?当集合最后返回到 EOF 时,集合被认为是完成的。但是过一段时间,日志文件中就会产生新的内容。如何知道有新的数据,然后继续采集呢?接下来就可以安心采集

日志了。日志采集

其实就是读取文件。在读取文件的过程中需要注意的是尽量按顺序读取,充分利用Linux系统缓存。必要时可以使用posix_fadvise采集

日志文件,清除后主动释放page cache释放系统资源。那么什么时候认为一个文件已经被采集了呢?当集合最后返回到 EOF 时,集合被认为是完成的。但是过一段时间,日志文件中就会产生新的内容。如何知道有新的数据,然后继续采集呢?接下来就可以安心采集

日志了。日志采集

其实就是读取文件。在读取文件的过程中需要注意的是尽量按顺序读取,充分利用Linux系统缓存。必要时可以使用posix_fadvise采集

日志文件,清除后主动释放page cache释放系统资源。那么什么时候认为一个文件已经被采集了呢?当集合最后返回到 EOF 时,集合被认为是完成的。但是过一段时间,日志文件中就会产生新的内容。如何知道有新的数据,然后继续采集呢?在读取文件的过程中需要注意的是尽量按顺序读取,充分利用Linux系统缓存。必要时可以使用posix_fadvise采集

日志文件,清除后主动释放page cache释放系统资源。那么什么时候认为一个文件已经被采集了呢?当集合最后返回到 EOF 时,集合被认为是完成的。但是过一段时间,日志文件中就会产生新的内容。如何知道有新的数据,然后继续采集呢?在读取文件的过程中需要注意的是尽量按顺序读取,充分利用Linux系统缓存。必要时可以使用posix_fadvise采集

日志文件,清除后主动释放page cache释放系统资源。那么什么时候认为一个文件已经被采集了呢?当集合最后返回到 EOF 时,集合被认为是完成的。但是过一段时间,日志文件中就会产生新的内容。如何知道有新的数据,然后继续采集呢?那么什么时候认为一个文件已经被采集了呢?当集合最后返回到 EOF 时,集合被认为是完成的。但是过一段时间,日志文件中就会产生新的内容。如何知道有新的数据,然后继续采集呢?那么什么时候认为一个文件已经被采集了呢?当集合最后返回到 EOF 时,集合被认为是完成的。但是过一段时间,日志文件中就会产生新的内容。如何知道有新的数据,然后继续采集呢?

  

" />

  我怎么知道文件的内容已经更新了?

  Inotify可以解决这个问题,通过Inotify监控一个文件,那么只要这个文件有新的数据,就会触发一个事件,获取到事件后就可以继续采集了。但是这个方案有一个问题,当大量文件写入时,事件队列会溢出。比如用户连续写日志N次,就会产生N个事件。其实只要日志采集

代理知道内容,就可以更新。至于更新几次并不重要,因为每次采集其实都是不断读取文件直到EOF,只要用户继续写日志,那么采集就会继续。此外,Intofy 可以监控的文件数量也是有限的。所以,这里最简单最常用的方案是轮询查询待采集文件的stat信息,发现文件内容有更新时进行采集,采集完成后触发下一次轮询,简单通用。通过这些方式,日志采集Agent最终可以不间断地采集日志。由于日志总是会被删除的,那么如果我们在采集

过程中删除了日志会怎样呢?不用担心,Linux中的文件是有引用计数的,即使删除打开的文件,引用计数也只会减1。只要有进程引用,就可以继续读取内容,所以日志采集Agent可以安心的继续读取日志,然后释放文件的fd让系统真正删除文件。但是你怎么知道集合已经结束了呢?废话,上面说的是采集到文件末尾就采集完成了,但是如果此时有另外一个进程也在打开文件,你采集完所有的内容之后,再往里面加一段内容。而你此时已经释放了fd,文件已经不在文件系统上了,也没办法通过文件发现找到文件,打开读取数据,怎么办?

  如何安全释放文件句柄?

  Fluentd的处理方式是将这部分责任推给用户,让用户配置一个时间。删除文件后,如果在指定时间范围内没有添加数据,则释放该fd。其实,这是一种间接的甩锅行为。如果这次配置太小,数据丢失的概率会增加。如果这次配置过大,fd和磁盘空间会一直被占用,造成短时间内空闲浪费的错觉。这个问题的本质是我们不知道还有谁在引用这个文件。如果其他人正在引用此文件,则可能会写入数据。这时候即使你释放了fd资源,它还是被占用了。最好不要释放它。如果没有人在引用这个文件,那么 fd 可以立即释放。如何知道谁在引用这个文件?想必大家都用过 lsof -f 来列出系统中进程打开的文件。这个工具会扫描每个进程的/proc/PID/fd/目录下的所有文件描述符,可以通过readlink查看这个描述符对应的文件路径,比如下面的例子:

  tianqian-zyf@ubuntu:~$ sudo ls -al /proc/22686/fd<br />total 0<br />dr-x------ 2 tianqian-zyf tianqian-zyf  0 May 27 12:25 .<br />dr-xr-xr-x 9 tianqian-zyf tianqian-zyf  0 May 27 12:25 ..<br />lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 0 -> /dev/pts/19<br />lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 1 -> /dev/pts/19<br />lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 2 -> /dev/pts/19<br />lrwx------ 1 tianqian-zyf tianqian-zyf 64 May 27 12:25 4 -> /home/tianqian-zyf/.post.lua.swp<br />

  22686 该进程打开了一个文件,fd为4,对应的文件路径为/home/tianqian-zyf/.post.lua.swp。通过该方法可以查询文件的引用计数。如果引用计数为1,即只有当前进程引用,那么基本上fd可以安全释放,不会造成数据丢失,但问题是开销有点大,需要遍历所有进程来检查他们打开文件表并一一比较。复杂度为 O(n)。如果能够实现O(1),这个问题就可以认为是一个完美的解决方案。通过查找相关资料,发现在用户态几乎不可能做到这一点,而且Linux内核也没有暴露相关的API。只能通过Kernel来解决,比如增加一个API,通过fd获取文件的引用计数。这在内核中相对容易做到。每个进程保存打开的文件,就是内核中的struct file结构。通过这个结构体可以找到文件对应的struct inode对象,并在对象内部维护引用。计数值。期待后续的Linux内核提供相关的API来完美解决这个问题。

  总结

  至此,介绍了一个基于文件的集合Agen涉及的核心技术点,其中涉及到大量的文件系统和Linux相关的知识。只有掌握了这些知识,才能更好的控制日志采集。编写可靠的日志采集

代理以确保数据不丢失的复杂性和挑战不容忽视。希望通过本文能让读者对日志采集有更全面的了解!

  <p style="font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;color: rgb(0, 0, 0);text-align: center;user-select: text !important;">  <strong style="letter-spacing: 0.544px;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;line-height: 25.6px;user-select: text !important;">点击加入【技术交流群</strong></p>

  解决方案:百度快照功能下线,对SEO是好是坏?

  理论上,取消百度快照功能对SEO没有影响,因为快照只是保存页面的文本文件,不决定排名!而且快照的内容和排名是两个没有关系的级别,所以不会影响关键词的排名。

  

  

" />

  首先我们来了解一下百度快照。我们可以把它看成是页面内容的镜像,只保存纯文本文件。当页面打不开时,用户可以点击百度快照浏览。而且百度快照的打开速度非常快,其实也是为了保证用户能够看到页面内容。那么百度搜索为什么取消快照功能呢?据说百度搜索考虑到目前大部分网站已经很稳定了,页面打不开的情况很少见,所以百度搜索不需要存储快照内容。考虑到快照内容的存储也需要相当多的服务器资源,网速和稳定性都达到了相当的水平,取消百度快照功能有利于节省资源。所以,不用担心百度取消快照显示。其实只要是正规的做SEO,不管百度怎么改,都不会对网站造成太大的影响。就算有影响,我相信也是正面的!如果您有网站和小程序定制开发的需求,请联系火猫网络。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线