Halo 容器高负载排障复盘(Docker 内存限制)
环境:2 核云服务器 · Docker 部署 Halo 博客 · 容器内存限制 512MiB(安装时为防爆内存手动设置)
日期:2026-10-24
背景:在 Halo 后台发文时服务明显卡顿,登上服务器排障,全程由表及里定位到 Docker 内存限制,最终动态扩容解决。
一、告警现象
- 博客后台操作明显卡顿
uptime:load average 高达 8 / 9 / 11,远超 2 核 CPU 的合理负载(2 核满载约为 2)top:us 0.5%、sy 2.8%,CPU 本身几乎不忙;wa高达 90% → 高负载由磁盘 I/O 等待贡献free -h:available还有 256M、swap 还剩 1G —— 宿主机内存看似正常df -h:磁盘空间充足
⚠️ 此处出现关键矛盾:CPU 几乎空闲(us+sy≈3%)但 load 很高,且 wa 极高。
结论方向:负载不在计算,而在I/O 等待——通常是内存不足引发换页/GC 抖动,或磁盘本身故障。
二、排查过程
| 步骤 | 命令 | 结果 | 结论 |
|---|---|---|---|
| 1. 确认 I/O 等待来源 | top |
us 0.5% / sy 2.8% / wa 90% | 高负载 ≈ I/O 等待,排除 CPU 计算瓶颈 |
| 2. 查宿主机内存 | free -h |
available 256M、swap 余 1G | 宿主机层面内存尚可,暂不深挖 |
| 3. 查磁盘空间 | df -h |
充足 | 排除磁盘写满 |
| 4. 联想到部署时的限制 | 回忆安装 Halo 时为防爆内存设置了 512MiB | —— | 转向检查 Docker 容器 |
| 5. 查容器实时资源 | docker stats |
Halo 容器内存 84.02%(430.2MiB / 512MiB),CPU/网络极低 | 容器逼近内存上限,OOM 高危 |
| 6. 查 JVM 配置 | 容器内 ps -ef \| grep java(精简镜像无 jdk 工具,此法有效) |
-Xms256m -Xmx256m |
JVM 堆配置本身合理,问题在容器总限额太紧 |
三、根因分析
容器 512MiB 的总限额对整个 Java 进程树过于紧张:
- JVM 堆固定 256m,但进程还有堆外内存、Metaspace、线程栈、JIT 代码缓存等开销,加上容器内其他进程,430MiB/512MiB 属于高危水位
- 内存逼近 cgroup 上限后,内核回收页缓存、JVM 频繁 GC,文件读写被迫直接落盘 → 表现为磁盘 wa 飙高、load 虚高,而 us/sy 几乎不动
- 换个视角:宿主机
free显示 available 正常,是因为限制发生在容器(cgroup)层面——宿主机有内存,容器却用不到
📌 教训一:
free正常 ≠ 容器内存正常。用了 cgroup 限制后,必须看docker stats。
四、解决方案
动态扩容容器内存限制(不重启容器):
docker update --memory 662m 31597ba84f7c
💡
docker update可以在线调整运行中容器的资源限制,无需 down/up,业务不中断。
注意:新限额不能低于容器当前实际用量,否则会被拒绝。
五、验证
docker stats --no-stream
- 内存上限已更新为 662MiB
- 使用率从 84.02% 降至 65.37%,进入健康水位
- 博客后台操作恢复流畅,
uptime负载逐步回落
六、经验沉淀
- load 高先看 wa:us/sy 低 + wa 高 = 瓶颈在 I/O 等待,别去升级 CPU
- cgroup 环境下双层视角:宿主机内存正常不代表容器不紧张,
docker stats是容器排查第一入口 - JVM 容器内存预算公式:容器限额 ≈
-Xmx+ 堆外/Metaspace(约堆的 50%~100%)+ 余量,512MiB 配 256m 堆太贴边 docker update --memory可在线扩容,应急首选;但这是"止血",改完记得同步更新 docker-compose.yml / 部署脚本,防止下次重建容器配置回退- 长期方案:Halo 建议 ≥1G 限额;或加宿主机 swap 并设置
--memory-swap明确换页策略,同时配置容器内存告警
评论