Composer下载失败多为假超时而非真丢包,因http.timeout默认60秒过短,弱网下TLS握手或首字节等待即触发CURL error 28,此时内核仍在重传但应用层已退出。

Composer下载失败到底是真丢包还是假超时
绝大多数 Composer 下载失败不是链路真丢包,而是 TCP 超时重传被提前放弃——http.timeout 默认 60 秒,但弱网下一次 TLS 握手或首字节等待就可能卡满这个阈值,触发 CURL error 28,此时内核其实还在重传,而 Composer 已经弃疗。
关键判断点:
- 抓包看
tcp retransmission是否持续出现(说明内核在重传,但应用层已退出) - 查日志是否含
CURL error 28或Operation timed out(典型假丢包) - 对比
ping -c 5 packagist.org和curl -v --connect-timeout 5 https://packagist.org响应差异:前者 ICMP 不丢,后者 HTTPS 卡住,基本锁定是 TLS 层或 HTTP 层超时,非物理丢包
为什么--retries=5在弱网下几乎无效
这个参数只控制 dist 包下载的重试轮数,且每轮仍用默认 http.timeout;它不改变单次请求的生存时间,也不启用指数退避。更糟的是:
-
--retries对git clone完全不起作用(私有包 fallback 到 source 时失效) - 旧版 Composer(如 2.1.x)会静默忽略该参数,实际仍是 3 次
- 重试前不清理
vendor/和composer.lock,残留损坏文件会导致下一轮校验失败(如filemtime(): stat failed) - 即使重试成功,若中间某次写入了部分 zip 文件,后续解压仍会报
corrupted archive
绕过 Composer 默认逻辑的 Shell 封装方案
真正抗抖动的重试必须控制三件事:清状态、延等待、切协议。直接改 Composer 源码不现实,shell 封装最可控:
for i in {1..3}; do
rm -rf vendor/ composer.lock
COMPOSER_NETWORK_TIMEOUT=300 composer install --no-interaction --prefer-dist && break
sleep $((i * 15))
done说明:
-
COMPOSER_NETWORK_TIMEOUT=300覆盖http.timeout,单位秒,让单次请求活过运营商 NAT 超时(常见于国内移动/联通网络) -
--prefer-dist强制走 zip 包,避开git clone的 SSH 连接不稳定问题 - 每次失败都
rm -rf vendor/ composer.lock,避免缓存污染导致重复失败 -
sleep $((i * 15))实现简易指数退避(15s、30s、45s),比固定 delay 更适应 RTT 波动
镜像源配置的常见误解与兜底策略
很多人以为配了多个 repositories 就能自动 fallback,实际上 Composer 的仓库匹配是元数据驱动的:只有首个源返回明确 404,才会查下一个;遇到 502、timeout、DNS fail 直接报错退出,绝不会切换。
可靠兜底方式:
- 用
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/替换全局 packagist 源(阿里云镜像稳定度高) - 私有包必须单独声明
type: package+dist地址,不能依赖源匹配 - 加环境变量
CURL_IPRESOLVE=4强制 IPv4,避开某些 ISP 的 IPv6 路由异常 - CI 环境建议预置
composer config -g http.sslverifypeer false(仅限内网可信环境),跳过证书链验证耗时
真正的难点不在重传次数,而在让每一次重试都有干净上下文、足够存活时间、和可预测的传输路径。TCP 重传机制本身没问题,问题是 Composer 没把它用透。


















