适用场景

服务运行正常,但在故障窗口里 journalctl 中恰好缺少应用最关键的一段日志;常见于 Java、Python、Nginx/OpenResty、容器运行时或短时间大量报错的 systemd 服务。运维人员往往把它误判为程序没有输出,实际日志可能已经被 systemd-journald 的限流机制主动丢弃。

本文适用于使用 systemd 和 journald 收集标准输出、标准错误或 syslog 日志的 Linux 主机。示例以 systemd 252+ 为例,较早版本的配置项名称相同。

现象描述

一次下游数据库故障使 API 服务在数十秒内连续打印重试异常。故障恢复后,应用日志中只保留了开头几条和最后一条,连接失败的调用链、请求 ID 与重试次数全部缺失。执行下面命令时,还会看到 journald 自己的告警:

journalctl -u systemd-journald --since '2026-08-08 10:00:00' \
  | grep -iE 'suppressed|rate limit|missed'

典型输出:

systemd-journald[612]: Suppressed 18432 messages from /system.slice/api.service

Suppressed 18432 messages 说明 journald 在该时间窗口没有把这 18432 条消息写入 journal;之后再调大保留天数、重新查询或从服务端重放都无法找回。

原理与可能原因

systemd-journald 默认会按来源控制日志洪峰。全局配置 RateLimitIntervalSecRateLimitBurst 表示:在一个时间窗口内,超过突发阈值的相似日志会被抑制。它的目的,是防止失控进程把磁盘、CPU 和 journal 空间耗尽。

常见触发原因包括:

  • 依赖不可用时,无退避的重试循环每次都打印完整异常栈;
  • 错误级日志记录在每个请求、每个循环或每个健康检查里;
  • 单元文件把高频调试日志直接输出到 StandardOutput=journal
  • 排障时临时开启 DEBUG 后忘记关闭;
  • 多个子进程共用同一个 cgroup 或同一日志来源,合并后形成突发流量。

不要把 “没有找到日志” 直接等价为 “代码没有执行”。先确认日志链路中的每一层:应用输出、systemd 捕获、journald 接收、持久化存储与日志平台采集。

排查步骤

1. 先确认是否发生过抑制

# 查看本次启动以来 journald 的抑制记录
journalctl -u systemd-journald -b | grep -i 'Suppressed'

# 只查看目标服务的故障时间段
journalctl -u api.service \
  --since '2026-08-08 10:00:00' \
  --until '2026-08-08 10:10:00' \
  -o short-precise

-b 限定当前启动周期,避免把历史事件混在一起;-o short-precise 会带微秒级时间戳,适合与网关、数据库或监控事件对齐。注意:抑制告警的来源可能显示为某个 cgroup,而非业务单元名,因此需要同时检查 systemd-journald 自身日志。

2. 查看当前生效配置,而不是只看配置文件

systemd-analyze cat-config systemd/journald.conf
journald --version

