Composer并发下载未变快是因为Keep-Alive需客户端与服务端双向配合,且存在三大限制:同一host+port才复用连接、cURL未设CURLOPT_TCP_KEEPALIVE导致NAT回收连接、镜像服务器未返回Connection: keep-alive响应头。

为什么 Composer 并发下载没变快,明明开了 keep-alive?
Keep-Alive 不是开关一开就自动复用连接——它依赖客户端和服务端双向配合,且 Composer 的 HTTP 客户端实现有隐式限制。常见现象是 Downloading 日志并行滚动,但每个请求仍走新 TCP 连接(ss -tan | grep ESTAB | wc -l 持续飙升),说明复用没生效。
关键原因有三个:
- Composer 2.2+ 默认启用 keep-alive,但只对「同一 host + port」的请求复用;若镜像 URL 带路径差异(如
https://mirrors.aliyun.com/composer/vshttps://mirrors.aliyun.com/composer/packs/),会被视为不同 host,连接池不共享 - PHP cURL 封装层未显式设置
CURLOPT_TCP_KEEPALIVE,操作系统级心跳不触发,NAT 或中间设备 60 秒后静默回收连接 - 镜像服务器未返回
Connection: keep-alive响应头(部分老旧 CDN 或自建中转服务会忽略),客户端收到Connection: close后强制断连
验证方式:抓包看响应头,或加 -vvv 日志里搜 Reusing existing connection —— 没这句就是没复用。
如何让 Composer 真正复用 TCP 连接?
不是改 PHP 配置,而是控制 Composer 自身的 HTTP 客户端行为。它底层用的是 composer/installers 封装的 HttpDownloader,复用逻辑藏在 Pool 和 HttpClient 里。
实操要点:
- 确保所有镜像 URL 归一化到同一根域名:比如全用
https://mirrors.aliyun.com/composer/(结尾带/),别混用https://mirrors.tencent.com/composer(无尾斜杠)或带子路径的地址 - 升级到 Composer ≥ 2.5,旧版本(如 2.2.x)对
http-max-concurrent-downloads的连接池管理有 race condition,高并发下复用率骤降 - 手动注入 keep-alive 参数:在
composer.json里加"config": {"http": {"timeout": 300, "max-http-headers-size": 8192}},其中max-http-headers-size防止因响应头过大被截断导致 keep-alive 识别失败 - 禁用
COMPOSER_NO_TLS=1—— TLS 1.3 支持 0-RTT 和 session resumption,比明文 HTTP 更利于复用;强行降级反而破坏复用链路
中转服务器侧必须做的 TCP 层调优
如果你自己搭了 Composer 镜像中转服务(如 Nginx + proxy_cache),keep-alive 复用效果取决于服务端配置,而非客户端单方面努力。
重点检查三项:
- Nginx 的
proxy_http_version 1.1必须开启,否则默认用 HTTP/1.0,不支持 keep-alive -
proxy_set_header Connection ''—— 清空上游传来的Connection头,避免把close透传给 Composer 客户端 -
keepalive_timeout 60s和upstream keepalive 32要配对:前者控制客户端连接空闲超时,后者控制 upstream 连接池最大空闲数;设太小(如keepalive 4)会导致连接池过早释放,复用率归零
验证命令:curl -I https://your-mirror.com/packages.json | grep -i "connection\|keep-alive",应看到 Connection: keep-alive 且无 close 字样。
TIME_WAIT 爆炸时,tcp_tw_reuse 怎么配才真正起作用?
当 ss -tan state time-wait | wc -l 超过 15000,且 cat /proc/sys/net/ipv4/tcp_max_tw_buckets 接近上限,说明短连接风暴已压垮本地端口池。此时 net.ipv4.tcp_tw_reuse=1 是解药,但极易配错。
致命误区:
- 只改
tcp_tw_reuse不配tcp_timestamps=1—— 内核直接跳过复用逻辑,日志里完全没提示 - 在容器里改 sysctl 却没加
--sysctl net.ipv4.tcp_tw_reuse=1启动参数,Docker 默认网络命名空间不继承宿主机配置 - PHP-FPM 池用了
systemd的PrivateNetwork=true,导致 sysctl 设置被隔离,需在php-fpm.d/www.conf里加systemd-sysctl = /etc/sysctl.d/99-composer.conf
正确顺序:先确认 sysctl net.ipv4.tcp_timestamps 输出为 1,再设 net.ipv4.tcp_tw_reuse=1,最后跑 composer install -vvv 观察 TIME_WAIT 数是否稳定在 2000 以下。超过这个数,说明复用没生效,回头查命名空间或时间戳。
最易被忽略的一点:tcp_tw_reuse 只对「主动发起连接的客户端」有效。如果你的 Composer 进程跑在 Kubernetes Pod 里,而镜像中转服务也部署在同一集群内,那连接其实是 Pod → Service → 中转 Pod,中间经过 kube-proxy,此时复用发生在 kube-proxy 层,不是你的 PHP 进程 —— 这种场景下调 sysctl 没用,得优化 Service 的连接跟踪策略。











