☰
日志系统选型与部署:ELK、EFK、Graylog对比及Elasticsearch调优实践
2026/10/16 12:39:02 网站建设 项目流程

日志系统这件事,看起来简单做起来坑不少。我刚接手的几个业务线每天都在产生海量访问日志和业务日志,之前分散在各个服务器上,排查问题全靠一台台登录去看,十几台机器翻下来一个上午就没了。后来下决心搞一套集中式日志平台,把 ELK Stack、EFK、Graylog 这三条主流技术路线都从部署到实战走了一遍,这里把完整的选型思路、部署细节、调优参数和踩坑记录整理出来,给正在做同样事情的朋友一个参考。

这套方案最核心的价值就是两件事:第一,所有日志集中存到一个地方,按时间、按关键词秒级检索,再也不用登录服务器翻文件;第二,通过 Kibana 或者 Graylog 的图表能力,把日志数据变成可视化的趋势图、错误分布图,故障响应速度能快一个量级。适合谁用?如果你是运维工程师、后端开发、或者小团队里什么都管的同学,这篇文章里的内容可以直接抄作业。

1. 选型先想清楚三个问题,再谈技术栈

很多人在日志平台选型上第一反应就是“我要上 ELK”,但实际落地之前,必须先回答三个问题:日志量到底多大、查询的实时性要求多高、团队手里有多少维护精力。这三个问题的答案,直接决定了你该选 ELK、EFK 还是 Graylog。

1.1 三种架构的本质区别

ELK 是经典三件套:Elasticsearch 负责存储和检索,Logstash 负责采集和加工,Kibana 负责可视化。它的特点是组件独立、可替换性强,但 Logstash 作为一个重量级的 Java 进程,吃掉的内存相当可观,而且 pipeline 处理链路的调优参数多,新手容易在配置上栽跟头。

EFK 是把 Logstash 换成了 Filebeat。Filebeat 是一个用 Go 写的轻量级采集器,二进制文件只有几十兆,部署方式就是拷贝过去起个进程,资源占用和 Logstash 完全不是一个量级。在“只是收集日志不做什么复杂处理”的场景下,EFK 比 ELK 香得多。

Graylog 是另一条路线。它把 Elasticsearch 当纯存储引擎来用,自己在前面套了一层消息队列和 Processing Pipeline,元数据放在 MongoDB 里。它内置了告警、权限、LDAP 集成、数据流控这些 ELK 需要组合插件才能实现的功能,开箱即用程度最高。

1.2 用一张表看懂适用场景

对比维度ELKEFKGraylog
采集端资源占用高(JVM)极低(Go 单二进制)中(自带采集端或对接 Filebeat)
数据处理能力强,支持丰富 filter弱,仅轻量处理中,支持 Pipeline 脚本
告警与权限靠 X-Pack 或免费插件同 ELK内置完善
部署复杂度高中中
典型场景复杂清洗、多数据源处理海量日志轻量收集中小团队、需要快速见效

我实际测试过一组数据:同样接入 5 台业务服务器的 Nginx 日志,Logstash 默认配置下常驻内存约 800MB 到 1.2GB,而 Filebeat 只需要 60MB 左右。如果你的服务器本来就紧张,这一项就能让你做出选择。

1.3 我的选择结论

我最后的落地方案是双轨制:主力采集用 Filebeat,服务端统一收进 Elasticsearch;但对于个别需要做复杂字段解析的数据源(比如后端服务打出的多行异常堆栈、混合格式的业务日志),单独保留一条 Logstash 处理链路。搜索和可视化统一走 Kibana。这个组合兼顾了资源和灵活性,也是目前社区里比较成熟的做法。

2. Elasticsearch 集群部署与调优,日志平台的地基

不管选哪条路线,Elasticsearch 都是绕不开的底座。这个环节做不好,后面所有查询都会变慢,甚至集群直接崩溃。日志场景下 ES 的部署和常规业务搜索场景有非常大的区别,很多参数必须按日志场景重新调整。

2.1 节点角色拆分,别一锅烩

