技术探索与实践

张涛
2025-06-30 02:18
阅读 616

一次“摸着石头过河”的技术探索 —— 我如何从0到1搭建了一个高并发日志处理系统

一次“摸着石头过河”的技术探索 —— 我如何从0到1搭建了一个高并发日志处理系统

引言:一个看似简单的任务,却暴露了架构的短板

去年我在一家中型电商平台负责后端架构优化。随着业务快速增长,我们原有的日志收集系统已经暴露出多个问题:响应延迟大、丢数据、排查困难。最初领导只是让我“优化一下”,但实际接触后才发现,这不仅仅是性能问题,而是一整套日志体系都需要重构。

于是,我决定推倒重来,用更现代、可扩展的方式构建一个新的日志采集和分析平台。整个过程历时两个月,中间踩了不少坑,也积累了不少实战经验,今天就结合这个项目,聊聊技术探索与实践的一些心得。


背景介绍:为什么我们要重构日志系统?

我们的原始架构非常简单粗暴:

  • 应用层通过 logback 写本地文件
  • 每台机器上部署一个 logstash 把日志打到 Kafka
  • 后面是 ELK(Elasticsearch + Logstash + Kibana)做分析

这套方案在初期确实能满足需求,但随着服务节点数量增长到 200+,访问量突破百万 PV/天后,出现几个问题:

  • 丢日志严重:部分日志未能成功写入 Kafka
  • 堆积延迟:Kafka 消费慢,日志经常滞后十几分钟
  • 存储压力大:ELK 成本迅速上升,查询也变慢
  • 定位困难:出问题时要登录多台机器查日志,效率极低

这时候我们就意识到:必须重新设计一整套日志处理链路,才能支撑未来 10 倍的业务增长。


面临的挑战:不只是选组件,而是重新思考整个流程

新的日志平台目标很明确:

  • 实时性强,延迟控制在秒级内
  • 稳定可靠,日志不丢失
  • 可扩展性强,能应对不断新增的服务
  • 查询友好,支持灵活分析

但这些要求具体怎么落地?我们首先要面对的是:

挑战一:采集方式的选择

是继续用 logstash?还是换成 filebeat 或者 Loki 这类轻量级组件?

我们做了个小实验,在 50 台应用服务器上分别跑 logstash 和 filebeat,结果发现 logstash 占用内存高出近两倍,CPU 使用率也不稳定。考虑到我们还有大量的 Kubernetes Pod 日志需要采集,最终我们选择了以 filebeat + fluent-bit 的组合为主。

挑战二:传输通道稳定性

Kafka 当然还是首选,但它本身也需要合理的 Topic 分区策略、副本配置等细节调整。另外,我们在测试过程中遇到消费者反压导致生产者阻塞的问题,后来加上了缓冲队列(Redis Stream)缓解高峰流量冲击。

挑战三:日志结构标准化

不同团队输出的日志格式五花八门,有的用 JSON,有的直接文本带时间戳。为了统一查询入口,我们制定了内部规范,强制要求所有服务使用 Structured Logging,并且每个条目包含 trace_id、service_name、timestamp、level 等字段。


技术实现:一条全新的日志管道搭建过程

最终我们构建的日志处理流水线如下:

[App Logs] 
   ↓ (filebeat/fluent-bit)
[Kafka Topics]
   ↓
[Log Aggregator: Fluentd]
   ↓
[Elasticsearch / Object Storage (S3)]
   ↓
[Kibana / Grafana Loki]

核心组件:

组件 作用
filebeat 主机日志采集
fluent-bit 容器日志采集
Kafka 消息中间件,解耦采集和处理
Fluentd 日志清洗、过滤、路由
Elasticsearch 支持全文检索
S3 归档冷数据
Loki + Promtail 结合 Prometheus 做日志监控

为什么要同时保留 Elasticsearch 和 Loki?
因为我们既希望快速检索特定关键字,又能基于 metric 找到对应日志上下文,两者互补效果更好。


关键代码片段 & 配置示例

1. Filebeat 输出到 Kafka 的配置片段:

output.kafka:
  hosts: ["kafka-broker1:9092", "kafka-broker2:9092"]
  topic: '%{[fields.log_type]}'
  partition.round_robin:
    reachable_only: true
  required_acks: 1
  compression: gzip
  max_message_bytes: 1000000

这里我们根据日志类型动态分配 Topic,比如 app_logaccess_log 等,方便后续按需消费。

技术概念图解-2

