Skip to content

WebSocket 故障排查手册 ​

适用范围:Mud.Feishu.WebSocket 用法:先按"现象"定位,再按"关键日志/指标/状态"取证,最后按"常见根因"处置。

0. 一次性取证清单 ​

csharp
// 连接状态(HealthCheck 同源)
var state = hostedService.GetConnectionState();   // IsConnected / ConnectedTime / ReconnectCount / LastError
var stats = hostedService.GetConnectionStats();   // Uptime / ReconnectCount / LastError
var reconnect = orchestrator.GetReconnectState(); // IsReconnecting / IsCircuitOpen / CurrentAttempt / LastReconnectReason

// 并发与积压
var concurrency = hostedService.GetConcurrencyService(); // MaxConcurrentHandlers / AvailableCount / PendingCount

健康检查 data 中的存活探针字段(R2/F1,判断"僵尸连接"必读):

字段含义
receive_loop_alive接收循环任务是否仍在运行
last_receive_utc最近一次收到帧的时间(never = 连接建立后从未收到帧)
idle_ms距最近一次收帧的毫秒数(-1 = 尚无样本,不代表静默)
is_zombietrue = 僵尸态(连接为 Open 但接收循环已结束)⇒ 判 Unhealthy

OTel 指标:feishu.websocket.connections、feishu.websocket.backlog、feishu.websocket.message.duration、 feishu.websocket.reconnect(outcome)、feishu.event.deduplication。

1. 连接建立不起来 ​

现象关键日志常见根因与处置
启动期连续失败飞书WebSocket服务初始连接失败 (第 N/3 次)凭据/网络/端点获取失败;查 FeishuAppConfig.AppId/AppSecret、GetWebSocketEndpointAsync 错误码
连接飞书WebSocket服务超时ConnectionTimeoutMs网络不可达或代理拦截;核对出网策略
WebSocket URL使用不安全的ws://协议抛 ArgumentException生产必须 wss://;仅测试环境设 Certificate.AllowInsecureWebSocket = true
WebSocket 主机 "..." 不在允许的主机白名单内抛 ArgumentExceptionP2-15:AllowedHostSuffixes 默认 *.feishu.cn;*.larksuite.com;连接自建代理/本地测试端点时把主机加入列表(支持 *. 通配后缀与精确主机名,分号分隔)或置空表示不限制
SSL证书验证失败SSL证书验证失败(链错误,非自签名根…)证书过期/链不完整;自签名需 Certificate.AllowSelfSignedCertificates(且 Certificate.Mode=Dev),名称不匹配需 Certificate.AllowCertificateNameMismatch
服务端拒绝认证WebSocket认证失败: {Code} - {Message} + 错误码明细10009 令牌过期 / 10010 令牌无效 / 10011 权限不足;AuthenticateAsync 内置退避 + 冷却(3 次失败进入 5 分钟冷却)
认证响应超时WebSocket认证响应超时(30秒)检查 AuthTimeoutMs;若启用了 AuthGateTimeoutMs > 0 且 MaxConcurrentHandlers 很小,长耗时处理器会推迟文本帧处理
连接建立成功但不收事件(僵尸连接)无异常日志;健康检查 data.is_zombie = true / receive_loop_alive = false;HostedService 记 健康检查发现僵尸连接 并触发重连R2/P0-1:接收循环已结束而 socket 仍为 Open。先确认是否有代码取消/释放了连接(ConnectAsync 的令牌现在只约束建连阶段;若确认调用方令牌仍传入且被取消,那是 D2 之前的行为,请改用 DisconnectAsync/DisposeAsync 终止连接)。无外部因素时按"内核 socket 假死 / 对端半开"处理:SDK 会自动重连,若持续复现请检查中间设备(NAT/负载均衡)的空闲回收配置与 ProtocolKeepAliveInterval
分片超限被丢弃分片消息大小超过最大限制 + 已排空超限消息的剩余分片…R2/WS2-03:超限消息现为"排空至消息边界后丢弃",连接保持可用、不回 ACK(服务端按超时重投)。Error 事件的 ErrorType = FragmentSizeExceeded。若日志出现 排空超限消息时达到上界,说明对端持续投递超大消息(协议层异常),SDK 已主动断连重连
分片超限导致连续解析失败排空超限消息时达到上界同上:此时不排空会污染消息边界(被丢弃消息的尾部会被当作新消息),必须在服务端侧限制单条消息大小

2. 频繁断线 / 反复重连 ​

