TL;DR
当你在生产服务器上排查故障,却发现 journalctl -u your-service 的输出中间有大段空白------不是服务没打日志,而是 systemd-journald 的速率限制功能静默丢弃了它们。即使你在 /etc/systemd/journald.conf 中设置了 RateLimitInterval=0 和 RateLimitBurst=0,日志仍然可能被丢弃。因为 systemd 有两层日志速率限制:全局的 journald.conf 和每个服务自身的 LogRateLimitIntervalSec/LogRateLimitBurst。更隐蔽的是,RateLimitInterval(旧参数名)不生效时需要用 RateLimitIntervalSec 替代,且 journald 重启后才能真正加载新配置。
一、事故现场:日志里出现诡异的"Suppressed"消息
某天凌晨 3 点,你的监控告警响了------生产服务出现异常。你 SSH 登录服务器,第一反应:
bash
journalctl -u my-app.service --since "2 hours ago"
输出看起来正常,但仔细一看,日志中间出现了这样的行:
python
systemd-journal[328]: Suppressed 864 messages from /system.slice/my-app.service
systemd-journal[328]: Suppressed 1532 messages from /system.slice/my-app.service
systemd-journal[328]: Suppressed 421 messages from /system.slice/my-app.service
这意味着 systemd-journald 在你的服务日志最密集的时间段,静默丢弃了总共 2817 条日志消息。
你心里一沉------这些被丢弃的日志里,可能恰恰包含了故障发生时的关键错误堆栈。
更糟糕的情况是:有些 systemd 版本在日志被丢弃时甚至不打印 "Suppressed" 消息(见 systemd-stable Issue #6),日志就那么悄无声息地消失了,你根本不知道丢了什么。
二、根因分析:systemd-journald 双层限速机制
2.1 journald 限速的工作原理
systemd-journald 默认启用了日志速率限制,配置在 /etc/systemd/journald.conf 中:
ini
RateLimitIntervalSec=30s
RateLimitBurst=10000
含义:在 30 秒的时间窗口内,同一个服务最多允许产生 10000 条日志。超过这个数量后,该时间窗口内后续的所有日志消息都会被丢弃,直到下一个 30 秒窗口开始。
默认值 RateLimitBurst=10000 看起来很高,但对于高吞吐量的服务(如每秒数百条 debug 日志的网关、消息队列消费者、大量短连接的 API 服务),这个阈值很容易被突破。
2.2 最常见的误区:改了配置却不生效
很多运维人员在发现问题后,第一反应是去修改 journald.conf:
bash
# 修改 /etc/systemd/journald.conf
RateLimitInterval=0
RateLimitBurst=0
# 然后重启 journald
systemctl restart systemd-journald
但日志依然被限速。原因有三:
原因一:参数名写错了
RateLimitInterval 是旧版参数名。在较新版本的 systemd 中,正确的参数名是:
| 旧参数名(已废弃) | 新参数名(正确) |
|---|---|
RateLimitInterval |
RateLimitIntervalSec |
RateLimitBurst |
RateLimitBurst(未变) |
如果你用的是 RateLimitInterval=0 而不是 RateLimitIntervalSec=0,journald 会使用默认值 30s,你的配置被静默忽略。
原因二:服务级别的限速覆盖了全局配置
这是最隐蔽的陷阱。systemd 允许每个服务单独设置日志速率限制 ,通过 systemd.exec(5) 中的参数:
ini
# 在 .service 文件中
[Service]
LogRateLimitIntervalSec=10s
LogRateLimitBurst=5000
服务级别的 LogRateLimit* 参数优先级高于 journald.conf 中的全局 RateLimit* 参数。
根据 ServerFault 上 U. Windl 的回答:
"If a service provides rate limits for itself through
LogRateLimitIntervalSec=and/orLogRateLimitBurst=in systemd.exec(5), those values will override the settings specified here."(如果服务通过 systemd.exec(5) 中的LogRateLimitIntervalSec=和/或LogRateLimitBurst=提供了自身的限速值,这些值将覆盖 journald.conf 中的设置。)
这意味着即使你禁用了全局限速,只要某个 .service 文件里定义了 LogRateLimitIntervalSec,该服务的日志仍然会被限速。
原因三:journald 需要完整重启
执行 systemctl restart systemd-journald 时,journald 会先停止再启动。但在停止期间,新产生的日志由 systemd 的 /run/systemd/journal/stdout socket 缓冲。如果 journald 重启后没有重新读取配置(某些版本确实存在这个问题),旧的限速配置仍然生效。
验证 journald 是否加载了你的配置:
bash
# 查看 journald 当前运行时参数
journalctl --no-pager -u systemd-journald | grep -i "rate"
# 或者检查 journald 进程的环境
tr '\0' '\n' < /proc/$(pidof systemd-journald)/environ | grep -i RATE
三、排查步骤:确认日志是否被静默丢弃
步骤 1:检查是否有 "Suppressed" 消息
bash
journalctl | grep -i "suppressed"
如果有输出,说明确实有日志被丢弃。数字越大,问题越严重。
步骤 2:查找被限速最严重的服务
bash
journalctl | grep "Suppressed" | awk -F'from ' '{print $2}' | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
步骤 3:对比应用实际输出量与 journald 记录量
临时将应用日志同时写入文件:
bash
# 修改 .service 文件,添加:
# StandardOutput=journal+console
# 然后用 tee 将日志也写入文件
对比文件行数和 journalctl | wc -l,如果差异很大,说明日志在被限速丢弃。
步骤 4:检查服务级别的限速配置
bash
# 查看服务的完整配置(包括 override)
systemctl cat my-app.service | grep -i "RateLimit\|LogRate"
四、解决方案:彻底消除日志限速
方案 A:禁用全局日志限速(推荐用于测试/开发环境)
bash
# 编辑 /etc/systemd/journald.conf
cat >> /etc/systemd/journald.conf << 'EOF'
RateLimitIntervalSec=0
RateLimitBurst=0
EOF
# 重启 journald
systemctl restart systemd-journald
# 验证
journalctl | grep -i "suppressed" | tail -5
注意:
- 一定要用
RateLimitIntervalSec而不是RateLimitInterval - 如果设置为 0 仍然有 Suppressed 消息,执行步骤 4 检查服务级别限速
方案 B:调整全局限速到合理的值(推荐用于生产环境)
完全禁掉限速可能导致日志洪水淹没磁盘。更好的做法是根据实际日志量调大阈值:
bash
cat >> /etc/systemd/journald.conf << 'EOF'
# 1 秒窗口内允许 50000 条消息
RateLimitIntervalSec=1s
RateLimitBurst=50000
EOF
方案 C:禁用特定服务的日志限速
如果只有一个服务产出大量日志,不要动全局配置,只禁用该服务的限速:
bash
# 创建 override 文件
mkdir -p /etc/systemd/system/my-app.service.d
cat > /etc/systemd/system/my-app.service.d/override.conf << 'EOF'
[Service]
LogRateLimitIntervalSec=0
LogRateLimitBurst=0
EOF
systemctl daemon-reload
systemctl restart my-app.service
方案 D:日志量过大的根本性解决
如果你的服务产生如此大的日志量以至于触发了限速,应该从根本上解决:
- 调整日志级别:生产环境不应使用 DEBUG 级别,改为 INFO 或 WARN
- 结构化日志采样:对重复性高的日志做聚合/采样,例如每 1000 次打印一条汇总
- 日志分流:将高频 debug 日志写入专用文件而非 journald
bash
# 在 .service 文件中重定向 stderr 到文件(绕过 journald)
[Service]
StandardError=file:/var/log/my-app-debug.log
五、参数速查表
| 参数 | 作用域 | 默认值 | 说明 |
|---|---|---|---|
RateLimitIntervalSec |
journald.conf(全局) | 30s |
限速时间窗口 |
RateLimitBurst |
journald.conf(全局) | 10000 |
时间窗口内允许的最大消息数 |
LogRateLimitIntervalSec |
.service 文件(单服务) | 继承全局 | 覆盖全局 RateLimitIntervalSec |
LogRateLimitBurst |
.service 文件(单服务) | 继承全局 | 覆盖全局 RateLimitBurst |
优先级 :服务级 LogRateLimit* > 全局 RateLimit*
彻底关闭限速 :将两个值都设为 0
六、常见误区与避坑清单
| # | 误区 | 正确做法 |
|---|---|---|
| 1 | 只改了 journald.conf 就认为限速已关闭 |
检查每个 .service 文件中的 LogRateLimit* |
| 2 | 用 RateLimitInterval= 而不是 RateLimitIntervalSec= |
systemd ≥235 必须用 RateLimitIntervalSec |
| 3 | 没有 systemctl daemon-reload 就重启服务 |
修改 .service override 后必须先 daemon-reload |
| 4 | 认为 systemctl restart systemd-journald 立即生效 |
有些版本需要完全重启或 reload 整个 systemd |
| 5 | 生产环境完全关闭限速导致磁盘爆炸 | 调大而非关闭,配合 SystemMaxUse= 控制日志量 |
七、总结
systemd-journald 的日志限速是一个典型的运维陷阱:默认行为在低负载时对你完全透明,但在服务日志量突然增大时(比如流量洪峰、重试风暴、死循环打印),它会静默丢弃你的日志------而这些丢弃的日志往往是最需要的关键信息。
排查的核心思路只有三步:
journalctl | grep -i suppressed确认是否有日志被丢弃- 检查
journald.conf的全局配置和服务级别的LogRateLimit*override - 对特定服务调大限速或关闭限速
记住:RateLimitInterval=0 不生效时,先检查参数名是不是写成了旧版,再检查服务自己有没有 override。
原始出处:
- ServerFault: systemd-journal Rate Limit
- Arch Linux Forums: systemd-journald does not honor rate limit settings
- Red Hat Knowledgebase: Unable to set RateLimitInterval= and RateLimitBurst= in journald
- systemd-stable Issue #6: journald: silent loss of messages
- systemd.exec(5) man page: LogRateLimitIntervalSec / LogRateLim...
- journald.conf(5) man page: RateLimitIntervalSec / RateLimitB...