ES 老版本是可以一个节点什么都干的,但日志场景千万别这么搞。日志场景的显著特点是写入压力大、查询相对简单,而且数据量随时间线性增长。你必须把节点拆成三类角色:

  • Master 节点:负责集群元数据管理,配置node.master: true、node.data: false、node.ingest: false。至少 3 个节点做高可用。
  • Data 节点:负责数据的存储和查询,是集群的绝大多数机器。
  • Kibana 和协调节点可以复用 Master 节点,也可以单独拆。

我一开始图省事把 3 台机器都设置成了 master 和 data 双角色,结果集群跑了两周,一次索引重建的时候 master 进程因为 IO 阻塞发生选举超时,整个集群写了半个多小时不可用。拆开之后这种问题再没出现。

2.2 内存和磁盘的硬指标

堆内存设置是最容易出错的地方。ES 官方推荐 Xms 和 Xmx 保持一致,设为系统内存的一半,且最高不要超过 30GB。为什么是 30GB?因为 JVM 的压缩指针技术在 30GB 以内有效,超过这个值对象指针占用会翻倍,GC 性能会明显下降。

比如服务器是 64GB 内存,给 ES 分配 31GB,文件系统缓存还有 33GB,这个配置是健康的。千万别给到 40GB、50GB,不仅浪费内存,GC 停顿还会让你怀疑人生。

磁盘方面,日志平台优先用 SSD,不仅是查询快,更关键的是写入合并时的随机 IO 性能。如果预算有限,至少把热数据放在 SSD 上,冷数据可以通过后面讲到的 ILM 生命周期策略滚动到机械盘。

2.3 索引生命周期管理,日志场景必配

日志数据有一个天然特性:时效性越久访问频率越低。如果不用 ILM(Index Lifecycle Management),你会在几个月后面对几百个索引挤爆磁盘的窘境。

我的实践方案是三个生命周期:

  1. Hot 阶段:当前写入的索引,保留最近 3 天。
  2. Warm 阶段:3 到 30 天的数据,可以做只读并合并分片,降低存储开销。
  3. Cold/Delete 阶段:30 天以上的数据迁移到冷节点,90 天以上的删除。

配合 ILM 策略的滚动索引机制,每天自动生成一个形如app-log-2024.06.30的索引。这样做还有个额外好处:删除过期数据只需要删索引文件,不需要走复杂的 delete-by-query,对集群负载的影响极小。

2.4 分片数和副本数的黄金法则

这是日志场景最容易踩的坑。很多人建索引时喜欢按照默认配置建多个分片,觉得分片越多并发性能越好。但分片数太多会导致主分片和副本分片的碎片化,反而拖垮查询速度。

对于日志数据,我建议每天一个索引,每个索引 3 到 5 个主分片,副本 1 份。公式可以这样估算:

单分片容量控制在 30GB 以内 分片数 = 预计每天日志量 / 30GB(向上取整)

例如一天产生 100GB 日志,除以 30GB 约等于 3.3,那就建 4 个分片。如果拿不准,宁少勿多,分片可以在后期通过 Split API 扩展,但 shrink 却很麻烦。

2.5 写入性能的关键配置

日志场景写入压力大,如果发现高峰期写入性能不足,按顺序检查这几个配置:

# 副本数可以在高峰期临时设置为 0,写入完成后改回 1 index.number_of_replicas: 0 # 刷新间隔,默认 1s,可调大到 30s,减少段合并的频率 index.refresh_interval: 30s # 合并策略调整,减少同时合并的段数 index.merge.scheduler.max_thread_count: 2

这些配置在写入高峰期临时调整是常用做法,等到搜索行为多的时候再调回来。我从 1 秒刷新改成 30 秒刷新后,集群写入吞吐提升了约 20%,查询延迟几乎没有感知,因为日志场景里对秒级可见的需求本来就不强烈。

3. 数据采集链路,Logstash 和 Filebeat 的角色分工

采集端是整个链路里最容易被人忽略、却最影响稳定性的环节。日志采集进程挂掉往往不是因为它自己出问题,而是被拿着各种错误配置去连接不存在的服务,或者被无休止的管道阻塞拖垮。想清楚它们的分工,整个链路的稳定性立刻上一个台阶。

