1. 引言
在分布式架构下,线上排查常面临日志分散痛点:服务实例随规模扩展增至数十台后,逐台登录检索不仅需反复确认节点位置(依赖运维协作),且多机切换效率极低。若实现在单个机器上进行日志实时检索,将大幅提升故障定位速度。
因此,集中化日志管理方案应运而生——主流方案ELK(Elasticsearch/Logstash/Kibana)通过Logstash采集转换日志,ES存储数据,Kibana实现可视化查询。鉴于Logstash性能瓶颈,实践中常采用EFK架构,由轻量级Filebeat替代Logstash完成日志采集。
但ES的高资源消耗成为痛点:作为一个小公司,可能你有4台 2核8g 的机器作为业务服务器,但是为了跑一个elk,可能需要用到4核16g的机器,而且随着日志量的上升,还需要扩展多台Elasticsearch集群,说不定就会出现日志服务器的成本比业务服务器成本还要高的情况,被老板发现是要挨骂的。因此,这里给出了一个高性能低成本的替代方案,让成本大幅降低,同时也满足基本的使用要求。
2. 性能参考
文章提到的亿级数据不是随便说说的,是根据目前线上的情况计算的,这里提供下一些相关数据以供参考:
- kafka数据qps: 单个topic最多的峰值12w/秒,一天单个topic总日志条数在47亿左右
- 机器使用,两台8c16g的机器,因为除了上面还有其他业务,目前跑的比较满,cpu占用比较多的是kafka和mongodb
- 业务服务器的rsyslog程序使用的cpu,这个和机器的日志量有关系,一般的在0.15核左右,日志多的在0.3核
3. 架构图和流程
方案流程:
使用rsyslog来收集各个服务器的日志,将数据发送到日志服务器上的kafak,然后在日志服务器上通过rsyslog将日志汇总后落盘存入日志服务器。同时还可以使用logstash将请求记录存入elasticsearch或者mongo,用于快速查询。所谓的请求记录就是每个请求过来时,通过统一的sdk在java中打印一条统一格式的日志,包括了请求参数,uri和返回参数。
通过开放请求日志自助查询功能,可以显著提升跨部门协作效率:开发组将标准化日志接口开放至全公司,各业务线可通过自助查询完成80%以上的问题溯源,有效降低让开发帮忙查询问题的频次。实践表明,该模式在控制运维成本的同时,使核心开发人力从被动支持中释放,专注高价值任务迭代。
关键组件角色:
- Rsyslog:轻量级日志采集,linux自带工具
- Kafka:消息队列,削峰填谷,抗突发流量,避免日志丢失
- Mongodb: 非关系型数据库,提供高性能的数据插入和读取
- ELK:(可选)存储、检索、分析
本方案优点:
- 低成本:对比 常规方案,节省 70%以上成本
- 低消耗:采集端和服务收集端的cpu使用率很低,对线上服务影响最小化
- 效果好:经实际使用,能满足大部分需求
- 低侵入:不改业务代码,用 Rsyslog 无感知收集日志
- 高可靠:Kafka 缓冲,避免日志洪峰丢数据
4. 核心实现详解
4.1 rsyslog
Rsyslog 是 Linux/Unix 系统中最主流的系统日志守护进程,作为传统 Syslog 的增强版,它不仅能处理本地日志,还能实现高性能的远程日志转发、过滤和存储。
我们会在两个地方使用rsyslog,一个是在业务机器上,一个是在日志机器上,这两个地方的配置不一样。
4.1.1 业务机器配置rsyslog
我们需要在业务服务器上使用rsyslog来收集分散在各个机器上的服务日志,因为linux自带rsyslog程序,不用额外下载,但是我们需要使用rsyslog将日志发送到kafka,因此需要下载rsyslog的omkafka模块。 在各个业务机器上执行:
yum install -y rsyslog-kafka
后续可以将下载过此插件的机器打个镜像,增加新机器时就不需要下载了,使用k8s部署机器的同学可以生成个容器镜像,配置个daemonset就行。 接着需要修改rsyslog的配置,让他把日志文本的内容往kafka上发。
执行命令修改配置文件:
vi /etc/rsyslog.conf
增加配置:
#imfile模块支持读取文件,默认安装,这里配置10秒一次读取,控制同步的频率
module(load="imfile" PollingInterval="10")
module(load="omkafka")
#template是传输的文本的格式,同学们可以自己根据需要定义,我这里是配置了模块名,pod名称和节点名称
template(name="commonmsg" type="string" string="%!module% %!pod% %hostname% %msg%")
#input配置输入的日志文件位置,下面是根据具体的日志文件夹格式来配置,符合规则的文件都会被监听并传输内容
input(type="imfile"
File="/biz/log/*/*.log"
Tag="all"
ruleset="transferKafkaRuleForCommon")
#ruleset是规则配置,上面input配置了ruleset规则,当匹配的文件发生变动时,会为每一行执行指定的规则,下面是根据文件名获取变量,转换下传输到kafka的日志格式,其中broker配置的是kafka集群
ruleset(name="transferKafkaRuleForCommon"){
set $!module = field($!metadata!filename, "/", 4);
set $!pod = field($!metadata!filename, "/", 5);
action(type="omkafka"
topic="log"
broker=["xxx.xx.x.xxx:9092,xxx.xx.x.xxx:9092"]
errorFile="/data/kafka/error"
template="commonmsg"
confParam=[
"socket.keepalive.enable=true"
] )
}
重启rsyslog令配置生效:
service rsyslog restart
或者手动杀掉rsyslog并启动
/usr/sbin/rsyslogd -n &
4.1.2 日志机器配置rsyslog
日志机器也需要下载rsyslog的omkafka模块。
yum install -y rsyslog-kafka
执行命令修改配置文件:
vi /etc/rsyslog.conf
增加配置:
#设置单条日志最大的大小
$MaxMessageSize 300k
module(load="omfile" )
module(load="imkafka")
input(type="imkafka" topic="log" consumergroup="default" broker="xxx.xx.x.xxx:9092,xxx.xx.x.xxx:9092" ruleset="acceptKafkaRule")
template(name="rawmsg" type="string" string="%rawmsg%\n")
template(name="dynaFileNameTemplate" type="string" string="/biz/log/%!module%/%!module%.log")
#这个规则会根据配置的文本模板,将内容写入到对应的文件夹路径下,这样能保证日志机器上和业务机器上的文件路径是一样的
ruleset(name="acceptKafkaRule"){
set $!module = field($rawmsg, " ", 1);
action(type="omfile" dynaFile="dynaFileNameTemplate" template="rawmsg")
}
重启rsyslog令配置生效:
service rsyslog restart
4.2 kafka
kafka是一个分布式、支持分区和多副本的消息系统,基于Zookeeper进行协调,能够实时处理大量数据,适用于多种场景,如日志收集、实时系统、流处理引擎等。我们这里用到kafka主要是用于削峰填谷,避免消费端出问题日志丢失,同时也能降低成本,比如如果业务的高峰期时,日志系统性能不足导致出现了消息堆积,但是如果这时候一般不需要查日志(比如业务高峰期时晚上),我们可以不用扩容日志服务器,等待他慢慢消费就行了,是不是又省了一笔钱。
这里的kafka不需要额外处理,就按照官网部署一个就行,这里就简单提几个优化建议:
- 如果有不同的日志需要消费,比如有业务日志,也有日志记录(前文说的一个请求对应的一条日志),可以在发送端将其设置为不同的topic,日志机器上使用不同的规则进行处理
- 如果发现日志机器上cpu没跑满,但是消息堆积了,可以改下kafka的topic的分片数,增加并行度,提高消费能力
4.3 logstash
Logstash 是一款强大的数据处理工具,它可以实现数据传输,格式处理,格式化输出,还有强大的插件功能,常用于日志处理。他的优点是插件多,功能强,但是缺点是性能比较一般,吃资源。因此我们把大数据量的日志都优先通过rsyslog进行处理,能降低很多资源使用,只有确实需要使用logstash的时候才使用。我们这里主要是用它将日志记录导入到mongodb数据库用于下一步查询和展示。
logstash的部署这里就不多讲了,主要提供下logstash的一些配置建议:
#input是输入的端口号配置,可以配置多个端口号用于区分不同的输入源,然后使用不同的格式解析
input {
tcp {
port => 5000
mode => server
}
tcp {
port => 5001
mode => server
type => "accesslog"
}
}
#filter是规则转换,可以进行一些文本的解析和转换,但是耗费cpu比较多,能少用就少用点
#需要注意的是,存入mongodb数据库的数据必须是json格式,建议服务输出日志记录的sdk直接输出json格式的日志,这里就不用进行json解析了
filter {
json {
source => "message"
}
mutate {
remove_field =>["port","message"]
}
if [type] == "accesslog" {
if [upstreamtime] != "-" {
mutate {
convert => {
"upstreamtime" => "float"
}
}
}
}
}
#output是输出,这里根据微服务的模块名称,将不同的模块日志传输到不同的数据库,达到分库的效果,也能将一些不多的数据汇总存入默认的数据库
#collection配置也可以根据配置进行默认按天分表,减少单表数据量,提高查询性能
output{
if [module] == "module1" {
mongodb {
uri => "mongorui"
database => "module1"
collection => "day_%{logdate}"
codec => "json"
bulk => true
bulk_size => 999
bulk_interval => 3
}
} else if [module] == "module2" {
mongodb {
uri => "mongorui"
database => "module2"
collection => "day_%{logdate}"
codec => "json"
bulk => true
bulk_size => 999
bulk_interval => 3
}
} else {
mongodb {
uri => "mongorui"
database => "defaultDatabase"
collection => "day_%{logdate}"
codec => "json"
bulk => true
bulk_size => 999
bulk_interval => 3
}
}
}
4.4 mongodb
MongoDB是一个面向文档的NoSQL数据库管理系统,使用类似于JSON的BSON(Binary JSON)格式来存储数据。MongoDB的设计目标是提供高性能、可扩展的数据存储解决方案,非常适合处理大规模数据集和高并发的读写操作。 mongo的主要优势是数据格式灵活,只要是json就能存,并且插入和查询的性能都很高,可以作为一个elasticSearch的平替,能存储大量的日志记录信息,用于下一步的查询和数据分析等用途。
这里同样不提供mongo的安装步骤,提几个建议:
- 插入mongo的数据,建议通过logstash的配置进行分库分表,这样单表数据量较小,性能会比较高。
- 可以弄个定时任务脚本,自动按天删除数据和创建表以及建立索引,这样性能是最高的,因为mongo删除表和新建表时增加索引都是秒级的
- sdk输出日志记录时,可以在业务上固定一些入参名称,比如常用的userId,确保每个请求都有这个参数,这样就可以根据这个参数建立索引,开发查询时只要数据userId就能查到所有的请求了
5. 总结
这个方案是通过替换elk部分组件,达到能高性能低成本的实现日志收集的方案。此方案排查问题的流程为
- 通过内部管理系统,使用userId之类的id通过mongo查请求记录来初步确认问题。
- 如果查看请求记录和返回参数没法确认问题的话,根据请求记录的traceId,要到日志机器上通过grep日志文件查看具体请求记录信息。
目前根据实际情况来看,通过mongo查日志记录 已经能满足大部分排查问题的需要了,少部分情况下需要登录机器查询日志。如果有需要实现低成本高性能的日志收集方案的同学,可以尝试下。
“日志不是成本,而是数据资产——通过 Rsyslog + Kafka + ELK 的黄金组合,我们让分散的日志变成实时可观测的业务洞察力。”