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 一律写「远程服务器」。
130 lines
11 KiB
Markdown
130 lines
11 KiB
Markdown
# 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 站点配置。
|