3.1 Logstash 适合做什么、不适合做什么

Logstash 的本质是一个数据处理管道,核心价值在 Filter 阶段。它能做正则解析、Grok 格式化、GeoIP 地址解析、日期格式化、字段裁剪、甚至是调用外部 API 做富化。举个例子,一条原始 Nginx 日志如果直接存进 ES,你只能全文搜索。但经过 Logstash 的 Grok 解析后,可以变成结构化 JSON 字段,后续按状态码、响应时间、客户端 IP 做聚合查询,效率天差地别。

但 Logstash 不适合做大规模轻量采集。它的 JVM 常驻内存开销、启动速度和配置复杂度,决定了它应该集中在服务端使用。看到有人拿二三十台 Logstash 去采集日志,我的第一反应是:这是给自己没事找事。

3.2 Filebeat 的部署和配置细节

Filebeat 的优势是便宜、干净、稳定。部署相当简单:

# 下载解压后,编辑 filebeat.yml filebeat.inputs: - type: log enabled: true paths: - /var/log/nginx/*.log fields: log_type: nginx service: web-01 filebeat.modules: - module: nginx access: enabled: true output.elasticsearch: hosts: ["es-node-01:9200", "es-node-02:9200"] index: "nginx-log-%{+yyyy.MM.dd}"

注意几个细节,都是实测经验。第一,fields下自定义的字段建议嵌套在fields_under_root: false下,避免覆盖 ES 默认字段;第二,如果后面要接 Logstash,输出模块改成output.logstash,但不要同时配置output.elasticsearch,Filebeat 只能配置一个输出;第三,它的harvester是按文件大小偏移量收集的,如果日志文件被 logrotate 切走,需要保证不重复读取,配置clean_removed: true和合理的close_inactive可以减少这类问题。

3.3 一条 Logstash 多路复用管道

我最终还是留了一条 Logstash 管道,专做高复杂度数据的清洗。管道配置的核心思路是把input里的多个数据源和filter里的多套解析规则隔离,用if条件分流,而不要为了不同格式的数据建多套管道。

input { beats { port => 5044 } tcp { port => 5010 } } filter { if [service] == "nginx" { grok { match => { "message" => "%{COMBINEDAPACHELOG}" } } } else if [service] == "java-app" { multiline { pattern => "^\\s" negate => false what => "previous" } } } output { elasticsearch { hosts => ["es-node-01:9200"] index => "%{[service]}-log-%{+yyyy.MM.dd}" } }

多路复用的好处是集中管理、减少进程数量,坏处是管道里某个环节出问题会影响所有数据流。所以我会在 Logstash 的pipeline.workers上按 CPU 核数调整,高峰期把pipeline.batch.size从 125 调大到 300,让一条管道吃下更多数据。

3.4 消息队列要不要引入

很多人纠结是否在采集端和服务端之间插入 Kafka。我的观点是:日志量每日小于 100GB 的团队,完全不需要。直接 Filebeat 到 ES 的链路已经足够稳。引入 Kafka 意味着中间多了一个需要维护的分布式系统,而且日志消费落后会导致查询不到新数据,排查起来比查询变慢更头疼。

只有当数据量达到日均几百 G 以上,或有多套下游消费系统时,Kafka 的缓冲和解耦价值才真正体现出来。到那个时候,我建议架构升级为 Filebeat采集 -> Kafka缓冲 -> Logstash消费 -> ES存储,这也是大型生产环境最成熟的标准链路。

4. Graylog 的架构思路,比 ELK 更轻量的另一条路

在搭建好 ELK 基础设施后,我依然花了一周时间把 Graylog 完整部署了一轮。原因很简单:有些团队对告警、权限、数据接入有强烈诉求,但又不想在 ELK 生态里拼接组合插件,Graylog 可能是更合适的选择。

4.1 Graylog 的组件拆解

Graylog 的架构由三个部分组成:

  • Graylog Server:处理输入数据、执行 Pipeline、提供 WEB 接口和 API。
  • MongoDB:存储配置、用户、告警规则等元数据,不存日志原文。
  • Elasticsearch:还是充当检索和存储引擎的底座。

