适用场景
服务运行正常,但在故障窗口里 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 默认会按来源控制日志洪峰。全局配置 RateLimitIntervalSec 和 RateLimitBurst 表示:在一个时间窗口内,超过突发阈值的相似日志会被抑制。它的目的,是防止失控进程把磁盘、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.conf 和 journald.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
LogRateLimitIntervalUSec 与 LogRateLimitBurst 是单元级覆盖项;显示为 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 当作默认修复方案,否则新的日志风暴可能拖垮整台主机。
方案二:修复日志风暴的源头
优先级从高到低通常是:
- 对重试增加指数退避、最大次数和熔断;
- 对相同异常按时间窗口聚合,保留计数器而不是重复堆栈;
- 将请求体、SQL 全量参数等高体积内容改为按需采样;
- 为依赖失败、重试次数和被限流次数建立监控告警。
如果服务本身连续输出十万条错误,提升 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-journald 的 Suppressed 记录确认事实,再分别处理“日志为什么爆发”“阈值是否合理”“证据是否可靠保存”三个问题。把限流参数、应用退避与集中采集一起治理,才能既保留故障证据,又不让日志系统成为新的故障源。
Discussion
评论