关键在于新实例就绪后再接入流量、旧实例优雅下线后再断连;需配置应用层 readiness 探针、sigterm 优雅关闭、stop_grace_period 及反向代理健康联动,并监控 5xx 和连接错误验证无损。

解决 Docker Compose 滚动升级中因连接重置导致的瞬时流量受损,关键在于让新实例真正“就绪”后再接收流量、旧实例“彻底下线”后再断开连接。默认的 stop-then-start 行为会切断 TCP 连接,引发 Connection reset by peer 或 ECONNRESET,尤其在 HTTP/1.1 长连接或 gRPC 流式调用场景下非常明显。
确保新容器通过应用层就绪检查再接入流量
仅靠容器进程启动成功(如端口监听)远远不够。微服务常需加载配置、建立数据库连接、预热缓存、注册到服务发现中心——这些耗时操作未完成前就转发请求,必然失败。
- 在
docker-compose.yml中定义细粒度healthcheck,检测真实业务就绪状态,例如:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health/readiness || exit 1"]
interval: 10s
timeout: 5s
retries: 3
start_period: 45s
- Spring Boot 项目应暴露
/actuator/health/readiness端点,并确保其返回UP时,所有依赖组件(DB、Redis、下游服务)均已连通且可响应; - 避免使用
/health(liveness)代替 readiness 检查,后者只反映进程存活,不保证服务能力。
控制旧实例优雅退出,等待连接自然关闭
旧容器被强制 kill 会立即终止所有 TCP 连接,客户端收到 RST 包。必须给它留出“无流量后 graceful shutdown”的窗口期。
- 在服务代码中监听 SIGTERM 信号,在收到后停止接受新请求(如关闭 HTTP Server 的 accept loop),并等待已有连接处理完毕(如设置
shutdown timeout为 30s); - 在 Compose 配置中配合
stop_grace_period,确保 Docker 等待足够时间再发送 SIGKILL:
deploy:
update_config:
order: start-first
parallelism: 1
delay: 15s
stop_grace_period: 30s
- 若使用反向代理(如 Nginx、Traefik),还需配置其 upstream 的健康探测与连接保持策略,避免在旧实例仍在处理请求时就将其从负载池剔除。
规避客户端连接复用导致的“残留调用”
即使服务端已下线,客户端(尤其是 Java HttpClient、OkHttp、gRPC client)可能仍持有空闲连接池,继续向已销毁的容器 IP:Port 发起请求,触发连接拒绝。
- 在客户端启用连接池的“validate before use”机制,例如 OkHttp 的
connectionPool.evictAll()或设置idleConnectionTimeout缩短空闲连接存活时间; - 服务端在 readiness 探针返回
DOWN后,主动向注册中心注销(如 Eureka 的/eureka/apps/{app}/instanceDELETE),通知消费者及时刷新服务列表; - 若无注册中心,可在 Compose 场景中结合反向代理的动态 DNS 轮询(如 Traefik 的 Docker provider)+ 健康状态联动,实现自动剔除不可用后端。
验证与观测:别只看容器状态,要看真实请求成功率
滚动更新是否真正无损,不能只依赖 docker-compose ps 显示 “Up”。必须监控端到端指标:
- 在更新窗口内实时观察 5xx 错误率、连接超时率、gRPC status code
UNAVAILABLE计数; - 捕获客户端日志中的
java.net.ConnectException: Connection refused或io.grpc.StatusRuntimeException: UNAVAILABLE; - 使用
tcpdump抓包确认旧容器是否在收到 FIN 后仍有新 SYN 到达(说明客户端未及时感知下线)。











