记一次 Docker 环境下 MySQL 与 MongoDB 内存抢占导致 CPU 爆表的深度排查

现象描述

在日常运维中发现,服务器 CPU 负载突然飙升,通过 top 指令发现:
在这里插入图片描述

  • mysqld 进程 CPU 占用持续在 220% - 360% 波动。
  • mongod 进程 CPU 占用约 110% - 360%
  • 内存占用惊人:两个进程的常驻内存(RES)分别达到了 18.6GB21.7GB,几乎榨干了宿主机 46GB 的物理内存。
  • 即使手动执行 STOP SLAVE 停止 MySQL 主从同步,CPU 和内存负载依然没有明显下降。

排查过程与根因分析

1. 误区:主从同步是元凶?

初始怀疑是主从同步压力大。但通过 SHOW SLAVE STATUS 发现 SQL 线程已停止,且 Created_tmp_disk_tables 数量并不多。这说明高负载并非单纯由同步 SQL 引起,而是系统级资源耗尽引发的连锁反应。

2. 核心矛盾:配置与实际占用的巨大偏差

通过数据库内部指令发现一个诡异现象:

  • MySQL 的 innodb_buffer_pool_size 仅配置了 2GB
  • 但在 Docker 中,mysqld 实际占用了 19GB 内存。

根因点 1: 数据库在 Docker 容器中默认没有内存限制(Limit),它会识别宿主机的总内存(46GB)来申请资源。
根因点 2: 由于物理内存被 MySQL 和 MongoDB 强行占满了 90% 以上,系统触发了频繁的内存分页交换(Swapping)。CPU 爆表实际上是内核在处理极高频的 I/O 等待和内存回收。

3. Docker 层的资源失控

通过 docker stats 发现:

  • MySQL 容器 CPU 占用率显示为 784%(意味着跑满了 8 个核心)。
  • 两个容器都没有设置 MEM LIMIT,处于完全竞争状态。

解决方案:实施“资源配额制”

针对 Docker 部署的数据库,单纯修改 my.cnfmongod.conf 是不够的,必须在 容器层应用层 同时进行约束。

优化后的 docker-compose 配置建议:
services:
  mysql:
    image: mysql:8.0
    deploy:
      resources:
        limits:
          cpus: '4.0'    # 限制 CPU,防止单容器锁死宿主机
          memory: 16G    # 强制物理内存上限
    command: 
      - --innodb_buffer_pool_size=12G  # 核心:给内部缓存留足空间,但不超过容器限额

  mongo:
    image: mongo:latest
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 16G
    command: 
      - --wiredTigerCacheSizeGB=10    # 显式限制 Mongo 缓存


总结与反思

  1. 不要让数据库“裸奔”:在 Docker 中运行数据库,必须显式设置 deploy.resources.limits,否则在高负载下,容器会无节制地吞噬宿主机资源。
  2. 内外配合:容器的内存 Limit 应略大于数据库内部缓存(如 Buffer Pool),预留 25% 左右的空间给线程连接、排序缓冲区等。
  3. 重启是最后的防线:当内存发生严重溢出且停止同步无效时,重启容器是清理僵死事务、释放泄露内存、重置系统 I/O 状态最有效的手段。

运维建议命令

  • 查看容器实时资源docker stats
  • 释放宿主机 Page Cachesync; echo 3 > /proc/sys/vm/drop_caches
  • 进入容器抓取实时 SQLdocker exec -it <id> mysql -e "show full processlist;"

更多推荐