真正破解“connection refused”或“too many open files”瓶颈需宿主机、Docker守护进程、容器运行时、应用启动逻辑四层协同:必须显式配置软硬限,匹配应用行为,防止脚本重置,且宿主机limits.conf、systemd LimitNOFILE及dockerd均需同步调优并交叉验证生效。

要真正破解“connection refused”或“too many open files”这类海量并发连接拒绝瓶颈,单靠 docker-compose.yml 里写几行 ulimits 是远远不够的。关键在于让容器内的应用进程,能稳定、完整地继承到你设定的资源上限——而这需要宿主机、Docker守护进程、容器运行时、应用启动逻辑四层协同生效。
ulimits 配置只是入口,不是终点
在 docker-compose.yml 中设置 ulimits,只是告诉 Docker 引擎:“请把这个限制传给容器主进程”。但它不保证容器内服务(比如 Nginx worker、Java 应用主线程)一定能拿到并长期维持该值。
-
必须显式指定软硬限:不能只写
nofile: 65536,要拆成soft: 65536和hard: 65536,否则部分运行时可能只设软限,导致实际可用值远低于预期 - 值要匹配应用行为:Nginx 需要高 nofile + 合理 worker_connections;Java 应用更依赖 nproc;Redis 客户端密集型服务则需同时调高 nofile 和 memlock
-
避免覆盖失效:若镜像中用了 su-exec、gosu 或自定义 ENTRYPOINT 脚本,它们常会重置 ulimit;建议在脚本开头加
ulimit -n $ULIMIT_NOFILE显式恢复
宿主机限制是硬性天花板
Docker 容器无法突破宿主机内核和用户级限制。即使 Compose 写了 nofile=1048576:1048576,若宿主机没放开,容器一启动就会静默降级回默认值(通常是 1024/4096)。
- 检查当前生效值:
ulimit -Sn && ulimit -Hn(注意:这是当前 shell 的值,非服务用户) - 确认服务用户真实限制:
su - www-data -c 'ulimit -n'(把www-data换成你的应用用户) - 永久放开:在
/etc/security/limits.conf中添加对应用户行,例如:www-data soft nofile 1048576www-data hard nofile 1048576 - 别漏 PAM:确保
/etc/pam.d/common-session包含session required pam_limits.so
systemd 管理的服务必须单独声明 LimitNOFILE
绝大多数生产环境的 Docker 守护进程(dockerd)由 systemd 托管。而 systemd 默认忽略 /etc/security/limits.conf,所以即使宿主机配好了,dockerd 自身的 nofile 仍可能是 1024 —— 这直接决定了它启动的所有容器的默认 ulimit。
- 查 dockerd 当前限制:
systemctl show docker --property LimitNOFILE - 永久提升:
sudo systemctl edit docker.service,填入:[Service]LimitNOFILE=1048576 - 重载并重启:
sudo systemctl daemon-reload && sudo systemctl restart docker - 验证:重启后运行一个测试容器,执行
ulimit -n,应返回 1048576
验证是否真生效,不能只看配置文件
配置写得再漂亮,不验证就等于没配。进容器后,要用以下方式交叉确认:
-
ulimit -a | grep -E "(open|process)"→ 看输出是否与 Compose 中一致 -
cat /proc/1/limits | grep "Max open files"→ 查 init 进程(PID 1)的真实限制,排除 shell 层干扰 - 对 Nginx 类服务,还要确认:
ps aux | grep nginx找 worker 进程 PID,再执行cat /proc/<pid>/limits | grep "Max open files"</pid>,确保 worker 继承到位 - 观察日志:高并发压测时,不再出现
accept() failed (24: Too many open files)或java.io.IOException: Too many open files

















