通过这种方式,当某一时刻出现线程读取到文件末尾时,只需要记录当前的位置,线程就进入等待状态,直到有新的日志内容写入后,线程又重新启动,启动后可以接着上次的尾部往下读取,代码参考如下。另外,在进程挂或者宕机恢复后,也会用到RandomAccessFile来从指定点位开始读取,不需要从整个文件头部重新读取。关于断点续传的能力后文会提到。
RandomAccessFile raf = new RandomAccessFile(file, "r");
byte[] buffer;
private void readFile() {
if ((raf.length() - raf.getFilePointer()) < BUFFER_SIZE) {
buffer = new byte[(int) (raf.length() - raf.getFilePointer())];
} else {
buffer = new byte[BUFFER_SIZE];
}
raf.read(buffer, 0, buffer.length);
}
4.4 实现断点续传
机器宕机、Java进程OOM重启、Agent升级重启等这些是常有的事,那么如何在这些情况下保障采集数据的正确呢?这个问题主要考虑的是采集Agent断点续传的能力。一般的,我们在采集过程中需要记录当前的采集点位(采集点位,即RandomAccessFile中最后的指针指向的位置,一个整型数值),当Agent把对应缓冲区的数据成功发送到Kafka后,此时可以先把最新点位的数值更新到内存,并且通过一个定时任务(默认是3s)持久化内存中的采集点位数值到本地的磁盘的点位文件中。这样,当出现进程停止,重新启动时,加载本次磁盘文件中的采集点位,并使用RandomAccessFile移动到对应的点位,实现了从上一次停止的点位继续往下采集的能力,Agent可以恢复到原有的状态,从而实现了断点续传,有效规避重复采集或者漏采集的风险。
Agent针对的每一个采集任务会有一个对应的点位文件,一个Agent如果有多个采集任务,将会对应多个点位文件。一个点位文件存储的内容格式为JSON数组(如下图所示)。其中file表示任务所采集的文件的名字,inode即文件的inode,pos即position的缩小,表示点位的数值;
[
{
"file": "/home/sample/logs/bees-agent.log",
"inode": 2235528,
"pos": 621,
"sign": "cb8730c1d4a71adc4e5b48931db528e30a5b5c1e99a900ee13e1fe5f935664f1"
}
]
4.5 实时数据发送
前面主要介绍了,日志文件的实时的发现、实时的日志内容变更监听、日志内容的读取等设计方案,接下来介绍Agent的数据发送。
最简单的模型是,Agent通过Kafka Client把数据直接发送到Kafka分布式消息中间件,这也是一种简洁可行的方案。实际上在Bees的采集链路架构中,在Agent与Kafka的数据链路中我们增加了一个"组件bees-bus“(如下图所示)。
bees-bus组件主要起到汇聚数据的作用,类似于Flume在采集链路中聚合的角色。Agent基于Netty开源框架实现NettyRpcClient与Bus之间通讯实现数据发送。网络传输部分展开讲内容较多,非本文章重点就此带过(具体可参考Flume NettyAvroRpcClient实现)。
这里稍微补充下,我们引入bees-bus的目的主要有以下几个:
- 收敛来自于Agent过多的网络连接数,避免所有Agent直连Kafka broker对其造成较大的压力;
- 数据汇聚到Bus后,Bus具备流量多路输出的能力,可以实现跨机房Kafka数据容灾;
- 在遇到流量陡增的情况下, 会导致topic分区所在broker机器磁盘IO繁忙进而导致数据反压到客户端, 由于kafka副本迁移比较耗时所以出现问题后恢复较慢,Bus可以起到一层缓冲层的作用。
4.6 离线采集能力
除了上面常见的实时日志采集的场景外(一般是日志采集到kafka这类消息中间件),Bees采集还有一个离线日志采集的场景。所谓离线日志采集,一般是指把日志文件是采集到HDFS下(参考下图)。
这些日志数据是用于下游的Hive离线数仓建设、离线报表分析使用。该场景数据时效性没有那么强,一般是按天为单位使用数据(我们常说的T 1数据),所以日志数据采集无需像实时日志采集一样,实时的一行一行的采集。离线采集一般可以按照固定时间一个批次采集。我们默认是每隔一小时定时采集上个小时产生的一个完整的小时日志文件,比如在21点的05分,采集Agent则开始采集上个小时产生的日志文件(access.2021110820.log),该文件保存了20点内产生的完整的(20:00~20:59)日志内容。
实现离线的采集能力,我们的Agent通过集成HDFS Client的基本能力来实现,HDFS Client中使用 FSDataOutputStream 可以快速的完成一个文件PUT到HDFS的目录下。
尤其要关注的一点是,离线采集需要特别的增加了一个限流采集的能力。由于离线采集的特点是,在整点左右的时刻,所有的机器上的Agent会几乎同时全量开启采集,如果日志量大、采集速度过快,可能会造成该时刻公司网络带宽被快速占用飙升,超出全网带宽上限,进一步会影响其他业务的正常服务,引发故障;还有一个需要关注的就是离线采集整点时刻对机器磁盘资源的需求是很大,通过限流采集,可以有效削平对磁盘资源的整点峰值,避免影响其他服务。
4.7 日志文件清理策略
业务日志源源不断的产生落到机器的磁盘上,单个小时的日志文件大小,小的可能是几十MB,大的可以是几十GB,磁盘很有可能在几小时内被占满,导致新的日志无法写入造成日志丢失,另一方面可能导致更致命的问题,linux 操作系统报 “No space left on device 异常",引发其他进程的各种故障;所以机器上的日志文件需要有一个清理的策略。
我们采用的策略是,所有的机器都默认启动了一个shell的日志清理脚本,定期检查固定目录下的日志文件,规定日志文件的生命周期为6小时,一旦发现日志文件是6小时以前的文件,则会对其进行删除(执行 rm 命令)。
因为日志文件的删除,不是由日志采集Agent自身发起和执行的,那么可能出现”采集速度跟不上删除速度(采集落后6小时)“的情况。比如日志文件还在采集,但是删除脚本已经检测到该文件生命周期已达6小时准备对其进行删除;这种情况,我们只需要做好一点,保证采集Agent对该日志文件的读取句柄是正常打开的,这样的话,即使日志清理进程对该文件执行了rm操作(执行rm后只是将该文件从文件系统的目录结构上解除链接 unlink,实际文件还未从磁盘彻底删除),采集Agent持续打开的句柄,依然能正常采集完此文件;这种"采集速度跟不上删除速度"是不能长时间存在,也有磁盘满的风险,需要通过告警识别出来,根本上来说,需要通过负载均衡或者降低日志量的方法,来减少单机器日志长时间采集不过来的情况。
4.8 系统资源消耗与控制
Agent采集进程是随着业务进程一起部署在一个机器上的,共同使用业务机器的资源(CPU、内存、磁盘、网络),所以在设计时,要考虑控制好Agent采集进程对机器资源的消耗,同时要做好对Agent进程对机器资源消耗的监控。一方面保障业务有稳定的资源可以正常运行;另外可以保障Agent自身进程正常运作。通常我们可以采用以下方案:
1. 针对CPU的消耗控制。
我们可以较方便采用Linux系统层面的CPU隔离的方案来控制,比如TaskSet;通过TaskSet命令,我们可以在采集进程启动时,设定采集进程绑定在某个限定的CPU核心上面(进程绑核,即设定进程与CPU亲和性,设定以后Linux调度器就会让这个进程/线程只在所绑定的核上面去运行);这样的设定之后,可以保障采集进程与业务进程在CPU的使用上面互相不影响。
2. 针对内存的消耗控制。
由于采集Agent采用java语言开发基于JVM运行,所以我们可以通过JVM的堆参数配置即可控制;bees-agent一般默认配置512MB,理论上最低值可以是64MB,可以根据实际机器资源情况和采集日志文件大小来配置;事实上,Agent的内存占用相对稳定,内存消耗方面的风险较小。
3.针对磁盘的消耗控制。
由于采集Agent是一个IO密集型进程,所以磁盘IO的负载是我们需要重点保障好的;在系统层面没有成熟的磁盘IO的隔离方案,所以只能在应用层来实现。我们需要清楚进程所在磁盘的基准性能情况,然后在这个基础上,通过Agent自身的限速采集能力,设置采集进程的峰值的采集速率(比如:3MB/s、5MB/s);除此之外,还需要做好磁盘IO负载的基础监控与告警、采集Agent采集速率大小的监控与告警,通过这些监控告警与值班分析进一步保障磁盘IO资源。
4.针对网络的消耗控制。
这里说的网络,重点要关注是跨机房带宽上限。避免同一时刻,大批量的Agent日志采集导致跨机房的带宽到达了上限,引发业务故障。所以,针对网络带宽的使用也需要有监控与告警,相关监控数据上报到平台汇总计算,平台通过智能计算后给Agent下发一个合理的采集速率。
4.9 自身日志监控
为了更好的监控线上所有的Agent的情况,能够方便地查看这些Agent进程自身的log4j日志是很有必要的。为了达成这一目的,我们把Agent自身产生的日志采集设计成一个普通的日志采集任务,就是说,采集Agent进程自身,自己采集自己产生的日志,于是就可以把所有Agent的日志通过Agent采集汇聚到下游Kafka,再到Elasticsearch存储引擎,最后通过Kibana或其他的日志可视化平台可以查看。
4.10 平台化管理
目前的生产环境Agent实例数量已经好几万,采集任务数量有上万个。为了对这些分散的、数据量多的Agent进行有效的集中的运维和管理,我们设计了一个可视化的平台,管理平台具备以下Agent控制能力:Agent 的现网版本查看,Agent存活心跳管理,Agent采集任务下发、启动、停止管理,Agent采集限速管理等;需要注意的是,Agent与平台的通讯方式,我们设计采用简单的HTTP通讯方式,即Agent以定时心跳的方式(默认5分钟)向平台发起HTTP请求,HTTP请求体中会包含Agent自身信息,比如idc、ip、hostname、当前采集任务信息等,而HTTP返回体的内容里会包含平台向Agent下发的任务信息,比如哪个任务启动、哪个任务停止、任务的具体参数变更等。
五、与开源能力对比
bees-agent与flume-agent对比
- 内存需求大大降低。bees-agent 采用无 Channel 设计,大大节省内存开销,每个 Agent 启动 ,JVM 堆栈最低理论值可以设置为64MB;
- 实时性更好。bees-agent 采用Linux inotify事件机制,相比 Flume Agent 轮询机制,采集数据的时效性可以在1s以内;
- 日志文件的唯一标识,bees-agent 使用inode 文件签名,更准确,不会出现日志文件误采重采;
- 用户资源隔离。bees-agent 不同 Topic 的日志采集任务,采用不同的线程隔离采集,互相无影响;
- 真正的优雅退出。bees-agent 在正常采集过程中,随时使用平台的"停止命令"让 Agent 优雅退出,不会出现无法退出的尴尬情况,也能保证日志无任何丢失;
- 更丰富的指标数据。bees-agent 包括采集速率、采集总进度,还有 机器信息、JVM 堆情况、类数量、JVM GC次数等;
- 更丰富的定制化能力。bees-agent 具备关键字匹配采集能力、日志格式化能力、平台化管理的能力等;