2. Fluentd Kafka 插件配置:

<source>
  @type kafka
  brokers "kafka-broker1:9092,kafka-broker2:9092"
  topics app_log,access_log
  format json
  tag_prefix logs.
</source>

<match logs.app_log>
  @type elasticsearch
  host "es-host"
  port 9200
  index_name "logs_app_%Y%m%d"
  type_name _doc
</match>

这段配置从 Kafka 拉取日志,并插入 Elasticsearch。

3. Loki 接收日志的 Promtail 配置:

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

scrape_configs:
  - job_name: application_logs
    static_configs:
      - targets:
          - localhost
        labels:
          job: "application-logs"
          __address__: "localhost:12345"
    pipeline_stages:
      - regex:
          expression: '^(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(?P<level>\w+)\] (?P<message>.*)$'

实现方案图-1

Promtail 会监听日志路径,并将日志转发给 Loki 服务。


踩过的坑 & 解决方法分享

1. Kafka 数据堆积怎么办?

上线初期某天晚上,突然收到告警,Kafka 中积压了上百 GB 日志,Fluentd 消费不过来。

排查发现是 Elasticsearch 在某个时间段写入异常缓慢,导致 Fluentd 缓冲池撑满。

解决办法:

  • 加了 Redis Stream 作为二级缓冲
  • Fluentd 增加了 retry 机制
  • 对 Elasticsearch 写入速度做压测并扩容了两个节点

2. 日志丢失现象严重

一开始 filebeat 的默认配置里没有开启 ACK 确认机制,导致某些日志被“静默”忽略。

解决方式是在 filebeat.yml 中添加:

spool_size: 2048
publish_async: false

这样确保每条日志都真正写到了 Kafka 才从本地删除。

3. 磁盘 IO 不堪重负

因为 Elasticsearch 是每天一个 index,我们一开始没做 TTL 控制,导致磁盘暴涨。

解决方案很简单,加了 ILM(Index Lifecycle Management)策略:

{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_age": "1d",
            "max_size": "50gb"
          }
        }
      },
      "delete": {
        "min_age": "7d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

这样可以自动滚动 index 并删除旧数据。


最终成果与收获

新系统上线后,整体表现提升明显:

  • 日志延迟从原来的几分钟下降到秒级
  • 日志完整率达到 99.9%+
  • 查询响应速度提升 3~5 倍
  • 存储成本降低约 40%,归档转存 S3 显著减轻 ES 压力

更关键的是,运维同学现在可以通过 Kibana 和 Grafana 快速定位故障根因,节省了大量的排障时间。

此外,这次经历让我们意识到:一个好的日志体系不是靠堆组件,而是要在结构设计、标准化、上下游协同上下功夫。


我的几点建议与反思

如果你也在考虑搭建或升级日志系统,不妨参考以下几个方向:

✅ 标准化永远是第一位

无论你用什么工具,先制定好日志格式规范、采集方式、命名规则,否则后续维护起来极其痛苦。

✅ 不要追求“一步到位”

很多同学总想找个“终极方案”,其实没有银弹。可以根据当前痛点先解决最严重的瓶颈,再逐步迭代。

✅ 监控一定要提前布局

无论是采集端、传输通道还是下游存储,都要有完整的健康检查和报警机制。我们就是在日志积压之后才补上了 Prometheus + Grafana 的监控体系。

✅ 做好性能测试和容量规划

上线前一定要压测!压测!再压测!包括高并发场景下的 Kafka 吞吐、ES 写入速度、Filebeat 的负载能力等等。

✅ 技术选型要考虑团队能力

不要盲目追新。比如当时我们考虑过 Vector,但团队对 Rust 不熟悉,最后还是选择了熟悉的 Fluentd + Filebeat 组合。


写在最后:每一次“折腾”都是成长

回顾这次技术重构的过程,其实并不是那么顺利。有很多深夜都在调试 Kafka 消费、优化索引结构,甚至还要和业务部门沟通他们写的日志乱七八糟……

但正是因为经历了这些“磨合期”,我才更加理解了什么是“工程化的日志处理系统”。它不仅仅是几个工具搭在一起运行,更是涉及到可观测性、服务治理、团队协作等多个维度的复杂工程。

如果你也在做类似的技术尝试,别怕麻烦,也不要怕犯错。技术探索从来都不是一蹴而就的事,有时候就是得边走边看,边学边改——毕竟,真正的成长,往往藏在那些你不愿意重复的“坑”里。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