这套设计聪明的地方在于,它把“实时处理”和“配置检索”分开,MongoDB 跑挂了只是 Web 界面和配置不可用,日志写入检索不受影响。对比 ELK 需要装的告警插件、index 管理插件等,Graylog 把这些能力内置了。

4.2 Graylog 的生产配置要点

安装部分直接照官方文档配就行,踩坑点集中在配置文件的几个参数上。

/etc/graylog/server/server.conf里最重要的一个是password_secret,必须是一个至少 64 字符的随机字符串,否则服务起不来。另一个是root_password_sha2,这是 Web 端管理员密码的 SHA256 哈希,千万不要记成明文填进去。

数据接入上,Graylog 对 Filebeat 天然友好,配置 Filebeat 输出为output.logstash形式只是让数据从 Graylog 的 Beats Input 进来。如果是纯 syslog 环境,它内置的 Syslog UDP/TCP input 也可以直接收。

4.3 Graylog 的 Pipeline 处理日志

Graylog 的处理逻辑比 Logstash 的 filter 更接近“编程模型”:数据进 Input 后先进入一个处理链,由 Pipeline 规则决定做什么。规则语法长得像 Java 和 Groovy 的混合体。

一个很典型的场景:把 Nginx 日志里的响应时间字段转成数字类型,方便后续做聚合统计。

rule "convert request_time to number" when has_field("request_time") then let rt = to_double(to_string($message.request_time)); set_field("request_time_number", rt); end

Graylog Pipeline 的when条件判断、阶段性的rule、以及函数库里的to_double、to_string等转换函数,组成了一个对普通运维也能上手的逻辑体系。对比 Logstash 的 Grok 语法,Graylog 对非专业开发者的学习曲线明显平滑一些。

4.4 什么时候选 Graylog

用了几天后,我的判断是:如果团队只有两三个人,日志量不大不小,没有专门的前端开发资源去定制 Kibana 展现,又需要告警和用户权限,Graylog 就是性价比之选。

但在深度搜索定制、可视化图表复杂度和大型集群优化上,ELK 生态的技术积累更深厚。本质上,Graylog 做的是“够用的开箱即用”,ELK 是“广袤的 DIY 空间”。

5. 实际部署与使用中最常见的问题排查清单

这部分是重点中的重点,按我经历的频率排序,都是真金白银踩出来的坑。

5.1 时间字段和日志时间不一致

这是日志系统里出现频率最高、影响也最隐蔽的问题。默认情况下,Elasticsearch 写入文档时会把@timestamp设置为 ingest 时间。一旦存在数据积压或网络延迟,文档实际落库时间会晚于日志产生时间,按时间筛选查询时会发现数据“迟到”了。

处理方法是在 Logstash 或 Filebeat 中,用日志自带时间覆盖@timestamp字段。

# Logstash filter 中的处理 date { match => ["log_time", "yyyy-MM-dd HH:mm:ss,SSS"] target => "@timestamp" }

对于 Filebeat,配置json.keys_under_root: true后,在字段映射时把时间对应关系配好。改完后必须验证:查一条刚写入的日志,@timestamp是否等于日志自身时间且带对时区,这个坑最坑的是它平时不出问题,一到跨时区大促活动就全乱了。

5.2 索引丢失或者字段类型冲突

日志平台的字段类型一旦写入,几乎不能更改。这是 index mapping 的硬约束。举个例子,某个字段之前被自动映射为long,后来另一条日志往同一字段写入了一个字符串,bulk 请求就会直接报错,要么丢弃这批次数据,要么整条索引写入状态变为 yellow。

解决办法分两层:事前定义好索引模板。对日志场景,尽量在索引模板里把动态映射关掉,或者对高确定性字段显式声明类型。

{ "template": "*-log-*", "settings": { "number_of_shards": 3 }, "mappings": { "properties": { "status_code": {"type": "integer"}, "request_time": {"type": "float"} } } }

但这类问题真正完美的解决方案是“事前规范”:每个接入的日志源先定义语义规范和字段命名规范,避免临时加字段导致的整体重构。我在实际项目中就遇到过一次:开发临时在日志里加了一个名为id的字段,结果把现有索引的全字段映射打乱了,花了半天时间重建索引。