第一条命令会按实际优先级展开 /etc/systemd/journald.confjournald.conf.d/*.conf,能发现被镜像、自动化脚本或旧配置覆盖的值。重点检查:

[Journal]
RateLimitIntervalSec=30s
RateLimitBurst=10000
SystemMaxUse=2G
SystemKeepFree=1G

限流与容量是两件事:调大 RateLimitBurst 只会减少被限流的概率,不能避免 journal 因 SystemMaxUse 达到上限而轮转。必须一起评估磁盘容量和日志保留策略。

3. 确认服务的输出路径和实际速率

systemctl show api.service \
  -p StandardOutput -p StandardError -p SyslogIdentifier -p LogRateLimitIntervalUSec -p LogRateLimitBurst

# 统计一分钟内服务实际写入 journal 的条数
journalctl -u api.service --since '1 minute ago' -o cat | wc -l

LogRateLimitIntervalUSecLogRateLimitBurst 是单元级覆盖项;显示为 0 通常表示没有单元级限制、继续使用 journald 的全局策略。统计结果要结合异常发生时的峰值看,而不是只看恢复后的平均值。

4. 复核应用是否在放大错误

将同一请求 ID、异常类型和重试次数聚合后,往往能发现一条根因被打印了几千次。例如 Python 重试应当有退避,并将重复异常降采样:

import logging
import time

log = logging.getLogger(__name__)

for attempt in range(1, 6):
    try:
        result = call_dependency()
        break
    except TimeoutError as exc:
        # 仅首尾两次保留完整堆栈,避免同一异常刷屏
        if attempt in (1, 5):
            log.exception("dependency timeout, attempt=%s", attempt)
        else:
            log.warning("dependency timeout, attempt=%s: %s", attempt, exc)
        time.sleep(min(2 ** attempt, 30))
else:
    raise RuntimeError("dependency remains unavailable")

关键点是退避时间 min(2 ** attempt, 30):失败越多,重试越慢,既降低下游压力,也避免日志量按请求数与重试次数相乘。

治理方案

方案一:为关键服务设置合理的单元级阈值

不要先全局关闭限流。对确实需要保留故障细节的服务做有边界的单元级调整:

# /etc/systemd/system/api.service.d/logging.conf
[Service]
LogRateLimitIntervalSec=30s
LogRateLimitBurst=50000

应用配置后执行:

systemctl daemon-reload
systemctl restart api.service
systemctl show api.service -p LogRateLimitIntervalUSec -p LogRateLimitBurst

这会将该服务每 30 秒允许的突发量提高到 50000。阈值应来自压测或历史峰值,并为磁盘写入预留余量;不要把 LogRateLimitIntervalSec=0 当作默认修复方案,否则新的日志风暴可能拖垮整台主机。

方案二:修复日志风暴的源头

优先级从高到低通常是:

  1. 对重试增加指数退避、最大次数和熔断;
  2. 对相同异常按时间窗口聚合,保留计数器而不是重复堆栈;
  3. 将请求体、SQL 全量参数等高体积内容改为按需采样;
  4. 为依赖失败、重试次数和被限流次数建立监控告警。

如果服务本身连续输出十万条错误,提升 journald 阈值只会把问题从“证据丢失”变成“磁盘与 IO 被打满”。

方案三:保证日志可持久化并可导出

确认 journal 不只是内存态:

test -d /var/log/journal && echo 'persistent journal enabled'
journalctl --disk-usage

若目录不存在,可创建后重启 journald:

mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
systemctl restart systemd-journald

生产环境还应把 journal 或结构化应用日志导出到集中日志平台。主机本地 journal 是排障的第一现场,但不应是唯一副本。

验证示例

变更后,在预发布环境通过受控压测制造短暂错误峰值,再检查是否仍出现抑制记录:

before=$(journalctl -u systemd-journald -b | grep -ci 'Suppressed' || true)
# 在预发布执行受控压测或故障注入
sleep 60
after=$(journalctl -u systemd-journald -b | grep -ci 'Suppressed' || true)
printf 'suppressed records: before=%s after=%s\n' "$before" "$after"

同时观察 journalctl --disk-usage、磁盘空间、服务 P99 延迟和日志平台入库延迟。验证通过的标准不是“完全没有错误日志”,而是故障期间保留了足够的上下文,且日志系统没有造成额外资源风险。

预防清单

  • 为高频错误建立“每分钟异常数”和“重复异常数”指标;
  • Suppressed .* messages 纳入主机日志告警;
  • 每个关键服务明确日志级别、峰值量和单元级限流策略;
  • 发布前用依赖超时、DNS 失败等场景验证日志是否可追溯;
  • 定期演练 journal 持久化、轮转和集中采集链路。

总结

journald 限流不是故障根因,而是日志风暴下的保护措施。排查时先用 systemd-journaldSuppressed 记录确认事实,再分别处理“日志为什么爆发”“阈值是否合理”“证据是否可靠保存”三个问题。把限流参数、应用退避与集中采集一起治理,才能既保留故障证据,又不让日志系统成为新的故障源。