审计日志写入 ES 延迟 2 秒,如何在不丢日志的前提下把延迟降到 500 ms

解读

  1. 业务约束:审计日志不能丢,意味着任何“异步+内存队列”方案都必须有持久化兜底。
  2. 性能目标:P99 写入延迟从 2000 ms 降到 500 ms,而非平均延迟。
  3. 场景特征:写为主、近乎无更新;字段多、体积大;合规要求保存 3~7 年以上;ES 集群通常由运维或云厂商托管,改动权限受限。
  4. 面试意图:考察候选人能否把“测试→定位→优化”闭环讲清楚,既懂 ES 写入链路,又能给出可落地的“测试验证”计划,而不是堆叠配置参数。

知识点

  1. ES 写入链路:Client → HTTP/REST → Coordinating Node → Primary Shard → Replica Shard → Translog & Lucene Segment。
  2. 延迟构成:网络往返 + 协调节点队列 + 分片路由 + Translog sync + Segment refresh + 副本同步。
  3. 同步 durability 等级:
    • request(wait_for) ≥ index.translog.durability=async 且 translog.sync_interval ≤5s。
  4. 测试方法:
    • 用 esrally 或 JMeter 自定义 sampler,对同一索引模板分别压测“wait_for vs async+5s”两种 durability,对比 P99、P999、丢 0 条。
    • 用 async-profiler 抓 coordinating node 的 CPU 火焰图,确认是否因字段过多或 dynamic mapping 导致 200 ms+ 的 CPU 序列化。
  5. 高可用队列:Kafka、Pulsar、RocketMQ 都支持“同步刷盘 + 多副本”,可作为持久化缓冲;Logstash/Beats 开启 persistent queue 仅防进程崩溃,防不了宿主机掉电,需评估合规。
  6. 批量策略:ES bulk size 并非越大越好,需结合网卡带宽、GC 压力、 coordinating node 的 heap 做梯度测试;国内 8C16G 的 coordinating node 在 10 MB/次左右出现拐点。
  7. 副本数与分片数:热数据 1 副本即可,冷数据 0 副本降 IO;分片数 = 数据节点 CPU 核数 × 2~3 是常见经验,但需用 rally 压实验证。
  8. 国内云厂商限制:阿里云 ES 6.x/7.x 默认不允许关闭 translog.durability,需工单申请“自定义模板白名单”;腾讯云支持“异步 translog”但要求至少 3 个专用主节点。
  9. 合规红线:金融、运营商场景要求“日志落地即持久化”,因此任何“纯内存异步”方案必须被否决;测试报告里要给出“断电演练”截图,证明队列落盘。

答案

回答时分三步:测试定位、配置优化、架构兜底,并给出可量化的验证手段。

第一步:测试定位

  1. 用 JMeter 自定义 Java Sampler,单线程循环写 1 kB 审计文档,开启“connection keep-alive + gzip”,样本 5 万条,结果 P99=2.1 s。
  2. 在 coordinating node 上打开 TRACE 日志,看到“wait_for”同步等待 replica 平均 1.6 s,占比 80%,判定主瓶颈是副本同步。
  3. 用 rally 压测同样数据,把副本数从 1 降到 0,P99 立即降到 380 ms,确认副本为关键瓶颈;但生产不能丢高可用,需继续找折中。

第二步:配置优化(不丢日志)

  1. 索引级模板:
    “index.translog.durability”: “async”
    “index.translog.sync_interval”: “5s”
    “index.refresh_interval”: “5s”
    这样 Translog 每 5 s 刷盘一次,比默认的 request 同步刷盘节省 1.2~1.5 s;合规上 5 s 窗口可接受,因为宿主机掉电最多丢 5 s 数据,而审计场景通常允许 RPO≤30 s。
  2. 副本策略:保持 1 副本,但把“index.write.wait_for_active_shards”从默认 1 改成 0(即不等待 replica),写入返回前只保证 primary 落盘;由 translog 异步同步 replica,延迟可再降 400~600 ms。
  3. 批量与线程:
    业务端用 8 线程、每批 800 条、单条 1 kB,总 6~8 MB/次, coordinating node CPU 60% 左右,P99 稳定在 480 ms;继续加大批量无收益,反而因 GC 导致抖动。
  4. 网络与序列化:
    开启 http.compression: true,索引字段用“copy_to”合并常用查询字段,减少 30% 体积,网卡带宽从 900 Mb/s 降到 600 Mb/s,延迟再降 30 ms。
  5. 资源层:
    数据节点 Heap 31 GB 踩线,改用 16 vCPU 32 GB 的新规格,G1GC 停顿从 220 ms 降到 90 ms,对 P99 贡献约 50 ms。

第三步:架构兜底(极端场景不丢)

  1. 在应用与 ES 之间加 Kafka:
    生产者开启 acks=all、retries=Integer.MAX、enable.idempotence=true,保证“同步刷盘 + 多副本”后才返回,RPO=0;Kafka P99 写延迟 12 ms。
  2. Logstash 消费端:
    使用“persistent queue + queue.type: persisted”,队列文件放在 SSD,即使 Logstash 重启也不丢;批量 800 条 → ES,开启“pipeline.workers: 8 | batch.size: 800”,端到端 P99 480 ms。
  3. 断电演练:
    直接 kill -9 Logstash 进程并重启,验证 5 万条日志 0 丢失;用 rally compare 基准,P99 仍维持 480 ms,满足 500 ms 目标。

最终交付:

  • 测试报告含 rally 对比图、GC 日志、断电演练截图;
  • 线上灰度 20% 流量运行一周,P99 稳定在 450~480 ms,无丢日志;
  • 全量切流后,把索引生命周期从 30 天 rollover 提前到 7 天,减少热数据分片数,进一步降低长尾延迟。

拓展思考

  1. 如果合规要求 RPO=0 且延迟≤200 ms,该如何设计?
    提示:考虑“双写”策略——应用同步写本地 RocksDB+异步写 Kafka;RocksDB 提供毫秒级读,ES 仅做近实时检索,通过测试验证 RocksDB 单机能抗 3 万 TPS。
  2. 当审计日志达到 10 TB/天,ES 存储成本过高,如何在不丢数据的前提下把热数据延迟保持在 500 ms,同时冷数据成本降 70%?
    提示:热数据 ES+SSD 保留 24 h,冷数据转存 OSS+自研 Parquet 索引,通过 Hive/Spark 查询;用 rally 验证冷热分离后写入链路 P99 不变。
  3. 在私有云环境,宿主机为 1 Gb/s 老网卡,升级到 10 Gb/s 需采购流程 3 个月,如何通过软件层把延迟再降 100 ms?
    提示:开启“index.codec: best_compression”+“index.mapping.total_fields.limit: 2000”减少 40% 体积;用 msgpack 替换 JSON,批量体积降 25%,在 1 Gb/s 网卡下 P99 可再降 80~120 ms,测试需用 iperf 先确认网卡已跑满。