5.3 堆内存使用过高和 Full GC

ES 堆内存飙升常见于两类场景:一类是查询语句里用了过大的聚合桶(terms聚合size设了极大值),另一类是大量分片在同一个节点上导致内存占用堆积。

遇到堆内存快速上涨时,先看热点线程:

# 查询热点线程和堆使用情况 curl -s http://localhost:9200/_nodes/hot_threads curl -s http://localhost:9200/_cluster/health

如果发现某个节点堆占用 85% 以上,立即排查两点:该节点分配的分片数是否过多;最近是否有大聚合查询散落在该节点上。解决方案可以是对该节点设置分片分配过滤,也可以限制聚合精度。但不要第一时间就想加内存,堆内存超出 31GB 上限后,加内存几乎没有正收益。

5.4 字段类型和磁盘 IO 的取舍

日志场景里常见的_source字段是罪魁祸首。默认 ES 会把完整的原始日志存在_source里,好处是查询后能看到完整原始信息;坏处是它会造成大量的磁盘占用。

一个实际的分配方案:如果只是需要结构化字段做聚合检索,可以把_source关闭或做裁剪。但没有_source就无法从查询结果里看到原始日志全文,所以我的经验是:存储成本可接受就保留。如果磁盘紧张,优先对_source做includes过滤,排除超大字段而非整体关闭。

{ "mappings": { "_source": { "includes": ["message", "service", "@timestamp", "status_code"] } } }

这样既控制了体积,又保留了排错需要的关键字段。

5.5 高峰期日志写入出现背压的排查路径

集群出现写入日志告警后,不要第一时间怀疑采集端。按这个路径排查,效率最高:

  1. 先看 ES 集群健康状态,确认有没有 red 索引或未分配分片。
  2. 再查 CPU 和线程池队列,thread_pool.write队列爆掉时,说明写入能力已到瓶颈。
  3. 排查是不是大量长时间运行的聚合查询抢占资源。
  4. 最后看磁盘水位,超过 85% 就会触发自动分配保护。

背压出现往往不是单独某个环节的问题。我之前遇到过一次极端情况:业务高峰流量比平时高了 3 倍,ES 的写入吞吐没变,但查询延迟暴增,导致应用超时重试,重试又带来更多查询,形成恶性循环。这时正确的动作是:减少查询并发、临时降低副本数、调大写入队列缓冲,然后让链路稳定下来再去根治根因。

5.6 日志平台的容量规划经验

容量规划的核心指标是存储压缩比。日志可压缩性极强,Nginx 访问日志在 ES 的_source下一般能压缩到原始文本大小的 30% 到 40%。我做过一次测算:每天产生 100GB 原始日志,经过解析和压缩后,落盘存储大约占 30GB 到 45GB,再加上副本和分片余量,每天实际占用 60GB 到 90GB。

所以规划一个月的数据量时,不要用原始文本量去算,而是用落盘量加至少 30% 的余量。磁盘使用率超过 70% 就该考虑扩容或清理旧索引,不然高峰期的段合并和新索引写入会把你逼到死角。

6. 部署方案落实到生产环境

这篇内容拉得比较长,但日志平台本身就是一个环环相扣的系统工程。最终我自己的线上环境采用了混合方案:Filebeat 作为默认采集器覆盖绝大多数服务器,Logstash 仅保留在少数数据源需要复杂结构化解析的链路上,Elasticsearch 作为统一存储和检索底座,Kibana 负责查询可视化,再写了一套脚本定期做索引清理和健康巡检。Graylog 作为备选方案的技术储备,后续如果个别项目有独立的告警和权限需求,可以直接快速启用。

再提醒一句:日志平台的价值体现在“用”上,而不是“建”上。建好之后一定要和团队约定日志规范:什么字段打什么类型、哪些字段要脱敏、哪些日志要保留多久。没有规范,再强的平台也会在混乱的数据面前失去意义。这套规范定清楚,后期百分之八十的问题都会绕着你走。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询