10.5 断线重连与传输错误处理


文档摘要

10.5 断线重连与传输错误处理 本节摘要:第 10 章收尾节,讲生产环境绕不开的话题——断线重连。网络不稳,连接会断(stdio 子进程崩溃、HTTP 网络中断、服务重启)。客户端要能优雅恢复,而不是「断了就崩」。本节讲清不同传输在重连语义上的差异、通用的重连模式(指数退避 + 状态恢复),以及哪些会话状态可恢复、哪些不能。读完本节,你能写出扛住网络抖动的生产级客户端。 一、为什么需要重连 生产环境,连接断开是常态: 如果客户端不处理断线,这些情况都会导致「正在跑的任务失败、用户看到错误」。生产级客户端必须能自动重连,把短暂中断变成无感恢复。

10.5 断线重连与传输错误处理

本节摘要:第 10 章收尾节,讲生产环境绕不开的话题——断线重连。网络不稳,连接会断(stdio 子进程崩溃、HTTP 网络中断、服务重启)。客户端要能优雅恢复,而不是「断了就崩」。本节讲清不同传输在重连语义上的差异、通用的重连模式(指数退避 + 状态恢复),以及哪些会话状态可恢复、哪些不能。读完本节,你能写出扛住网络抖动的生产级客户端。

一、为什么需要重连

生产环境,连接断开是常态:

连接断开的常见原因: - 网络抖动(HTTP 客户端,瞬时不可达) - 服务重启(部署、崩溃恢复) - 子进程崩溃(stdio 客户端) - 长时间空闲被关闭(防火墙、负载均衡) - 客户端重启

如果客户端不处理断线,这些情况都会导致「正在跑的任务失败、用户看到错误」。生产级客户端必须能自动重连,把短暂中断变成无感恢复。

二、不同传输的重连语义

不同传输,「断线」的含义与重连方式不同:

传输 断线含义 能否重连 重连方式
stdio 子进程崩溃/退出 能(重启子进程) 重新启动子进程
流式 HTTP 网络中断/服务重启 能(重新 HTTP 连接) 重新连 URL
SSE SSE 流断开 能(重新开流) 重新开 GET /sse
内存 进程内对象失效 否(同进程才有) 不适用

stdio 与 HTTP 都能重连,但重连后的会话状态不同——这是关键差异。

三、会话状态:可恢复 vs 不可恢复

重连后,要区分「哪些状态能恢复、哪些不能」:

会话状态分类 ┌─────────────────────────────────────────┐ │ 可恢复状态(重连后还在) │ │ - 服务端注册的工具/资源/提示词(定义) │ │ - 订阅(服务端记的) │ │ - 资源内容(重新读即可) │ └─────────────────────────────────────────┘ ┌─────────────────────────────────────────┐ │ 不可恢复状态(重连后丢失) │ │ - 正在执行的请求(断了就失败) │ │ - 客户端内存缓存(进程内,重启清空) │ │ - 引导填写的多轮往返(断了要重来) │ │ - 临时会话数据(取决于服务端实现) │ └─────────────────────────────────────────┘

关键认知:重连不是「无缝继续」,而是「重新建立连接后,重新获取状态」。正在执行的请求会失败(要重试),内存缓存会丢(要重建)。

⚠️ 注意:别假设重连后「一切如常」。重连后的客户端要重新 list(确认服务端能力没变)、重新 subscribe(确认订阅还在)、重试失败的请求。把重连当成「一次软重启」,而非「无感继续」。

四、通用重连模式:指数退避

生产级客户端用「指数退避 + 状态恢复」模式重连:

import asyncio import random async def connect_with_retry(url, max_retries=5): """带指数退避的重连。""" retry = 0 while retry < max_retries: try: client = Client(url) await client.__aenter__() # 尝试连接 return client except Exception as e: retry += 1 if retry >= max_retries: raise # 重试次数用尽,抛出 # 指数退避 + 抖动 wait = min(2 ** retry, 60) + random.random() * 2 print(f"连接失败,重试 {retry}/{max_retries},{wait:.1f}s 后重试") await asyncio.sleep(wait)

指数退避的关键:

  • 等待时间指数增长(1s、2s、4s、8s...),避免风暴
  • 加上随机抖动(jitter),避免多个客户端同时重连
  • 设上限(如 60s),别无限增长
  • 设最大重试次数,别无限重试

五、状态恢复

重连成功后,要恢复状态:

async def reconnect_and_recover(url): client = await connect_with_retry(url) # 1. 重新 list(确认服务端能力没变) tools = await client.list_tools() resources = await client.list_resources() # 2. 重新 subscribe(订阅可能丢了) await client.subscribe("config://app") # 3. 重新读取需要的资源 config = await client.read_resource("config://app") # 4. 重试失败的请求(如果业务允许) # ... return client

这个「重连 → 恢复」流程是生产客户端的标准操作。

六、不同场景的重连策略

不同业务场景,重连策略不同:

场景 策略
短暂抖动(几秒) 自动重连,用户无感
服务重启(几十秒) 重连 + 显示「重连中」
长时间不可用 重试有限次后,告知用户「服务不可用」
关键操作中断 重连后提示用户「操作中断,是否重试」

💡 技巧:重连要对用户透明,但不掩盖真问题。短暂中断自动重连(用户无感);长时间不可用要明确告知(别让用户傻等)。设计时区分「可自动恢复」与「需用户介入」的边界。

七、stdio 的特殊重连

stdio 的重连有个特殊点——子进程崩溃要重启子进程:

# stdio 重连(概念性) async def stdio_reconnect(params): # 子进程可能已崩溃,重新启动 async with stdio_client(params) as (read, write): async with Client(read, write) as client: # 新的子进程,新连接 # 注意:子进程内的状态(如内存数据)丢了 ...

stdio 重连的注意点:

  • 子进程状态丢失(进程内变量、缓存都没了)
  • 要重新建立环境(env 重新注入)
  • 服务端要能从持久化恢复(否则数据丢)

八、预防优于恢复

最后强调:预防断线优于恢复。几个预防措施:

措施 说明
长连接保活 HTTP 配 keep-alive,避免空闲断开
服务端高可用 多实例 + 负载均衡,单实例崩了不影响
健康检查 客户端定期 ping,早发现早处理
优雅关闭 服务端关闭前通知客户端
幂等设计 工具设计成幂等,重试安全

⚠️ 注意:重连是兜底,不是首选。如果你的服务频繁断线需要重连,根因可能在服务端稳定性或网络质量——优先解决根因,而非优化重连。重连机制是「保险」,不是「正常工作模式」。

本节要点回顾

  1. 生产环境断线是常态,客户端要能自动重连而非崩溃。
  2. 不同传输重连语义不同:stdio 重启子进程,HTTP 重新连接。
  3. 会话状态分两类:可恢复(订阅、资源定义)vs 不可恢复(执行中请求、内存缓存)。
  4. 重连是「软重启」,要重新 list、subscribe、读资源、重试失败请求。
  5. 指数退避 + 抖动:等待时间指数增长、加随机、设上限与最大次数。
  6. 重连策略因场景而异:短暂抖动无感,长时间不可用告知用户。
  7. stdio 重连要重启子进程,进程内状态丢失。
  8. 预防优于恢复:保活、高可用、健康检查、优雅关闭、幂等设计。

第 10 章结束。你已经掌握客户端进阶的全部:传输接入、会话组、缓存、订阅、重连。第 11 章讲认证授权——给这套客户端连接加上 OAuth 保护。


发布者: 作者: 灏天文库 转发
评论区 (0)
U