Docker exec 与容器内常驻服务压测结论

测试日期:2026-07-07;工作目录:[本地路径已隐藏];脚本:ws_daemon_bench.py
结论:在容器里先放一个常驻小服务,让它和外部保持一条长连接,再由外部通过这条连接下命令,速度会明显高于每次都调用 docker exec。 本次压测里的“短任务”具体就是在容器里执行 true,它几乎什么都不做,只用来测“启动一次命令”本身要花多久。 如果每条命令仍然要在容器里新启动一次 true,大约能做到 900-1200 次/秒;如果只是让常驻服务收到消息后直接回包,可以到 1 万-2.5 万次/秒。

关键结果

16.7 次/秒 每次都走 docker exec 执行 true
909 次/秒 长连接,逐条执行 true
1,246 次/秒 长连接,连续下发 true
1 万-2.5 万次/秒 长连接,只发空请求并立刻回成功

四种方式说明

方式 怎么跑 这组数字说明什么 怎么看
1. 每次都用 docker exec 外部每来一条命令,就让 Docker 进容器启动一次命令。本次命令是 true true 是 Linux 里的极轻量命令:直接成功退出,不打印内容。用它是为了尽量排除业务逻辑耗时,只看“启动命令”本身的成本。 写起来简单,但每条命令都要重新走一遍完整流程。这个环境里只有约 16 次/秒。
2. 长连接,只做空请求回包 容器里先启动一个常驻服务,它主动连到外部。外部发送的内容类似 {"kind":"noop"},意思是“不做事,只回包”。 服务收到后不执行 true,也不启动任何系统命令,只直接返回类似 {"rc":0} 的成功结果。这是在测“发消息、收消息”本身有多快。 如果以后很多操作可以直接在常驻服务里完成,不需要再启动新进程,这就是大致上限:约 1 万-2.5 万次/秒。
3. 长连接,逐条执行 true 外部还是一条一条地下命令;本次发送的内容类似 {"kind":"shell","cmd":"true"} 常驻服务收到后,在容器里执行一次 true,等它退出后再处理下一条。这相当于把 docker exec 换成长连接,但命令本身还是每次新启动。 这更接近“远程执行命令”的真实情况。这个环境里约 900 次/秒,比 docker exec 高很多。
4. 长连接,连续下发 true 外部不等上一条 true 返回,就先连续发一批 {"kind":"shell","cmd":"true"};服务按编号返回结果。 这组数字说明:如果任务很多,而且彼此独立,连续下发能让整体处理量更高。 适合大量小任务。这个测试里同时最多压 64 条 true 时,整体约 1,246 次/秒,但单条命令平均等待会变长。

这四种不是四个互斥产品方案,而是把问题拆开看: 先看老办法执行 true 有多慢,再看长连接只发空请求有多快,再看“仍然每条命令都执行一次 true”时能提升多少,最后看连续下发很多个 true 对大批量任务有没有帮助。

过程细节

普通 Bridge 网络:box-bh

方式 次数 按单条延迟折算 按真实总耗时折算 中位等待 95% 等待 99% 等待
每次都用 docker exec 执行 true 300 16.7 16.7 59.260 66.979 70.383
长连接,空请求回包 5000 10,175.9 10,175.9 0.094 0.113 0.132
长连接,逐条执行 true 5000 909.1 909.1 1.097 1.201 1.321
长连接,连续下发空请求 5000 486.6 24,801.6 2.046 2.243 2.491
长连接,连续下发 true 5000 19.6 1,246.3 51.827 60.464 66.579

说明:“按真实总耗时折算”更适合看系统整体一秒能处理多少条;“中位等待、95% 等待、99% 等待”更适合看单条命令要等多久。

Host 网络:box-ba

方式 次数 按单条延迟折算 按真实总耗时折算 中位等待 95% 等待 99% 等待
每次都用 docker exec 执行 true 300 15.8 15.8 62.760 71.164 74.573
长连接,空请求回包 300 9,661.9 9,661.9 0.102 0.123 0.192
长连接,逐条执行 true 300 934.0 934.0 1.088 1.155 1.190
长连接,连续下发空请求 300 745.2 5,624.0 1.084 1.813 2.019
长连接,连续下发 true 300 32.3 1,027.0 29.345 56.690 69.523

判断

多个容器同时跑会怎样

又补了一组测试:同时启动 1、2、3 个容器内常驻服务,外部同时给它们下发命令。 每个容器各执行 3000 次 true;同时也测了一组“空请求回包”,用来观察长连接本身的压力。

方式 总次数 整体每秒处理 单条中位等待 95% 等待 99% 等待
1 个容器,空请求回包 3000 21,887 次/秒 2.036 ms 2.146 ms 2.268 ms
2 个容器,空请求回包 6000 25,685 次/秒 3.944 ms 5.425 ms 6.546 ms
3 个容器,空请求回包 9000 29,008 次/秒 5.336 ms 8.974 ms 10.392 ms
1 个容器,连续下发 true 3000 1,443 次/秒 40.822 ms 56.776 ms 67.186 ms
2 个容器,连续下发 true 6000 2,016 次/秒 63.213 ms 68.909 ms 81.627 ms
3 个容器,连续下发 true 9000 3,383 次/秒 55.534 ms 59.819 ms 72.853 ms

结论:增加容器数量后,整体吞吐没有衰减,反而会上升;但不是完全线性上升。 例如执行 true 时,1 个容器约 1443 次/秒,2 个容器约 2016 次/秒,3 个容器约 3383 次/秒。 也就是说,总处理量提高了,但每个容器平均分到的速度会比单独跑时低一些。

原因也比较直观:多个容器同时跑时,宿主机 CPU、进程创建、网络收发、Python 调度都会一起竞争。 对空请求来说,单条等待从约 2 ms 增加到约 5 ms;对执行 true 来说,单条等待基本在 40-65 ms 区间。 所以如果目标是“总量更高”,多容器有帮助;如果目标是“单条命令尽快返回”,容器数量和同时下发数量都要控制。

复跑命令

uv run --with websockets python ws_daemon_bench.py \
  --container box-bh \
  --host 0.0.0.0 \
  --connect-host [内网地址已隐藏] \
  --count 5000 \
  --window 64 \
  --command true \
  --skip-docker-exec
uv run --with websockets python multi_daemon_bench.py \
  --containers box-bh,box-bos,box-ba \
  --count-per-container 3000 \
  --window 64 \
  --command true \
  --verbose
后续如果要做成正式方案,建议补上身份校验、请求编号、超时、取消、同时处理数量限制、命令输出实时返回,以及容器重启后的自动重连。