Files
dsh_ai1net_server/dsh-server-docs/04-调整方案/21-guest会话卡顿排查.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

130 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 21 · guest 会话"卡顿 / 断开显示连接中"排查
- 日期:2026-09-10
- 触发:用户报障——在另一台电脑用 guest 账号,**每次对话后等几分钟就"断开→连接中",提问要等好一会模型才回复,获取会话信息很慢**
- 结论一句话:**服务端与模型都不慢**(8 轮对话 TTFT 1.0–3.5 s、实例回环 TTFB 2 ms、模型出网 0.3 s、nginx 全部 <5 s);用户看到"看不到流式输出、结果与思考一次性出现"的**根因是 nginx 默认 `proxy_buffering on` 把流攒块下发**(已修复并 reload)。另外存在两个可消除的干扰项:**① 生产实例启用着 HMR(热重载)客户端**;**② 无关域名流量打进编排器**(默认 vhost 误配置)。
- 状态:**根因已定位并修复(nginx 缓冲)→ 待用户浏览器复验流式**;干扰项 A1/A2 待拍板
> **TL;DR**|**结论**:guest「卡顿 / 显示连接中」排查:**服务端与模型都不慢**(TTFT 1.0–3.5 s、实例 TTFB 2 ms、nginx 全 <5 s)。
> **关键**:体感根因 = nginx 默认 `proxy_buffering on` 使流式"一次性出现" + 生产启用 HMR 的干扰(→ 档案 22 的 nginx 修复)。
> **状态**:🔄 已定位;A1 禁 HMR / A2 catch-all 用户已决定暂缓
---
## 一、实测证据(全部只读取证,未导出任何对话正文)
| 维度 | 实测 | 判定 |
|---|---|---|
| **每轮 TTFT**(turn/start → 首个流式 chunk) | 8 轮分别为 **1.0 / 1.8 / 1.3 / 1.6 / 1.6 / 1.7 / 3.5 / 1.7 秒** | ✅ 服务端与模型都快,**"等好一会才回复"不是服务端造成** |
| 轮时长 | 19 / 118 / 291 / 30 / 64 / 46 / 36 / 49 秒(长轮 = 多步工具调用,正常) | ✅ 无异常 |
| 工具调用 | 95 次,平均 **2.3 s**,最慢 79.3 s / 44.2 s(个别命令本身耗时) | ✅ 正常 |
| 会话规模 | 8 轮、12 条 user/message、3268 行事件、1.0 MB(压缩) | ✅ 无膨胀(档案 14 H3 保留期可继续缓做) |
| 实例自身 HTTP | 回环 `ttfb=0.002s`、CPU 7.1%、RSS 276 MB / 512 MB 上限 | ✅ 实例健康 |
| 实例 → 模型 API 出网 | DNS 1.3 ms / TCP 3.9 ms / TLS 17 ms / TTFB 0.30 s | ✅ 出网无碍 |
| nginx(guest 子域) | 732 请求:**594×200、24×401、1×404、1×302,全部 <5 s** | ✅ 边缘不慢 |
| 24h 内崩溃 / 熔断 / idle-reap | **0 条**(新增的 `[crash-restart]` 也无记录) | ✅ 实例没被反复重建 |
| 系统资源 | 内存 636/1870 MB、可用 1234 MB、负载 0.08、0 次 OOM | ✅ 无资源压力 |
> 会话时序里最大的几个间隔(34374 s / 1853 s / 561 s…)**全部出现在 `turn/end` 之后**,即用户离开后隔很久再提问 → 属正常空闲,不是掉线。
## 二、根因定位与修复:"看不到流式输出"(2026-09-10 23:00)
**用户补充关键症状**:*"刚才那轮对话看不到流式输出,结果和思考过程都是一下显示的"* → 指向**中间代理缓冲**。
| 跳 | 检查 | 结果 |
|---|---|---|
| 应用层 | dsh 是否发 `X-Accel-Buffering: no`(若发则 nginx 自动不缓冲) | ❌ **未发**(全包 grep 无命中) |
| 编排器代理 | `src/supervisor/proxy.ts` 转发方式 | ✅ 正确流式:`upRes.pipe(reply.raw)`、WS 走 `upgrade` 隧道,**不缓冲** |
| 传输形态 | 客户端用 WebSocket 还是 HTTP 流 | guest 子域 **101 计数 = 0**(全站 159 条 101 属其他页面)→ 聊天走 **HTTP 流式**,正受 nginx 缓冲影响 |
| 前面还有没有一层 | nginx 日志真实客户端 IP(是否 Cloudflare) | 125.85.124.119 / 113.248.166.66 / 154.40.35.3 → **无 CF 段**,单层 nginx |
| **nginx 配置** | `nginx -T` 全局搜 `proxy_buffering` | ❌ **只有 `proxy_send_timeout 60`,没有任何 `proxy_buffering off`** → 默认 `on` 生效 ⇒ 流被攒成大块 |
**修复(已执行)**:在两个 443 站点块(主域 + 通配子域)的 `location /` 增加:
```nginx
proxy_buffering off; # 关键:流式响应逐块透传
proxy_cache off; # 避免任何缓存介入
proxy_send_timeout 3600s; # 与 proxy_read_timeout 对齐
```
**执行与验证**:备份 → 写配置 → `nginx -t` 通过 → `nginx -s reload` → 生效确认(`nginx -T | grep -c "proxy_buffering off"` = **2**)→ `https://dsh.alotbuy.com/login.html` **200**、编排器直连 200。
**回滚副本**:`/www/server/panel/vhost/nginx/dsh.alotbuy.com.conf.bak-20260910-2300-pre-buffering`(改动前原文,56 行)。
> ⚠️ **宝塔面板注意**:在面板里保存/修改站点配置会按模板**重写**该 vhost,可能抹掉这 3 行 → 若流式又变回"一次性显示",先检查这 3 行是否还在。
## 三、两个可消除的干扰项(待拍板)
### ① 生产实例启用了 HMR(热重载)客户端 —— 头号嫌疑
- 证据:`dsh-web-app/cordis.patch.yml` 第 148-149 行挂着 `- id: client-hmr / name: '@deepseek-ai/dsh-client-hmr'` → **每个实例都启用**;其客户端持续请求 `/plugins/events`。nginx 记录该路径在 guest 子域上以 **≈65 s 间隔**出现(21:43:38 → 21:44:43 → 21:45:48 …),并夹杂 **401**(21:45:53)与 **302**(22:39:36,实例未运行时的守卫跳转)。
- 影响:HMR 是**开发期热重载**能力,生产环境无用;但它会**周期性建立/重试一条通道**,UI 上很可能就是"连接中"的来源,并且每次实例重启后都会产生 401/302 重试噪音。
- 建议修复(**低风险、可回滚**):在 profile patch 层按档案 09 已实证的手法**禁用该行**:
```yaml
- id: client-hmr
name: '@deepseek-ai/dsh-client-hmr'
disabled: true
```
生效需**重启实例**;先在 admin 或 guest 单个实例验证(页面功能是否正常、`/plugins/events` 请求是否消失)。
### ② 无关域名流量打进编排器(默认 vhost 误配置)
- 证据:编排器 24h 收到的 Host 分布 —— **evertwins.com 880 次**、裸 IP `47.77.182.89` 640 次、`www.evertwins.com` 307 次、`wp.` / `staging.evertwins.com` 各 95 次;而本机**没有**这些域名的 vhost 文件。
- 成因:nginx `include /www/server/panel/vhost/nginx/*.conf` 顺序加载,**未显式 `default_server` 时第一个 server 块即该端口默认站点**,我们的 dsh vhost 恰成默认 → 所有未匹配 Host(含扫描器、`/.env` 探测)都被转进业务进程。
- 影响:无关流量与扫描落在业务进程上;访问这些域名会看到 dsh 登录页(品牌混淆)。
- 建议修复:加一个 catch-all 默认站点(`return 444`,或 `server_name _;` + 444)并 `nginx -t` 校验;约 1 分钟,可回滚。
## 四、还需客户端侧取证
服务端已能证明"轮次一旦开始就很快",**无法从服务端看到"用户按下发送 → 消息抵达服务端"之间的耗时**。请在有问题的电脑上:
1. 打开 DevTools → **Network**,复现"连接中"的瞬间,看:
- `/api/session/prompt` 的 **Waiting(TTFB)** 与是否长时间 pending;
- 是否有 **WebSocket/EventSource 反复重连**(`/plugins/events`、`/api/...` 101/未完成);
- Console 是否有 401 / 断线报错。
2. 记录该电脑的网络环境(是否走 VPN/代理、Wi-Fi 信号、运营商)——"网络很卡"的体感与 401 重试可能叠加。
3. 若 DevTools 显示 `/api/session/list` 等请求本身很快而 UI 仍"卡",则属**前端渲染/长轮次等待**,另案处理。
## 五、待拍板
| # | 措施 | 风险 | 需要 |
|---|---|---|---|
| **A1** | 禁用 `client-hmr`(profile patch,含 admin/guest 全量或先单例) | 低(生产无用;可回滚=删行) | 需**重启实例** |
| **A2** | 修默认 vhost:加 catch-all `return 444` | 低(仅影响未匹配域名) | nginx `-t` + reload |
| A3 | 客户端 DevTools 取证(用户侧) | — | 用户在问题电脑操作一次 |
| ~~A0~~ | ~~nginx 关闭响应缓冲(流式修复)~~ | — | ✅ **已完成**(2026-09-10 23:00,见 §二) |
## 六、"刷新后载入历史会话要几分钟"排查与优化(2026-09-10 23:15)
**新症状**:刷新后载入历史会话要**几分钟**,体感"网络很卡"。
**取证(nginx 访问日志解析,4000 行窗口内 guest 子域 823 个请求)**:
| 发现 | 数据 |
|---|---|
| **单次刷新要下载一个巨大的插件 bundle** | `GET /plugins/??<45 个 client.js 拼接>&rev=<hash>` → **实发 980 KB / 930 KB**(含 `dsh-client-hmr`、`typert-registry`、`api-gateway`、~30 个 `dsh-client-ui-*` 面板) |
| 另有哈希静态资源 | `/assets/vendor-CCJJTK99.js` = **209 KB** |
| 大量小 API 轮询(每次刷新都打) | agentPresets/list **131**、commands/list **105**、modelCatalog **78**、syncInspectManifest **76**、subagents/list **73**、session/list **68** … |
**根因(两条叠加)**:
1. **反代响应未被 gzip 压缩**:全局 `gzip_proxied` 只列了 `expired no-cache no-store private auth`,**不含 `any`** → dsh 反代出来的 JS/JSON 大多不满足条件 → 那个 ~930 KB 的 bundle **以未压缩体积下发**(日志 `$body_bytes_sent` 即实发字节,若压缩过应是 250 KB 量级)。
2. **哈希资源无长缓存**:`/assets/*` 与 `/plugins/*&rev=*` 每次刷新都可能重新拉取 ~1.2 MB。
叠加 200+ 次小请求的往返延迟,在**链路质量差**的电脑上就表现为"几分钟"。
**优化(已执行,档案 21 · 2026-09-10 23:15)**:在 dsh vhost 两个 443 站点块中
- `location /` 增加 **`gzip_proxied any;`**(放开反代压缩)
- 新增 `location ~* ^/assets/` → 同代理参数 + `gzip_proxied any;` + **`expires 30d`**(哈希文件名 → 可长缓存)
执行:备份(`*.bak-YYYYMMDD-HHMM-pre-gzip`)→ 写配置 → `nginx -t` **通过** → reload → 生效确认(`gzip_proxied any` ×4、`/assets/` 长缓存块 ×2)→ 主域 200。
**预期效果**:那个 ~930 KB bundle → **约 250 KB(gzip 后)**;重复刷新时 `/assets/*` 走浏览器缓存。
**验证方法**:用户刷新后,取该请求的 `$body_bytes_sent` 对比 —— 预期从 ~930 KB 降到 250–300 KB 量级。
**仍待排查(若压缩后依旧慢)**:① 该电脑链路本身(建议本地测速 / 是否走 VPN);② 每次刷新的 200+ 次小请求往返(属客户端行为,受 R2 限制不能改官方 dsh,只能靠降 RTT 或减少刷新)。
## 七、红线遵守
- 排查**全程只读**:仅 `ps/free/journalctl/nginx 日志/会话文件解压统计`,**未导出、未展示任何对话正文**(分析器只输出时间戳与事件类型)。
- 建议项不触碰 R1(不升级 dsh)与 R2(不改官方主程序);A1 走 profile patch 层官方机制,A2 仅改 nginx 站点配置。