filebeat主要模块
- input: 找到配置的日志文件,启动harvester
- harvester: 读取文件,发送至spooler
- spooler: 缓存日志数据,直到可以发送至publisher
- publisher: 发送日志至后端,同时通知registrar
- registrar: 记录日志文件被采集的状态
日志整个采集过程
1、filebeat启动后根据linux glob规则去遍历input(Crawler的结构体)。
2、每个文件启动一个harvester去读取文件并发送到内存缓存队列memqueue,当文件被删除或者文件不活跃时间达到close_inactive(默认5min)时,harvester会被关闭。harvester被关闭后,删除的文件的磁盘空间才回被释放。
3、BufferingEventLoop获取到response channel后,consumer对memqueue的消费。(filebeat中的libbeats库包含了kafka、elasticsearch、logstash等几种client,可以实现这种队列方式)
4、调用Publish接口发送消息,并通知registrar
5、filebeat启动了一个独立的registry协程负责监听日志数据发送至后端成功后返回ack的事件,接收到ack事件后会将日志文件的State状态更新至registry文件中,State中的Offset表示读取到的文件偏移量。
如何保证数据不丢失和数据不重复
- filebeat维护了一个registry文件在本地的磁盘,该registry文件维护了所有已经采集的日志文件的状态。
每当日志数据发送至后端成功后,会返回ack事件。filebeat启动了一个独立的registry协程负责监听该事件,接收到ack事件后会将日志文件的State状态更新至registry文件中,State中的Offset表示读取到的文件偏移量,所以filebeat会保证Offset记录之前的日志数据肯定被后端的日志存储接收到。 - 文件被改名或移动,filebeat会根据registry文件中维护的inode和设备号来标志每个日志文件,保证文件不会被重复读取。
- filebeat异常重启,在每个harvester启动的时候都会读取registry文件,获取对应文件上次读取的位置继续采集,确保不会从头开始重复发送所有的日志文件。
- harvester读取中的日志文件被清空,filebeat会在下一次Reader.Next方法中返回ErrFileTruncate异常,将inode标志文件的Offset置为0,结束这次harvester,重新启动新的harvester,虽然文件不变,但是registry中的Offset为0,采集会从头开始。
- 使用容器部署filebeat,需要将registry文件挂载到宿主机上,否则容器重启后registry文件丢失,会使filebeat从头开始重复采集日志文件,导致数据重复或丢失。
注册表文件(registry)
目录文件详细:
data/registry/filebeat/
├── 237302.json # registry 快照文件,记录所有日志文件的当前状态,采用最后一次动作的编号作为文件名
├── active.dat # 记录最新一个快照文件的绝对路径
├── log.json # registry 日志文件,记录最近执行的一连串动作的日志
└── meta.json # registry 的元数据
filebeat每执行一个动作,会在log.json文件中记录两行JSON日志,如下:
{"op":"set", "id":237302} // 本次动作的编号
{
"k": "filebeat::logs::native::778887-64768", // key ,由 beat 类型、日志文件的 id 组成
"v": {
"id": "native::778887-64768", // 日志文件的 id ,由 identifier_name、inode、device 组成
"prev_id": "",
"ttl": -1, // -1 表示永不失效
"type": "log",
"source": "/var/log/supervisor/supervisord.log", // 日志文件的路径(文件被重命名之后,并不会更新该参数)
"timestamp": [2061628216741, 1611303609], // 日志文件最后一次修改的 Unix 时间戳
"offset": 1343, // 当前采集的字节偏移量,表示最后一次采集的日志行的末尾位置
"identifier_name": "native", // 识别日志文件的方式,native 表示原生方式,即根据 inode 和 device 编号识别
"FileStateOS": { // 文件的状态
"inode": 778887, // 文件的 inode 编号
"device": 64768 // 文件所在的磁盘编号
}
}
}
日志异常的情况
- 日志发送过程中,没来得及回复ack事件,filebeat就挂掉了,registry文件未更新到日志的最新状态,但实际上这条日志是发送成功了的,这就导致在filebeat重启后,这条日志会被重新发送,即存在一条重复的日志数据。
- linux下老文件被移除,新文件马上创建,这时可能会出现registry文件中维护的inode相同的情况( inode重用),会导致registry里记录的其实是被移除的文件State状态,这样新的文件采集却从老的文件Offset开始,从而会遗漏日志数据。
- 日志滚动过于频繁,少于scan_frequency时间(默认10s),这就可能出现个别文件未被Filebeat的input模块扫描到,就已经滚动生成重命名为其他文件,导致日志数据丢失。(概率极低,因为一般很少有系统的日志会在几秒内频繁滚动)
docker或logrotate日志滚动
harvester监听的还是旧文件,因为文件重命名或者移动是不会改变inode的。滚动生成的新文件拥有新的inode,通过Filebeat的input模块会被扫描到,并启动新的harvester进行监听。旧文件会因为文件不活跃到达close_inactive时间,harvester会被关闭。
日志文件滑动策略或大量日志文件产生导致registry文件过大
调整以下参数:
- close_renamed:当文件被重命名时关闭重命名的文件处理,默认false。
- clean_removed:当文件被删除时关闭被删除的文件处理,从registry记录中清除文件记录,但是如果该文件后续再一次出现,将会导致文件会被filebeat从头再读一遍,默认false。
- clean_inactive:当文件不活跃时间达到指定的时间,从registry记录中清除文件记录,若在scan_frequency时间间隔后,改文件再次发生更改,则会从最新的内容开始采集。
- ignore_older:忽略在指定时间跨度之前修改的任何文件,例如只需要采集一天内的文件,则配置为24h。
- 确保文件在忽略前,文件不再被读取,所以ignore_older的值必须大于clean_inactive的值。
注意:即使是文件被重新命名,这些参数也会生效,因为registry记录的是inode,与文件名称无关。所以以上的重命名或者删除都是基于inode有没有发生变化,重命名时inode是不会变的,若inode发生变化则认为原文件被删除。