现象关键日志处置
断开后长于健康检查间隔才恢复健康检查发现连接断开(无 连接断开事件触发)说明 Disconnected 未及时送达:核对是否为"旧连接已关 + 新连接握手失败"场景(应由 SDK 保证送达);并检查 HealthCheckIntervalMs
关闭原因异常飞书WebSocket连接已断开: {Status} - {Description}EndpointUnavailable=对端不可达/网络中断;ProtocolError=协议错误;NormalClosure=正常关闭
重连风暴等待 {Delay}ms 后进行第 N 次重连尝试冷却期(Reconnect.Cooldown)+ 指数退避(Reconnect.BaseDelayMs/Reconnect.MaxDelayMs)已生效;若仍过密,检查是否多实例同时重连
停止重连重连熔断器已打开:已达到重连上限达上限后熔断(IsCircuitOpen = true),健康检查返回 Degraded;仅在连接成功后自动清除;需人工/配置变更介入

3. 事件丢失或重复消费 ​

现象取证处置
事件重复feishu.event.deduplication(outcome=hit)+ 处理日志服务端重投是契约行为(ACK 500 或未回 ACK);处理器必须幂等;确认去重模式(EventDeduplication.Mode)为 InMemory/Redis
事件丢失检查是否出现 消息序号验证失败 / 统一去重检查跳过(此时回 ACK 200,服务端不再重投)若是误判重复:核对事件级去重(event_id,带 AppKey 维度)是否被跨应用/跨环境共用同一后端(如共用 Redis)。注意 IFeishuSeqIDDeduplicator 的键是裸 SeqID、进程内全局、无租户隔离(R2/WS2-10 口径纠正:本层不提供按租户/应用分域的隔离键);它不涉及多实例判重,多实例各自独立去重
处理失败无重投已发送ACK消息: code=500 缺失ACK 发送依赖连接可用;断线期间处理失败不会补 ACK,需依赖服务端超时重投
重启后首条消息异常接收消息时发生错误/ProtoBuf 解析失败重连时会清空半包缓冲(BinaryMessageProcessor.Reset);若仍异常,检查 MessageSizeLimits 是否过小导致分片被丢弃

4. 背压与积压(性能) ​

现象取证处置
事件处理延迟增大feishu.websocket.backlog 升高;健康检查 concurrent_utilization_pct ≥ 90 → Degraded提升 MaxConcurrentHandlers;排查处理器内部同步阻塞/串行依赖
健康检查 Unhealthy(槽位耗尽)WebSocket并发槽位已耗尽 (0/N)槽位耗尽即接收循环被反压(不再读帧、不回 ACK);优先排查处理器是否卡住
吞吐不升反降处理器中存在同步 I/O 或长 Task.Delay处理器应纯异步;避免在处理器里等待"接收循环推进"(I10 循环等待)
想临时回退旧行为—设 MaxConcurrentHandlers = 0(无限制)即可让接收侧永不阻塞

5. 关停相关 ​

现象说明/处置
关停时报 ObjectDisposedException改造后 Dispose 之后调用连接/发送 API 会确定性抛出;请确保关停顺序为 await StopAsync() → await DisposeAsync()
关停后仍有 Release() 相关异常I9:信号量不随 Dispose 释放,在途租约归还是安全的;若出现,说明访问了已释放的连接对象而非信号量
关闭握手超时(5s)服务端未应答关闭帧:SDK 会强制 Abort 并观察残留任务异常(不再遗留 UnobservedTaskException)
服务端记录为异常断线同步 Dispose() 不执行停止流程;确定性停止请 await StopAsync() 或 await DisposeAsync()

6. 测试与验证手段 ​

目标手段
重连契约(无死锁、闸门不泄漏)Tests/.../ReconnectionOrchestratorReentrancyTests(Category=Concurrency)
连接代次/释放竞态ConnectionLifecycleRaceTests、DisposeRaceTests
背压与租约不泄漏BackpressureTests、BinaryMessageProcessorConcurrencyTests
收发大小边界SendSizeLimitTests
真实协议交互(握手/分片/关闭码/P1-3 路径)Integration/WebSocketConnectionIntegrationTests(Category=Integration,net8.0+,回环 HttpListener)
源码规范守卫SourceConventionTests(版权头)
架构不变量守卫(I13–I16 / D5)ContractGuards/WebSocketContractGuards(R2/WS2-13,源码级断言);dotnet test Tests/Mud.Feishu.WebSocket.Tests --filter "FullyQualifiedName~WebSocketContractGuards"
连接存活性 / 僵尸态Core/FeishuWebSocketClientLivenessTests(R2)
压力与长稳(不进全量门禁)FeishuWebSocketStressTests(Category=Stress,手工执行):dotnet test Tests/Mud.Feishu.WebSocket.Tests -c Release -f net8.0 --filter "Category=Stress"。注意 xUnit 的 Trait 不会自动排除用例,scripts/verify-build.ps1 已在步骤 4 显式传入 --filter "Category!=Stress"