日志存储与索引:ELK架构、时序数据库

日志存哪,怎么查,一直是个绕不开的大问题。我早期做审计系统时,日志直接往文本文件里塞,结果出事了:想查一条关键记录,grep跑了十几分钟,磁盘IO直接打满。嗯,那之后我才明白,存储结构设计不好,再牛的检测模型也白搭。

为什么日志存储这么重要?

审计日志有几个特点:量大、写多读少、需要全文检索。你想想看,每天几千万条,偶尔要查某个用户ID在某个时间段的所有操作。传统数据库 insert 慢,like 查询更慢。说白了,我们需要一个能扛高频写入、又能快速搜索的存储底座。

目前主流方案就两派:ELK(Elasticsearch + Logstash + Kibana) 和 时序数据库。我的习惯是,纯文本日志用 ELK,时序指标类数据用 TSDB。但实际项目中,很多人混着用,下面我具体拆开讲。

ELK架构:经典三板斧

ELK 这三个组件,各管一摊:

  • Logstash:数据采集、解析、过滤。从文件、网络、Kafka 等源拉日志,还能做字段提取。
  • Elasticsearch:存储 + 索引 + 搜索。核心引擎,倒排索引让关键词检索快到飞起。
  • Kibana:可视化 + 仪表盘。直接在上面画折线图、看异常分布,方便。

我在项目中用过两套 ELK 集群,一套管网络设备日志,一套管应用日志。Logstash 那边配了 8 个 pipeline,各自处理不同格式的数据。刚开始忘了配过滤规则,结果客户端发来的 JSON 里混了脏字符,整个索引就崩了。血的教训:Logstash 的 filter 一定要先做数据清洗。

ELK 架构日志处理流程 日志源 文件 / Syslog / TCP Logstash 采集 / 解析 / 过滤 Elasticsearch 存储 / 索引 / 搜索 Kibana 可视化 / 仪表盘 倒排索引原理 不是按行扫描,而是先建“词→文档” 映射表,查询复杂度 O(1) 级别。 按时间分区 每天自动生成一个新索引 (logstash-2024.12.06), 查询只扫特定分区,速度快。

时序数据库:另一种选择

如果你手里的日志本质上是 数值指标(例如 CPU 使用率、响应时间、连接数),那用 ELK 反而大材小用。时序数据库(比如 InfluxDB、TimescaleDB、VictoriaMetrics)天然优化时间戳范围内的聚合查询。

我比较过两者的写入性能:同样单机配置,InfluxDB 写入吞吐可以到每秒 50 万点,Elasticsearch 大概在 10 万条左右(当然取决于文档大小)。但 Elasticsearch 的搜索灵活性远超 TSDB。举个例子:你要查“用户ID=10086 在 15:00~15:05 之间所有操作详情”,ES 一条 regex 就搞定,TSDB 得绕一大圈。

维度ELK (Elasticsearch)时序数据库 (InfluxDB)
数据模型文档 (JSON)点 (measurement + tag + field)
查询方式全文检索 + 聚合时间范围 + 标签过滤 + 聚合函数
写入性能中 (10^4~10^5 条/秒)高 (10^5~10^6 点/秒)
存储压缩一般 (倒排索引占空间)优秀 (列式压缩)
适用场景非结构化文本日志、安全审计监控指标、传感器数据、性能曲线

我的小建议:如果日志里同时包含文本和指标(比如 Syslog 里既有错误消息又有 CPU 数值),可以双写:文本进 ES,指标进 TSDB。别嫌麻烦,后期查询效率差距非常明显。

我的选型建议

回到审计日志异常检测这个场景,我基本都是用 ELK。原因很简单:我们需要拿到原始日志全文做特征提取,而不是只关心数值聚合。举个例子,用户登录失败的日志,里面包含源 IP、User-Agent、时间、失败原因,全是字符串。TSDB 对字符串支持偏弱,不如 ES 的倒排索引灵活。

那什么时候用 TSDB?如果你要做实时异常检测的基线模型(比如检测流量突增、CPU 异常飙升),TSDB 的连续聚合查询特别适合。我在另一个监控项目里,用 VictoriaMetrics 存储了 6 个月的指标,查询 95 分位响应时间直接秒出,换成 ES 要跑好久。

实践:搭建一个简易日志存储方案

这里给一套我常用的快速配置,适合单机开发环境:

# 1. 启动 Elasticsearch (单节点)
docker run -d --name es01 -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  docker.elastic.co/elasticsearch/elasticsearch:8.10.0

# 2. 用 Filebeat 代替 Logstash (更轻量)
# filebeat.yml 配置
filebeat.inputs:
  - type: log
    paths:
      - /var/log/audit/*.log
output.elasticsearch:
  hosts: ["localhost:9200"]
  index: "audit-%{+yyyy.MM.dd}"

# 3. Kibana 配置索引模式
# 访问 http://localhost:5601
# 创建索引模式 audit-* ,时间字段选 @timestamp

注意:Filebeat 比 Logstash 轻很多,适合只做采集转发,不做复杂过滤。如果要做字段提取(比如从日志里把 IP 单独抽出来),还是得上 Logstash 的 grok 插件。

我曾经踩过的坑: 给 ES 的 mapping 设了错误的类型。比如把 IP 字段设成了 text,结果之后想按 IP 做聚合(比如 group by 源 IP 统计请求数),发现性能极差。正确的做法:对于不做全文搜索的字段,用 keyword 类型;对 IP 字段,用 ip 类型,这样 ES 可以做精确匹配和范围查询。

避坑指南

  • 索引生命周期管理(ILM):别等磁盘满了才想起来清理。ILM 可以自动按时间滚索引,比如保留 30 天,到期删除或 cold stage 压缩。我第一次用的时候就忘了开,三个月后磁盘爆了,直接导致整个集群不可用。
  • 分片数与副本数:审计日志一般是冷数据,副本数设 1 就行。分片数按数据量预估,一个分片最好不要超过 50GB。我习惯每天一个索引,分片数 = (期望写入条数 / 5000万) 向上取整。
  • 字段映射要提前定义:动态映射虽然方便,但容易造成类型不匹配。建议把通用的字段(timestamp, user_id, action, ip)手动 mapping 好,其他字段用 dynamic: false 或 dynamic: strict 控制。