技术探索与实践踩坑记录:一次分布式日志系统的实战经验

杨雨佳★
2025-06-28 15:44
阅读 2121

开篇:为什么我决定写这篇技术分享?

开篇:为什么我决定写这篇技术分享?

在平时的开发工作中,我们常常会遇到各种各样的“坑”——有些是自己不熟悉新技术导致的,有些则是业务复杂度带来的副作用。而我在参与一个大型分布式系统重构项目时,就遇到了关于日志收集、分析与存储方面的不少挑战。这个项目不仅涉及多个微服务模块,还对接了大量的第三方系统和消息队列,对可观测性的要求极高。

今天我想把这段经历记录下来,不只是为了总结复盘,更希望能给有类似需求或者正准备搭建统一日志系统的同行们一些参考。毕竟,很多问题你不去踩一遍,是永远也想不到会出什么幺蛾子的。


项目背景:从零构建一套完整的日志体系

项目背景:从零构建一套完整的日志体系

公司原来的日志方案非常原始,每个服务都直接写本地文件,通过脚本上传到对象存储,查找起来基本靠 grep + 手动翻日志。随着服务数量增多,数据量激增,这种做法已经完全无法满足定位线上故障的需求。

于是我们决定搭建一套基于 ELK(Elasticsearch、Logstash、Kibana)的日志平台,并逐步引入 Filebeat 采集器替代 Logstash 做 agent 层的日志采集,目标是:

  • 集中管理所有服务日志
  • 实现秒级搜索、报警能力
  • 提供可视化看板辅助排查

整个项目的预期周期为2个月,由我和另一位同事共同负责,中间配合运维团队一起部署集群。


遇到的第一个坑:Filebeat 的吞吐量瓶颈

遇到的第一个坑:Filebeat 的吞吐量瓶颈

实现方案图-2

在测试环境搭建好之后,我们开始往其中导入一部分生产流量进行压测。刚开始一切正常,但当某个服务的日志量突增后,我们发现 Kibana 上明显出现了延迟,甚至个别时间段的数据完全缺失。

一开始我们怀疑是 Elasticsearch 集群资源不足或索引配置不当。结果排查下来发现 Filebeat 这一层才是真正的瓶颈。

原因分析

我们用的是最简单的标准 Filebeat 配置,没有做任何并发优化。日志格式虽然规范,但由于服务输出频繁且体积偏大,单个 Filebeat 实例的吞吐能力根本扛不住。最终导致日志堆积、丢弃。

解决方案

我们做了以下几点调整:

  1. 增加 processors 并行处理能力
  2. 使用 pipelines 提高 event 处理速度
  3. 多实例部署并分片采集目标目录

调整后的关键配置如下:

filebeat.inputs:
- type: log
  paths:
    - /var/log/app/*.log
  fields:
    service: app
  tags: ["json"]
  
processors:
  - decode_json_fields:
      fields: ["message"]
      process_array: false
      max_depth: 1
      target: "json"
      overwrite_keys: true

output.elasticsearch:
  hosts: ["http://es-node1:9200", "http://es-node2:9200"]
  indices:
    "app-*": "%{[fields.service]}-%{+yyyy.MM.dd}"

queue.mem:
  events: 4096
  flush.min_events: 512
  flush.timeout: 1s

max_procs: 4

我们特别调大了 max_procsqueue.mem.events 参数来提升并行处理能力和缓冲能力,同时结合多实例部署,使采集端具备了一定的横向扩展性。


深入挑战:Elasticsearch 写入性能瓶颈

实现方案图-1

解决了 Filebeat 侧的问题之后,新的瓶颈又来了。当接入服务数量超过 10 个、日均日志量突破 10GB 后,Elasticsearch 出现明显的写入延迟。查询响应时间变长,甚至在高峰时段出现部分请求超时。

这个问题比前面那个更隐蔽,排查过程也很曲折。

初步观察和尝试

起初,我们以为是索引文档结构设计不合理,或者 shard 分配出了问题。所以第一时间做了:

  • 增加副本数
  • 改为 hot/warm 架构(hot node 做写入,warm node 做查询)

但效果不大。

后来通过 _nodes/stats 监控指标查看后,发现问题主要集中在磁盘 IO 和线程池压力上。

最终解决方案

我们重点优化了以下几个方面:

1. 索引模板设计优化

默认模板下的字段类型过于宽泛(大量 text 类型未关闭 fielddata),导致内存暴涨和查询延迟。我们做了自定义模板限制不必要的字段映射:

{
  "index_patterns": ["app-*"],
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "index.mapping.total_fields.limit": 50,
    "index.mapping.nested_fields.limit": 5,
    "refresh_interval": "30s"
  },
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "@timestamp": { "type": "date" },
      "level": { "type": "keyword" },
      "json.message": { "type": "text" }
    },
    "dynamic_templates": [
      {
        "strings_as_keyword": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

小插曲:刚上线这版模板时,由于没考虑历史索引兼容性,新旧混搭导致部分索引 mapping 格式混乱。最后还是得滚动更新 + reindex 数据才彻底解决。

2. 增加 hot 节点 + 使用 SSD

这是硬件层面的优化,但我们发现哪怕只升级热节点磁盘为 SSD,整体写入性能也提升了至少 30%。而且在高频写入场景下,IOPS 的表现差异尤为明显。

3. 控制批量写入大小 & 间隔

我们将 bulk 请求的 size 从默认的 5MB 调整为 2MB,并将刷新间隔延长至 30 秒左右,在一定程度上缓解了瞬时高压对集群的压力。

最终,这套组合拳下来,Elasticsearch 的平均写入延迟稳定在了 1~2 秒之间,系统负载回归健康水平。


另外一个常被忽视的问题:日志的时间戳处理

你以为日志打出来就是准的?错。特别是在分布式环境中,不同服务节点的系统时间往往存在误差。如果不加以处理,Kibana 上看到的时间可能是错乱的。

我们在调试过程中就发现某些服务日志显示时间差了几分钟,导致排障时根本找不到关键链路信息。

我们的解决办法是:

  1. 在每台服务器启用 NTP 协议同步时间;
  2. 如果服务本身日志中携带了 UTC 时间戳,则在采集阶段做自动时区转换;
  3. 对于本地时间无标准格式的服务日志,统一在 Filebeat 中添加时间戳预处理逻辑。

例如下面这段 processors 配置就能实现动态解析:

processors:
  - timestamp:
      field: message
      layouts:
        - "2006-01-02T15:04:05Z"
        - "2006-01-02 15:04:05,000"
      test:
        - "2023-08-10T12:00:00Z"

这样做完后,即使服务打出来的日志不是 ISO8601 标准格式,也能被正确识别并转成 UTC 存储。


效果总结:从混沌到可控的转变

经过两个月的努力,我们的日志系统最终达到了以下效果:

指标 优化前 优化后
日志采集延迟 5-10min <5s
查询响应时间 5-10s <1s
故障定位时间 ≥2小时 ≤15分钟
日均可承载量 ≈5GB ≥20GB

更重要的是,现在每次上线都可以通过日志快速定位异常点,不再需要反复登录机器、执行 grep 命令。

这也极大增强了我们对系统可观测性的信心。可以说,这次搭建不仅是工程能力的一次锤炼,更是我们整个技术团队向 DevOps 文化迈出了实质性的一步。


经验分享:别再掉进这些“老坑”

结合本次日志平台建设的经验,我想给大家几个建议:

✅ 1. 不要一开始就追求“一步到位”

很多人喜欢上来就把整套 ELK/EFK 架构一次性全部搭起来,但实际上对于中小型团队来说,完全可以先搭建最小可用单元验证可行性。

比如:

  • 先试跑一个服务接入 + 查看效果
  • 测试采集器和 ES 的稳定性
  • 再根据压测结果决定是否扩展架构

✅ 2. 重视日志格式标准化

如果能在开发阶段就规定好统一的日志结构规范,后续采集、解析、搜索都会轻松得多。尤其是 JSON 格式一定要规范化,避免嵌套过深、字段名随意等问题。

✅ 3. 关注日志采集体量和压缩策略

在数据传输过程中,适当开启 gzip 压缩能节省大量带宽资源,尤其是在日志量大的情况下效果非常明显。

当然也要注意压缩等级的取舍,太高可能会消耗更多 CPU。

✅ 4. 考虑日志冷热分离

如果数据量较大,可以像我们一样使用 Hot/Warm/Cold 架构来做分级存储。Hot 节点用于写入最新日志,Warm 或 Cold 节点存储历史数据,这样既能保障性能,又能控制成本。

✅ 5. 别忘了权限控制和安全性

Elasticsearch 默认是开放访问的,一旦暴露到公网风险极高。所以我们必须配合 HTTPS、RBAC 来做权限隔离,尤其要注意敏感日志内容不能被随便搜索。


结语:技术成长总是在不断踩坑中发生的

这次的技术探索之路,让我深刻体会到,“看起来简单”的事情背后往往藏着很多细节。每一个不起眼的参数设置、每一处看似无关紧要的日志格式约定,都可能影响整个系统的健壮性和稳定性。

也正因为如此,我才更加坚定了自己的信念:架构设计不是纸上谈兵,而是要在一次次实践中摸索总结出来的。

希望我的这次“踩坑实录”能让你少走一点弯路,也许下次你们在部署日志系统的时候,能想起这篇文章中的某句话:“记得开个 time processor 哦,不然你的日志时间会打架的。”

共勉。

评论 0

最热最新
暂无评论
杨雨佳★Lv.1
0
影响力
0
文章
0
粉丝