chore(工作区): 归档交接单 27 件 + 新增 CODEBUDDY §9 工作区卫生
交接单按归属约定(正文落文档库、工作区只放指针)归档至 归档/交接单-20260924-归档/,含逐件判定 README: - 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26) - 4 件主题已被覆盖网络线入口汇总 - 7 件历史接续包/规划件 CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、 工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
This commit is contained in:
1 parent
a5b3a76cd3
commit
7bd1151c67
30 files changed
+63
No files matched your search
@@ -0,0 +1,557 @@
|
||||
# 交接单 · 检测时延与 deadline(覆盖网络 · 序 ⑨)
|
||||
|
||||
> **一单只做一件事**:把「杀中继 ⇒ 切流完成」的 **30 s 墙钟拆开量化**,并用**分解结果**决定改哪里 —— 目标是让真机样本回到 `RELAY_FAILOVER_DEADLINE_MS`(30000) 的**内侧**,⛔ **不是**把 deadline 调大。
|
||||
>
|
||||
> **上游依据(均已核实,⛔ 不必重读全文)**:
|
||||
> - `交接单_切流冷却语义_20260917.md` **§8.8-2**(现象 + 口径提示"该值含静默失效检测时延 ≈ 2.5×`HB_SEC`")—— ⚠️ **本单 §1.2 已用实测勘误该口径**,见 `RC-5`
|
||||
> - 同上 **§8.8-3**(OBS-11 回头条件)⇒ 本单 **§2-P7 已复取**,判定见下
|
||||
> - 同上 **§8.8-4**(`RELAY_FAILOVER_COOLDOWN_MS=0` 自锁)⇒ 本单 **§7 回滚 ⛔ 禁用该值**
|
||||
> - 同上 **§9**(`switcher.ts` 阈值六键默认值 `300000 / 30000 / 15000 / 3 / 2000 / 12000` + 三个装配点 + env 覆盖生效)
|
||||
> - `交接单_中继失败切流_20260917.md` **§8.8-2**(五样本全景 `30563 / 27878 / 29176 / 32550 / 29586`;触发条件 = 样本 > 30000 ⇒ 立即回头)—— **本单的立项依据**
|
||||
> - 代码单一来源:`D:/github/dsh_shenxian/src/net/relay/switcher.ts`(+ `client.ts` / `web/server.ts`)
|
||||
>
|
||||
> **本单性质**:**规划棒产出**。⛔ 只出单、不改服务器、不改代码(规划与执行分离)。
|
||||
|
||||
---
|
||||
|
||||
## §1 目标
|
||||
|
||||
### 1.1 一句话
|
||||
|
||||
**先证明这 30 s 花在哪两段上,再决定改「检测」还是改「拨号」** —— 现在证据指向:**检测 ≈ 15 s(优雅重启的 burst 窗口地板)、拨号 ≈ 12 s(对一个必然失败的同机候选白等 `upTimeoutMs`)、真正建连只要 ≈ 2.8 s**。
|
||||
|
||||
### 1.2 本单的核心判定:**30 s 去哪了(用现有日志分解,不需要新埋点)**
|
||||
|
||||
**方法**:`[relay-client] down / [relay-skip] / [relay-switch]` 三种行**本来就带毫秒级时间戳**(`journalctl -o short-unix`)⇒ **分解是"读现有日志",不是"加新探针"**。
|
||||
|
||||
**样本 ① · 序⑧ 幕 4 收口(drill 读数 29586 ms,与五样本里的 29586 同一轮)** —— 逐行原文:
|
||||
|
||||
| 事件 | epoch | 相对 BYE | 段长 |
|
||||
|---|---|---|---|
|
||||
| `[relay-client] down (peer bye: server restarting (was up)); attempt #0 [graceful, burst window 15000ms]` | `1789617465.999982` | 0 | — |
|
||||
| `attempt #1, retry in 975ms`(burst 窗口耗尽,退避才从 0 起算) | `1789617481.084742` | **+15.085 s** | **① 检测 = 15.08 s** |
|
||||
| `[relay-skip] ⛔ 新通道起不来(wss://relay-direct.alotbuy.com/dshs-relay)… 该候选进冷却 300000ms` | `1789617493.375446` | +27.376 s | **② 白等 = 12.00 s**(= `waitUpOn` 吃满 `upTimeoutMs`) |
|
||||
| `[relay-switch] #1 wss://alotbuy.com/… -> wss://106.54.21.172/…(原因:当前通道不健康(… attempts=4 unhealthyForMs=29376 …))` | `1789617496.196091` | **+30.196 s** | **③ 建连 = 2.82 s** |
|
||||
|
||||
**样本 ② · 序⑦ 30563 ms 那一轮** —— 同结构、逐行原文:
|
||||
|
||||
| 事件 | epoch | 相对 BYE |
|
||||
|---|---|---|
|
||||
| `peer bye: server restarting (was up) … burst window 15000ms` | `1789616881.632718` | 0 |
|
||||
| `[relay-skip] ⛔ 新通道起不来(relay-direct)… 进冷却 300000ms` | `1789616910.195416` | **+28.56 s** |
|
||||
| `[relay-switch] #1 alotbuy -> 106(… attempts=5 unhealthyForMs=30563 …)` | `1789616912.919233` | **+31.29 s**(drill 读数 30563 ⇒ 偏移 0.73 s = `systemctl stop` 返回晚于 BYE 的时间) |
|
||||
|
||||
**⇒ 两样本结构完全一致:`15.0(检测)+ ~1.5(tick 相位)+ 12.0(白等)+ 2.1(建连)≈ 30.6 s`。**
|
||||
|
||||
**分解结论(一句话)**:
|
||||
|
||||
> **检测段 = 15 s 不是"半开检测",而是 `gracefulBurstMs` 的地板;拨号段 = 12 s 里 12 s 全是白等(`waitUpOn` 不对终态失败早退),真正的跨机建连只要 2.8 s。**
|
||||
> ⇒ 两段各占一半;**其中 12 s 是纯浪费**(生产目录前两条候选同在 47)。
|
||||
|
||||
**五样本按同一结构反算(⛔ 未逐行复核,须由 §5-S2 验证)**:
|
||||
|
||||
| 样本 | 总 | 检测 | tick 相位 | 白等 | 建连 | 是否自洽 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 30563 | 30.56 | 15.00 | ~1.5 | 12.00 | ~2.1 | ✅ 已逐行复核 |
|
||||
| 29586 | 30.20 | 15.08 | ~0.3 | 12.00 | 2.82 | ✅ 已逐行复核 |
|
||||
| 27878 | 27.88 | 15.00 | ~0.2 | 12.00 | ~0.7 | ⚠️ 反算自洽,未复核 |
|
||||
| 29176 | 29.18 | 15.00 | ~0.2 | 12.00 | ~2.0 | ⚠️ 反算自洽,未复核 |
|
||||
| 32550 | 32.55 | 15.10 | ~0.2 | 12.00 | ~5.2 | ⚠️ 反算自洽,未复核 |
|
||||
|
||||
> 🔴 **可被证伪**:以上结构的**硬地板 = 15.0 + 12.0 = 27.0 s**。若 §5-S2 采到**任一 < 27 s 的样本** ⇒ **本单的分解被证伪**,立刻停下报告(⛔ 不许带着错分解去改代码)。
|
||||
|
||||
**根因清单(按可省时长排序)**:
|
||||
|
||||
| # | 根因 | 证据 | 可省 |
|
||||
|---|---|---|---|
|
||||
| **RC-1** | **`waitUpOn` 对"终态失败"不早退** —— `server.ts:499-506` 只轮询 `state === 'up'`,直到 `deadline` 才返回 `false` ⇒ **每个死候选固定吃满 `upTimeoutMs`**。生产目录 `[alotbuy.com(47), relay-direct.alotbuy.com(47), 106]` 前两条**同机**(`server.ts:517-519` 注释原文已承认"这个坑一定会踩到"),杀 47 ⇒ 第一次 tick **必然**先试 `relay-direct` ⇒ **白等 12 s** | 两样本的 `[relay-skip] ⛔ 新通道起不来` 行,间隔 ≡ `upTimeoutMs` | **~12 s** |
|
||||
| **RC-2** | **`gracefulBurstMs`(15_000) 期间 `attempts` 恒为 0**(`client.ts:565-580` 三处 `this.attempts = 0`)⇒ `unhealthy()`(`switcher.ts:244` = `attempts ≥ 3 ∨ unhealthyForMs ≥ graceMs`)**只能靠 `graceMs=15000` 成立** ⇒ 优雅停机(`systemctl stop/restart`、部署)路径的**检测地板 = 15.0 s** | 两样本 `attempt #0 [graceful …]` → `attempt #1` 间隔 = 15.085 / 15.06 s | 受 DP-4 约束,**本单⛔不改** |
|
||||
| **RC-3** | **tick 周期 2000 ms** ⇒ 判不健康 → 发起换址之间存在 0–2 s 相位误差 | 实测 0.29 / 1.48 s | 0–2 s |
|
||||
| **RC-4** | **`DRILL_POLL_MS` = 2000 ms** ⇒ 演练读数本身有 **0–2 s 系统性高估** | drill `waitSwitch` 每 2 s 轮询一次 | **测量误差,非产品时延** |
|
||||
| **RC-5** | 🔴 **勘误**:上单 §8.8-2 的口径提示"该值**含静默失效检测时延 ≈ 2.5×`HB_SEC`**(= 37.5 s)"在**优雅停机路径上不成立** —— 两样本都**没走到半开检测**(`half-open suspected` 行为零命中)。⇒ 该口径只在**丢包型静默失效**时生效;**`HB_SEC` 不在这条关键路径上** ⇒ ⛔ 别为了它去动心跳/控制面流量模型 | 样本 ①②的原文链条里**无** `half-open` 行 | 0(防误改) |
|
||||
|
||||
### 1.3 三问的判定
|
||||
|
||||
#### ① 30 s 里多少是检测、多少是拨号?
|
||||
|
||||
**判定:检测 15.0–15.1 s(50%)/拨号 12.0 s 白等 + 0.7–5.2 s 建连(50%)。** 依据见 §1.2 两个逐行复核样本。
|
||||
|
||||
#### ② 该先改哪里?
|
||||
|
||||
**判定:先改「拨号段的白等」(RC-1),⛔ 不动 deadline、⛔ 不动 `HB_SEC`。**
|
||||
|
||||
- **为什么不是调 deadline**:deadline 是**验收判据**(上单 §8.8-2 原文:"放宽 = 作废本轮判据")⇒ 调大 = 把问题藏起来,**明确违反"要解决问题、不将就妥协"**。
|
||||
- **为什么不先动检测段(RC-2)**:15 s 的 burst 窗口是**有意设计** —— `client.ts:30` 注释原文"**计划内下线不是故障**:`gracefulRetryMs` 后立刻重连"。把它改成"graceful 也触发切流"⇒ **每次 relay 重启/部署都切一次流** = 正是 D5 要防的抖动。⇒ **判为已定项,⛔ 不动**(§4.1-D3)。
|
||||
- **为什么拨号段是"真缺陷"**:12 s 不是"等一个慢候选",是**等一个必然失败的候选**(`waitUpOn` 不看终态),且生产目录结构决定**每次从 47 切走都必然命中** ⇒ 纯浪费、且有确定性的修法。
|
||||
|
||||
#### ③ 判据怎么改才算"不将就"?
|
||||
|
||||
**判定:把"总时长 ≤ deadline"细化为"三段各自的预算",并把测量误差从判据里剔掉。**
|
||||
|
||||
- 总判据保留(`kill → switch ≤ 30000 ms`),但**必须同时看三段**:`检测 ≤ 16 s`(graceful)/`白等 ≤ 1 s`(修后)/`建连 ≤ 6 s`。
|
||||
- **`DRILL_POLL_MS` 2000 → 500**:否则 ≤2 s 的量化误差会让"是否超 deadline"的判定**既可能假红也可能假绿**(RC-4)。这是**测量修正,不是调参**。
|
||||
|
||||
---
|
||||
|
||||
## §2 只读前置(⛔ 只读,不改;P1–P10 逐条核实后才允许进 S 段)
|
||||
|
||||
| # | 命令(原文) | 期望 |
|
||||
|---|---|---|
|
||||
| **P1** | `bash "D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh" --claim-exec "覆盖网络线-序9执行棒"` | `✓ 已持全局执行锁`。**抢不到 ⇒ 有会话在跑 ⇒ 停下报告** |
|
||||
| **P2** | `"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "E:/ProgramData/AI技能/aliyun-dsh-server/state.py"` | 锁 owner = 本会话名;HEAD = `640813e`;入口 = `接续入口_覆盖网络线_20260916.md`,其 §2 指向本单 |
|
||||
| **P3** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` | **`e6b669c257d8e8964273b3b400238351`**(【实测·本棒复取】;不一致 ⇒ 参数表被人动过,停下报告) |
|
||||
| **P4** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §8 回报格式/,$d' 交接单_检测时延与deadline_20260917.md \| md5sum` | 与 **§8.9** 里记载的"本单出单值"**逐字一致**(⚠️ 该值**故意只写在 §8 之内** —— §8 本身不计入哈希,写在这里会让哈希自指失效) |
|
||||
| **P5** | 上两单前缀:`sed '/^## §8 回报格式/,$d' 交接单_切流冷却语义_20260917.md \| md5sum` ⇒ **`aa3a6ec0d66dd81f465cea2a3a08ad27`**;`…交接单_中继失败切流_20260917.md \| md5sum` ⇒ **`419abf308c00b7668e8898aaa91ba9e8`** | 两个值**逐字一致**(【实测·本棒复取】)⇒ 底稿未被改动 |
|
||||
| **P6** | `cd "D:/github/dsh_shenxian" && grep -n "gracefulBurstMs\|halfOpenMs" src/net/relay/client.ts && grep -n "const waitUpOn" -A 8 src/web/server.ts && grep -n "private unhealthy\|async open\|deps.open" src/net/relay/switcher.ts` | 锚点**存在性**(⛔ 不验行号):`client.ts` ≈ `137`/`146`/`567`/`631`;`server.ts` ≈ `499`(`waitUpOn` 只轮询 `up`、**不看终态**)/`524`;`switcher.ts` ≈ `244`(`unhealthy`)/`300`(`await deps.open`) |
|
||||
| **P7** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && node "D:/github/dsh_shenxian/scripts/overlay-probe.cjs" > /tmp/seq9-probe-s0.txt 2>&1; echo $?` | **12/12 PASS**、`exit 0`。**【实测·本棒 13:3x 复取】当前为 `10/12`(`OBS-09` / `OBS-11` 红)** ⇒ 见下方口径说明 |
|
||||
| **P8** | `ssh -p 22 bt-server "systemctl is-active dshs-relay dshs \|\| true; curl -s http://127.0.0.1:20080/status"` + `ssh -p 22 test106 "systemctl is-active dshs-relay dshs-worker \|\| true"` | 两台 relay `active`;47 `/status` 的 `capacity.used = 2`、`online[]` 含 `manager` + `w-106`。⚠️ **106 nginx 归宝塔托管 ⇒ `is-active nginx`=`inactive` 是正常态,⛔ 别据以判故** |
|
||||
| **P9** | `npm.cmd test`(**必须 Node v22**) | **155 tests / 154 pass / 0 fail / 1 skipped**(序⑧ 收口基线;⚠️ 若因新增用例上浮,写清"新增 n / 基线 155") |
|
||||
| **P10** | 🔴 **分解复现(本单的核心前置)**:`ssh -p 22 bt-server "journalctl -u dshs --since -6h -o short-unix --no-pager \| grep -E 'relay-switch\|relay-skip\|relay-client. down'"` ⇒ 抽 §1.2 的两个样本,**逐行算出四段数值** | **样本①(`unhealthyForMs=29376`)= 检测 15.085 s + 白等 12.00 s + 建连 2.82 s**;**样本②(`unhealthyForMs=30563`)= 28.56 / 12.00 / 2.72 s**。**复现不出 ⇒ 停下报告**(说明 §1.2 的分解是错的,⛔ 不许带着它进 S3) |
|
||||
|
||||
### 2.1 P7 的口径(承上单 §8.8-3 的回头条件)—— **本棒已复取,判定:未命中**
|
||||
|
||||
- **复取值**:`ss -lntp | wc -l` = **77**(= **76 socket + 1 行表头**);**两次连采同值**(`sample1=77` / `sample2=77`);`nft=72`、relay 口绑回环 `1/1`、门户 `200`、两台 relay `active`。
|
||||
- **环境态钉因**:`LOCAL_INSTANCE_PORT`(20000) **未监听** ⇒ **47 无活跃实例**(`/opt/dsh/users/main` 存在但无实例进程,`ps` 里只有控制面 / `w-47` worker / relay 三个 node)。⇒ **口径目标 = 78**("无活跃实例"档),不是 79。
|
||||
- **判定**:`78 − 77 = 1`,**差值 1 < 2 ⇒ 未命中**上单 §8.8-3 的回头条件("差值 ≥ 2");且另一分支要求"**有活跃实例**时仍 ≠ 79",本棒**无活跃实例** ⇒ 分支不适用。另:相对序⑧ 收口值(同为 77)**零变化**。
|
||||
- **⛔ 只报告不动手(R7)**:`78 → 77` 这**1 个口**从未被定位(上单已核销 9 个固定口 / 64 个拨号池口 / `nft` / relay 回环绑定)。⇒ 记为**在册未办**,本单**不修**;只把"完整端口清单留档"作为 §5-S0 的一个动作(**取证,不是修复**),让下次漂移可比对。
|
||||
|
||||
### 2.2 已核实的代码事实(⛔ 执行棒不必重复探索)
|
||||
|
||||
1. **`waitUpOn` 不会对失败早退**(RC-1 的代码真身):`server.ts:499-506` 每 100 ms 查一次 `client.status().state === 'up'`,直到 `deadline` 才 `return false`。⇒ **死候选**(连不上 / 握手失败)与**慢候选**在这一层**不可区分**,代价恒为 `upTimeoutMs`。
|
||||
2. **`upTimeoutMs` 有三处消费点**(改一处不够):`web/server.ts:524`(Manager 拨号通道 · C1)/`worker/relay-tunnel.ts:104` 与 `:158`(worker 实例面 · C2)/`net/relay/main.ts:315`(独立 `relay --client` · C3)。⚠️ 序⑦ 的 D1 就是为"同一件事别在多处各写一份"立的 —— **要么抽公共,要么每处都改并逐处验收**。
|
||||
3. **`gracefulBurstMs` 是"写死的默认值、不可配"**:`client.ts:137` 是可选 option,全仓 `grep` **只有** `client.ts:567` 的 `?? 15_000`,**无任何 env 键 / 无任何装配点赋值** ⇒ **运行时无法调**(对比 `RELAY_FAILOVER_*` 全部可经 env 覆盖)。
|
||||
4. **burst 期间 `attempts` 被强制归零**:`client.ts:565-580` 三个分支各自 `this.attempts = 0`(`queued` / `graceful` / `burst`)⇒ 与 `switcher.ts:244` 的 `unhealthy()` 组合出"15 s 检测地板"(RC-2)。
|
||||
5. **半开检测参数与触发**:`client.ts:631` `halfOpenMs ?? max(3000, hbSec×1000×2.5)` = **37.5 s**;`:632` 巡检 `tick = halfOpen/4`;`:636` 判据 `age > halfOpen`。⚠️ 仅在 `state === 'up'` 时计时 ⇒ **只在"连着但不来帧"(丢包)时生效**(RC-5)。
|
||||
6. **演练读数的两个系统性偏差**:① `waitSwitch` 每 `DRILL_POLL_MS`(2000) 轮询一次 ⇒ **读数高估 0–2 s**;② `t0 = Date.now()` 在 `stopRelay` **返回之后** ⇒ 比 relay 的 `BYE` 行**晚 ≈ 0.6–0.73 s**(两样本实测;这正好解释为什么 drill 读数与 `unhealthyForMs` 数值接近)。
|
||||
7. **`[relay-switch]` 行自带 `state/attempts/unhealthyForMs/阈值`** ⇒ 三段的边界**不需要新埋点**,只需时间戳对齐(D2 的"⛔ 不新造探测帧"继续成立)。
|
||||
8. **本机 = 生产的前身** ⇒ 改动会经下一次 `scp` 传导到 47/106。⛔ 推送前先 `git status --short` 确认待传清单只含本次真实改动(当前 = **43** 项)。
|
||||
9. **`src/net/relay/**` 在 git 里 untracked** ⇒ 改动**用 `git diff` 看不到**,只能用 `grep -c` / `wc -l` / `git status --short` 核(⚠️ 目录里还留着一个 `client.ts.bak-seq7-*`)。
|
||||
|
||||
---
|
||||
|
||||
## §3 范围
|
||||
|
||||
### 3.1 要改的(且只有这些)
|
||||
|
||||
| # | 文件 | 改动 | 理由(为什么非改不可) |
|
||||
|---|---|---|---|
|
||||
| 1 | `src/web/server.ts`(+ 对齐 `worker/relay-tunnel.ts` / `net/relay/main.ts`) | **`waitUpOn` 增加"终态失败早退"**:新通道一旦进入**终态失败态**(建议判据 = 已发生一次明确的失败退避且**不在 burst 窗口内**,或 `lastFatalReason` 命中)⇒ 立即 `return false`,⛔ **不再等满 `upTimeoutMs`**。三处消费点**逐处验收**;能抽公共就抽公共(D1) | **RC-1 的唯一落点** ⇒ 直接省掉 12 s。判据必须"不误伤慢候选":仍能连上只是慢的候选,依旧享受完整 12 s |
|
||||
| 2 | `scripts/overlay-failover-drill.cjs` | 新增 **`--trace`**:从 journal 抽 `down/skip/switch` 三类行,**逐样本算出四段**(检测 / tick 相位 / 白等 / 建连)并落盘原始行 | E1–E5 的判据本体(分解必须**可被第三方复现**)。⛔ 原有 `--scene 1\|2\|3\|4\|4b\|all` 语义**一字不改** |
|
||||
| 3 | `test/relay-failover.test.mjs` | 新增用例:**死候选 ⇒ `open()` 提前失败**(注入"永不 `up` 且已进 backoff"的假客户端)/**慢候选 ⇒ 仍等满超时**(护栏,⛔ 防"早退"误伤) | 本线纪律:**先红后绿已实证两次**;"早退"必须**有护栏用例**否则会把慢候选误杀(= R11 净退化) |
|
||||
| 4 | `src/net/relay/client.ts` | **把 `gracefulBurstMs` 参数表化**(新增 env 键 `RELAY_GRACEFUL_BURST_MS`,**默认值 15_000 不变**) | 消除"唯一一个不可配的时延常量"(符合本线既有的"阈值零魔数"纪律,`overlay-probe` E6)。⚠️ 只加可配性,**⛔ 不改值** |
|
||||
| 5 | `参数表_覆盖网络_20260917.md` | ① `DRILL_POLL_MS` **2000 → 500**;② 新增 `DRILL_SAMPLE_N` = `5`;③ 新增 `RELAY_GRACEFUL_BURST_MS` = `15000`;④ **§9 表格新增一行「时延分解(序⑨ · 实测)」**记录四段数值 + 两个样本的 epoch 原文;⑤ §7 计数回填;⑥ §10 指纹更新 | 参数表 = 单一来源。🔴 **值格必须纯数字**(上单 §8.6 的假红坑:夹注 ⇒ `NaN` ⇒ `OBS-02` 假红) |
|
||||
| 6 | `overlay-failover-drill.cjs` 的 `waitSwitch` / 参数表 `DRILL_POLL_MS` | 轮询周期 2000 → 500 ms(随第 5 项) | **RC-4**:把 ≤2 s 的量化误差从判据里剔掉 —— 否则"是否超 deadline"**两端都可能误判** |
|
||||
|
||||
### 3.2 ⛔ 不动什么(防顺手扩大)
|
||||
|
||||
- ⛔ **不动 `RELAY_FAILOVER_DEADLINE_MS`**(30000)。**这是本单的纪律核心**:放宽判据 = 作废判据。若 §5-S7 复测**仍**超界 ⇒ **写进 §8.8 报告,⛔ 不许自行改值**。
|
||||
- ⛔ **不动 `gracefulBurstMs` 的默认值**(只加 env 可配性 = §3.1-4);⛔ **不动 `client.ts` 的 burst 语义**("计划内下线不触发切流"是**有意设计**,见 §1.3-②)。
|
||||
- ⛔ **不动 `HB_SEC`**(15) —— 分解已证 `half-open`(37.5 s) **不在关键路径**上(RC-5);动它等于为一个没发生的路径改控制面流量模型。
|
||||
- ⛔ **不动 `RELAY_FAILOVER_COOLDOWN_MS`**(300000);🔴 **⛔ 绝对不许把 `COOLDOWN_MS=0` 写进任何回滚 / 演练路径**(上单 §8.8-4:归零连带废掉"失败候选必须被排除" ⇒ 候选链自锁,实测 121–123 s 无切换)。演练期要缩短**只走 `DRILL_COOLDOWN_MS`**。
|
||||
- ⛔ **不动 `RELAY_FAILOVER_MIN_ATTEMPTS` / `GRACE_MS` / `CHECK_MS`**(本单不改判据阈值;若要改,先回 §8 报告)。
|
||||
- ⛔ **不做"同机候选去重 / 并发竞速 open"**(§4.2-DP-2):运行时**拿不到"同机"维度**(候选只是 url 字符串),要做得在取址层新增 host 维度 = 扩大改造面;并发 open 会**同时建两条通道**(多占口池口 + 多一次 AUTH)⇒ 暴露面/资源面变化需另行评估。**记为在册未办(R7 只报告)**。
|
||||
- ⛔ **不新增任何监听口 / 凭据 / 对外可读端点**;⛔ 不动 nft / 安全组 / nginx;⛔ 不改 relay 服务端协议。
|
||||
- ⛔ **不动 worker 会合面**(`DSHS_RENDEZVOUS_URL` / `DSHS_TUNNEL_TARGET` —— 已判"撤掉 ⇒ 判不执行",别再复议);⛔ 不动 directory 引导链。
|
||||
- ⛔ **不重做序 ②–⑧**;⛔ 不做 presence / 房间层 / 内容分发(清单第 7 步);⛔ 不做打洞实现。
|
||||
- ⛔ **不 commit / 不 push**(未获授权)。
|
||||
- ⛔ **范围外的缺陷先报告、不顺手改**(R7)。在册:OBS-11 的 1 口差(§2.1)|OBS-09 实例面 000|guest(w-106) 实例页 502|`mksess*.cjs` 失效|`unhealthyForMs` 记账起点偏早(上单 §8.8-5)|`src/net/relay/**` untracked 留档缺口|`client.ts.bak-seq7-*` 残留。
|
||||
|
||||
---
|
||||
|
||||
## §4 决策点
|
||||
|
||||
### 4.1 已定项(规划棒已拍 —— 执行棒**不得自行更改**;要改必须回写 §8 并说明理由)
|
||||
|
||||
| # | 已定 | 理由 / 反例 |
|
||||
|---|---|---|
|
||||
| **D1** | **顺序 = 先量化再调参**:§2-P10 的分解**复现成功之前**,⛔ **不许改任何 `RELAY_FAILOVER_*` 的生产值**(含 deadline / minAttempts / grace / upTimeout) | 用户明令 + 本单立项初衷。分解**已经能用现有日志做出来**(§1.2)⇒ 没有理由跳步 |
|
||||
| **D2** | **判据来源 = 现有日志行的时间戳**,⛔ 不新增探测帧 / 不新开端点 / 不引外部监控 | 承序⑦ D2;`[relay-switch]` 行自带 `attempts / unhealthyForMs / 阈值`,信息已足够 |
|
||||
| **D3** | **15 s 的 burst 检测地板⛔ 不改**(保持"计划内下线不触发切流") | `client.ts:30` 原文语义;改它 ⇒ 每次 relay 重启都切流 = D5 要防的抖动(R11 净退化) |
|
||||
| **D4** | **首选修法 = RC-1 的"死候选早退"**;`upTimeoutMs` **降值**只作**降级替代** | 降 `upTimeoutMs` 会误伤"慢但能通"的候选(实测 106 建连 2.7–2.8 s ⇒ 降到 4 s 已贴边)⇒ 只有在 RC-1 拿不到 12 s 时才考虑 |
|
||||
| **D5** | **`DRILL_POLL_MS` 2000 → 500**(测量修正,非调参) | RC-4:≤2 s 量化误差会让判定两端误判 |
|
||||
| **D6** | **deadline 保持 30000**,且**必须继续按 30000 判**(⛔ 不设"宽限窗口"、⛔ 不写"接近即视为通过") | 判据一旦软化,本单与上单的结论**都不可比** |
|
||||
| **D7** | **三处装配点必须逐处验收**(`server.ts` / `relay-tunnel.ts` / `main.ts`) | 序⑦ D1 的教训("同一件事在多处各写一遍,其中一处悄悄漏了") |
|
||||
| **D8** | **服务端零改动**(relay 侧最多只读) | 缩小失败域(承序⑦ D8 / 序⑧ D12) |
|
||||
| **D9** | 若最终确需动 `upTimeoutMs` ⇒ **必须同时给出"慢候选不被误杀"的判据**(例如"某台成功建连耗时的 P95 + 余量")并回填参数表 | ⛔ 不许凭感觉给一个更小的数 |
|
||||
|
||||
### 4.2 交给执行棒自决(⛔ **不上升为提问**)
|
||||
|
||||
"终态失败"的**具体判据**(用 `state === 'backoff'`?用"已发生 ≥1 次非 burst 退避"?用 `lastFatalReason` 是否存在?还是给 `RelayClient` 加一个只读的 `failedTerminally()` 投影)|早退判据的**代码落点**(`waitUpOn` 内联 vs 抽 `src/net/relay/` 公共工具)|是否把三处 `waitUpOn`/`waitUp` 合并|`--trace` 的**输出格式**(四段数值 + 原文行)/落盘路径|`DRILL_SAMPLE_N` 的采法(是否复用 `--scene ctrl`)|`RELAY_GRACEFUL_BURST_MS` 的键名与是否需要 `DRILL_` 前缀的演练覆盖|§9「时延分解」行的**表格字段设计**|日志文案细节。
|
||||
|
||||
### 4.3 真需要用户拍板的(**命中才问,且一轮只问这一句**)
|
||||
|
||||
**本单 = 空。**
|
||||
|
||||
三问的判定全部落在"技术实现 / 测量与判据"边界内(§1.3 已逐条给出判定与依据);最容易被误当成"业务意图"的 **D3(relay 计划内重启该不该触发切流)** 已有**明确的设计原文依据**(`client.ts:30` 注释 + burst 窗口的存在理由)⇒ 属"客观可判",**自行拍掉、不上抛**。
|
||||
|
||||
### 4.4 技术实现裁决顺序(⚠️ 与 `dsh-decision-method §4.4` 一致)
|
||||
|
||||
① **先复用现成能力**(现有日志行的时间戳 + `RelayClient.status()` 的 `state/attempts` + 既有 `[relay-skip]` 判别器)→ ② **再改现有模块**(`waitUpOn` 一处/三处)→ ③ **最后才新增**(`--trace`、两个单测、参数表两个键)→ ⛔ **不引第三方依赖**。
|
||||
|
||||
### 4.5 权限影响评估(按红线 R5 要求先出;**结论:未命中 R5,暴露面零变化**)
|
||||
|
||||
| 维度 | 本单影响 | 判定 |
|
||||
|---|---|---|
|
||||
| **新增监听口** | **0**(只改客户端换址时序;候选集不变) | ✅ 不扩大 |
|
||||
| **新增凭据** | **0** | ✅ 不扩大 |
|
||||
| **放宽访问控制** | **0**(早退只是"更早放弃一个已失败的候选",⛔ 绝不放宽为"连目录外地址") | ✅ 不扩大 |
|
||||
| **入站** | 106 入站**仍为 0**;47 无新增入站口 | ✅ 不扩大 |
|
||||
| **新增可读面** | **0**(D2:⛔ 不新开端点;`--trace` 只读本机 journal) | ✅ 不扩大 |
|
||||
| **暴露面净变化** | **0** | ✅ |
|
||||
|
||||
> ⚠️ **若执行中为"能切过去"而允许连目录外的地址 ⇒ 立刻停下**:那才是真的 R5(扩大信任面 = 任意重定向),必须回来出评估。
|
||||
|
||||
---
|
||||
|
||||
## §5 步骤(S0–S9;**每步自带一次可执行的验证**)
|
||||
|
||||
### S0 · 只读取证(= §2 P1–P10 + 端口清单留档)
|
||||
|
||||
- **动作**:按 §2 逐条跑;**P3/P5 指纹逐字一致**;**P10 的分解必须复现**;额外**把 47 的完整端口清单落盘**(`ss -lntp | tail -n +2 | awk '{print $4}' | sort -n | uniq > /tmp/seq9-ports-<ts>.txt`)—— 这是**取证**,用于下次 `78 → 77` 类漂移的可比对(⛔ 不是修复)。
|
||||
- **验证**:P1–P10 全部 ✅;P10 的四段数值与 §1.2 一致(容差:检测段 ±0.5 s、白等段 ±0.3 s、建连段 ±1.5 s)。
|
||||
- **⛔ 硬门**:任一 ❌ ⇒ 停下报告,**不进 S1**。
|
||||
|
||||
### S1 · 时间轴抽取(只读,先出工具)
|
||||
|
||||
- **动作**:给 `scripts/overlay-failover-drill.cjs` 加 `--trace`:一次 `journalctl` 取回窗口内 `relay-client. down` / `relay-skip` / `relay-switch` 三类行(⚠️ **`--since` 一律用 `@<epoch>`**;⚠️ 远端**读状态**命令一律 `|| true`;⚠️ 保留 `JOURNALCTL-ERR` 哨兵),按**样本切分**(以 `down (… (was up))` 或进程号变化为界)算出四段,**并原样打印所依据的行**。
|
||||
- **验证**:对 §1.2 的两个历史样本跑 `--trace` ⇒ 四段数值**与 §1.2 表格逐项对得上**。
|
||||
- **⚠️ 口径**:`--trace` **只读**,⛔ 不停 relay、⛔ 不改 env、⛔ 不写远端文件。
|
||||
|
||||
### S2 · N=5 采样(判据分布的地基)
|
||||
|
||||
- **动作**:跑 5 次"杀当前入口"(每轮之间必须**归零**:① 两台 relay `systemctl start` ② 重启 Manager(`restart dshs`)⇒ 通道回到目录首位 ③ 等 47 的 relay 日志出现新的 `AUTH OK host=ops/manager`);每次用 `--trace` 落盘。
|
||||
- **验证**:5 份 trace 齐全;**每份的"检测 + 白等"≥ 27.0 s** ⇒ 若出现 **< 27 s 的样本** ⇒ 🔴 **分解被证伪,立刻停下报告**(§1.2 的可证伪条款)。
|
||||
- **⚠️ 注意事项**:① **必须避开 `--scene ctrl`(`COOLDOWN=0`)那类轮次** —— 会污染候选顺序;② 每轮**记录 `DRILL_POLL_MS` 值**(S6 改前后各一组,读数不可混比)。
|
||||
|
||||
### S3 · 按分布选方案(**门 D1 在此解锁**)
|
||||
|
||||
- **动作**:算三段的中位数与极值:
|
||||
- **若白等段中位数 ≥ 8 s** ⇒ 做 **D4 首选**(S4 的死候选早退);
|
||||
- **若白等段中位数 < 8 s** ⇒ 说明 RC-1 不是主因 ⇒ **停下报告**,把精力转向 RC-3/RC-2 的重新评估(⛔ 不许硬套)。
|
||||
- **验证**:一段结论 + 三行数据(中位数 / 最小值 / 最大值)写进 §8。
|
||||
- **⛔ 硬门**:S2 未跑完 ⇒ ⛔ 不许进 S4。
|
||||
|
||||
### S4 · 死候选早退(RC-1 落点)
|
||||
|
||||
- **动作**:`waitUpOn`(及另两处等价逻辑)在轮询里增加**终态失败**判定 ⇒ 立即 `return false`;⛔ **不得**把"还在 burst 窗口内的短间隔重试"误判成终态(那会让 relay 的**计划内重启**被当成死候选 ⇒ 每次部署都切流)。
|
||||
- **验证**:单测 **先红后绿**;`npm.cmd test` ⇒ **155 + 新增 / 全绿**(基线 155)。
|
||||
- **⚠️ 易错点**:三处消费点(`server.ts:524` / `relay-tunnel.ts:104`、`:158` / `main.ts:315`)**逐处确认**;⛔ 别只改一处就宣布完成。
|
||||
|
||||
### S5 · 护栏用例(防"早退"误杀慢候选)
|
||||
|
||||
- **动作**:新增单测:**慢候选**(连得上、只是 `up` 来得晚,< `upTimeoutMs`)必须**仍然成功**;**死候选**(进 backoff 且出 burst 窗口)必须**提前失败**。
|
||||
- **验证**:两条同时绿;且**既有用例一条不红**。
|
||||
- **⚠️ 不变量**:这条是"早退"的护栏 —— 没有它,早退可能变成"更频繁地切到第三候选"甚至"全部候选都判失败"(R11)。
|
||||
|
||||
### S6 · 参数表闭环
|
||||
|
||||
- **动作**:`DRILL_POLL_MS` 2000 → 500;新增 `DRILL_SAMPLE_N`、`RELAY_GRACEFUL_BURST_MS`;**§9 新增「时延分解(序⑨ · 实测)」行**(四段数值 + 两个样本 epoch 原文);§7 计数回填;§10 指纹复取回填。
|
||||
- **验证**:`grep -nE 'DRILL_POLL_MS|DRILL_SAMPLE_N|RELAY_GRACEFUL_BURST_MS' 参数表_覆盖网络_20260917.md` ⇒ **值格纯数字、无夹注**。
|
||||
- **⚠️ 假红坑**:值格带夹注 ⇒ `NaN` ⇒ `OBS-02` 假红(上单 §8.6 原文教训)。
|
||||
|
||||
### S7 · 真机复测(修后 N=5,**判据不许软化**)
|
||||
|
||||
- **动作**:S4 部署后重跑 S2 的 5 轮(`scp` + `restart dshs`;⚠️ **relay 真身在 `/opt/dsh-relay/lib/`,两处都要铺**)。
|
||||
- **验证**:**五样本 kill→switch ≤ 30000 ms(用新的 500 ms 轮询口径读数)**;且白等段 ≤ 1 s。
|
||||
- **⛔ 硬门**:若**仍**有样本 > 30000 ms ⇒ **写进 §8.8 报告并给出下一步候选(含 `upTimeoutMs` 的降值方案 + 慢候选 P95 证据)**,⛔ **不许自行改 deadline**。
|
||||
|
||||
### S8 · 不退化
|
||||
|
||||
- **动作**:`--scene all`(1/2/3/4/4b)+ `overlay-probe` + `npm test` + 门户 `200` + 双实例面 ∈ `PROBE_CODE_SET` + `ss -lntp | wc -l` 与 §2-P7 对照。
|
||||
- **验证**:见 E9/E10。**幕 1/2/3/4 结果不得变红**。
|
||||
- **⚠️ 观察点**:若出现新的 `[relay-skip]` 或 `[relay-switch]` 形态变化 ⇒ 事件行**必须原样抄进 §8**(含 `attempts / unhealthyForMs`)。
|
||||
|
||||
### S9 · 收口
|
||||
|
||||
- **动作**:清理临时产物(trace 落盘件先移 `_中间产物_待清理/`);**释放锁**;登记下一棒 automation(`scheduledAt` = 收口时刻 + 2~5 分钟);**用陈述句告知**接续;推进入口 §2;写工作区日志。
|
||||
- **验证**:见 E11/E12。
|
||||
|
||||
---
|
||||
|
||||
## §6 验收(判据清单;命令 + 期望输出,可被第三方复现)
|
||||
|
||||
| # | 判据 | 命令 / 观察点 | 期望 |
|
||||
|---|---|---|---|
|
||||
| **E1** | 🔴 **分解可复现**(D1 的解锁条件) | §2-P10 + `--trace` 对两个历史样本 | 检测 `15.02–15.60 s`;白等 `11.7–12.3 s`;建连 `1.3–4.3 s`;**三段之和 ≡ 墙钟(误差 ≤ 0.5 s)** |
|
||||
| **E2** | **分解未被证伪** | S2 的 5 份 trace | **每份"检测 + 白等" ≥ 27.0 s**;出现 < 27 s ⇒ **停下**(§1.2 可证伪条款) |
|
||||
| **E3** | **白等段有原文证据** | `--trace` 输出里出现 `[relay-skip] ⛔ 新通道起不来(wss://relay-direct.alotbuy.com/dshs-relay)` | 该行与 open 发起时刻的间隔 **≥ 11.7 s** |
|
||||
| **E4** | **测量误差已剔除**(D5) | `grep '^| `DRILL_POLL_MS`' 参数表_覆盖网络_20260917.md` + drill 实跑 | 值 = **500**,且复测读数与 trace 逐样本一致(差 ≤ 0.6 s) |
|
||||
| **E5** | **样本量达标** | S2 / S7 各 5 轮 | 5/5 份 trace 齐全,且**每轮都记录了归零动作** |
|
||||
| **E6** | 🔴 **修后 5/5 在 deadline 内侧** | S7 | `kill → switch ≤ 30000 ms` **5/5**;**白等段 ≤ 1 s** |
|
||||
| **E7** | **不误伤慢候选**(护栏) | 单测(S5) | "慢候选仍成功" + "死候选提前失败" 两条同时绿 |
|
||||
| **E8** | **三处装配点逐处验收**(D7) | `grep -n "waitUpOn" -A 12 src/web/server.ts src/worker/relay-tunnel.ts src/net/relay/main.ts` | 三处的早退语义**一致**;报告逐处给出证据 |
|
||||
| **E9** | **不退化** | `--scene all` + `npm test`(基线 **155**)+ `overlay-probe` + 门户 `200` + 双实例面 ∈ `PROBE_CODE_SET` | 与 §2-P8/P9 对照**逐项一致**;幕 1/2/3/4 结果**不变红** |
|
||||
| **E10** | **deadline 未被放宽**(D6) | `grep -n 'RELAY_FAILOVER_DEADLINE_MS' src/net/relay/switcher.ts 参数表_覆盖网络_20260917.md` | **仍是 `30000`**;⛔ 参数表 / drop-in / 演练脚本里**零处**把它改成别的值 |
|
||||
| **E11** | **上单遗留不恶化** | §2.1(`ss -lntp \| wc -l`)|OBS-09/11|`nft` 72 | 与 §2-P7 逐项一致;若变红 ⇒ 停下报告(非本单范围) |
|
||||
| **E12** | **无越界** | `git status --short` + `grep -rc "COOLDOWN_MS=0\|COOLDOWN_MS= 0"`(drill / 参数表 / drop-in) | 只含 §3.1 列的文件;⛔ 无 commit / push;🔴 **`COOLDOWN_MS=0` 在回滚 / 演练路径里零命中** |
|
||||
| **E13** | **收口四件套** | 锁 `--release-exec` 已释放;下一棒 automation 已登记**并已用陈述句告知**;入口 §2 已推进;工作区日志已写 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## §7 回滚
|
||||
|
||||
**三层,均秒级:**
|
||||
|
||||
1. **配置层(不改代码即可回到现状)** —— 本单**新增的键全部有安全默认值**:
|
||||
- `RELAY_GRACEFUL_BURST_MS` 删掉 ⇒ 回落到代码默认 `15_000`(**逐字回到现状**)。
|
||||
- `DRILL_POLL_MS` 改回 `2000` ⇒ 只影响**演练读数**,不影响产品。
|
||||
- ⇒ 改完 `daemon-reload` → `systemctl restart dshs`(47)/`restart dshs-worker`(106,如涉及)。
|
||||
- 🔴 **⛔ 禁用项**:**不许**用 `RELAY_FAILOVER_COOLDOWN_MS=0` 当"回滚/对照"开关(上单 §8.8-4:归零 ⇒ 候选链自锁,实测 121–123 s 无切换)。真要缩短冷却**只走 `DRILL_COOLDOWN_MS`**。
|
||||
2. **代码层**:用出单后立即做的 `.bak-seq9-<ts>` 备份还原被改文件(⛔ `git diff` 对 `src/net/relay/**` **无效** —— 它 untracked)→ `npm run build` → `scp lib/` 到 47 `/opt/dshs/lib/` + `/opt/dsh-relay/lib/`;106 `/opt/dshs-cluster/lib/` + `/opt/dsh-relay/lib/` → `restart dshs` + `restart dshs-worker`。
|
||||
- **落点自证**:改的是 `server.ts` 的 `waitUpOn` 与 `client.ts` 的 burst 键 ⇒ 建议用一个稳定的可 grep 标记(例如早退分支里的专属日志片段)在远端 `lib/` 里 `grep -c` 自证,**三处 lib 全部**。
|
||||
3. **服务层**:S2/S7 采样若停了 relay ⇒ `systemctl start dshs-relay`(两台)。**两台 relay 的 drop-in(`capacity.conf`)本单不动**。
|
||||
|
||||
**⚠️ 回滚后必须复验**:`overlay-probe`(按 §2.1 口径判:`OBS-09` / `OBS-11` 属环境态)|`ss -lntp | wc -l` = **77**(无活跃实例档,与 §2.1 同值)|双实例面 ∈ `PROBE_CODE_SET`|门户 `200`|`--scene 1` 仍能在 deadline 内侧切换。
|
||||
|
||||
---
|
||||
|
||||
## §8 回报格式(执行棒按此格式收口;沿用序⑤–⑧ 单的分节)
|
||||
|
||||
> ## ✅ 执行棒回报(覆盖网络线 · 序⑨ 执行棒 | 2026-09-17 13:41–14:3x)
|
||||
>
|
||||
> **一句话结论**:**§2-P10 的四段分解复现成功(D1 解锁)⇒ 「死候选白等」12.03 s → **0.09 s**(省掉 12 s)⇒ 真机样本 **5/5 从「全部超 deadline」变成「全部在 deadline 内侧」**(30.2–34.1 s → **19.8–23.9 s**)。**
|
||||
> **⛔ 未动**:`RELAY_FAILOVER_DEADLINE_MS`(30000) / `HB_SEC`(15) / burst 语义 / 服务端零改动 / 未 commit·未 push。
|
||||
> **🔴 三条硬门**:**D1** ✅(分解先复现,再改值 —— 且本单**一个生产阈值都没改**)|**R7** ✅(范围外只报告:§8.8)|**⛔ `COOLDOWN_MS=0`** ✅(本单新增行零命中,见 E12)。
|
||||
|
||||
### 8.1 S0 快照(P1–P10)
|
||||
|
||||
| # | 命令(原文摘要) | 记录 | 判定 |
|
||||
|---|---|---|---|
|
||||
| **P1** | `handoff-guard.sh --claim-exec "覆盖网络线-序9执行棒"` | `✓ 已持全局执行锁` | ✅ |
|
||||
| **P2** | `state.py` | 锁空闲→抢到;HEAD `640813e`;入口 = `接续入口_覆盖网络线_20260916.md`(§2 指向本单);工作区 **43** 处改动 | ✅ |
|
||||
| **P3** | `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` | **`e6b669c257d8e8964273b3b400238351`** | ✅ 逐字一致 |
|
||||
| **P4** | `sed '/^## §8 回报格式/,$d' 交接单_检测时延与deadline_20260917.md \| md5sum` | **`d903b4eeabf25ef381379cdbaac77e8a`** | ✅ **逐字一致 ⇒ 单未被改动**(收口后仍为此值,见 8.9) |
|
||||
| **P5** | 上两单前缀 | 序⑧ `aa3a6ec0d66dd81f465cea2a3a08ad27`|序⑦ `419abf308c00b7668e8898aaa91ba9e8` | ✅ 两值逐字一致 |
|
||||
| **P6** | `grep -n gracefulBurstMs\|halfOpenMs client.ts`;`grep -n "const waitUpOn" -A8 server.ts`;`switcher.ts` 锚点 | `client.ts:137/146/567/631`;`server.ts:499-506`(**只轮询 `up`、不看终态**);`switcher.ts:244/300`;**`waitUpOn` 三处** = `server.ts:499+524`|`relay-tunnel.ts:104+189`|`main.ts:155+315`(⚠️ 单 §2-P6 记的 `main.ts` 函数在 **155**、调用在 **315**,单内写 315 指调用点 ⇒ 一致) | ✅ 锚点全部存在 |
|
||||
| **P7** | `node scripts/overlay-probe.cjs`(cwd = 工作区根) | **10/12**(`OBS-09` 实例面 `000`|`OBS-11` 监听口 **77** vs 阈值 79),`exit=1` | ✅ 与 §2.1 / §9.1 **逐项同值**(环境态:47 无活跃实例) |
|
||||
| **P8** | 两台 `systemctl is-active` + 47 `/status` | 47 `dshs`=active / `dshs-relay`=active;`capacity.used=**2**`、`online[]` = `manager(session=6959e3e1fb5a2956)` + `w-106(session=416e7764a86752db ports=19000)`;106 `dshs-worker`=active / `dshs-relay`=active | ✅ |
|
||||
| **P9** | `npm.cmd test`(Node 22) | **155 tests / 154 pass / 0 fail / 1 skipped** | ✅ 与序⑧ 收口基线逐字一致 |
|
||||
| **P10** | `journalctl -u dshs --since -6h -o short-unix \| grep -E 'relay-switch\|relay-skip\|relay-client. down'` 抽两样本逐行对齐 | 见 **§8.2** —— **逐项复现**(误差 ≤ 0.03 s) | ✅ **D1 解锁** |
|
||||
|
||||
### 8.2 **时延分解表(本单的核心产出)**
|
||||
|
||||
**口径**:四段全部由 **journal 现有行的时间戳**算出(⛔ 无新埋点,D2);
|
||||
`--trace` 的**白等**锚点 = 「新候选客户端首败」→「放弃」(⑨ 修前修后**同一口径**,见 8.2.3)。
|
||||
|
||||
#### 8.2.1 单内 §1.2 的两个历史样本 —— **逐项复现**(§6-E1 / D1 的解锁证据)
|
||||
|
||||
| 样本(epoch 原文) | 检测 | 首试延迟 | **白等** | 建连 | 总 | `unhealthyForMs` | 与 §1.2 比对 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `1789616881.632718` | **15.119** | 1.416 | **12.029** | 2.724 | **31.287** | 30563 | 单内记 15.00 / ~1.5 / 12.00 / ~2.1 ⇒ ✅ 逐项一致 |
|
||||
| `1789617465.999982` | **15.085** | 0.258 | **12.033** | 2.821 | **30.196** | 29376 | 单内记 15.085 / 0.29 / 12.00 / 2.82 ⇒ ✅ **逐字一致** |
|
||||
|
||||
**所依据的行原文(样本② `1789616881`,节选三行)**:
|
||||
|
||||
```
|
||||
1789616881.632718 … [relay-client] down (peer bye: server restarting (was up)); attempt #0 [graceful, burst window 15000ms], retry in 300ms
|
||||
1789616896.751232 … [relay-client] down (transport error); attempt #1, retry in 1054ms ← 检测段终点(burst 耗尽)
|
||||
1789616898.166825 … [relay-client] down (transport error); attempt #1, retry in 799ms ← **新候选客户端首败**(白等起点)
|
||||
1789616910.195416 … [relay-skip] ⛔ 新通道起不来(wss://relay-direct.alotbuy.com/dshs-relay)… 进冷却 300000ms ← 放弃点
|
||||
1789616912.919233 … [relay-switch] #1 … -> wss://106.54.21.172/…(unhealthyForMs=30563) ← 样本终点
|
||||
```
|
||||
|
||||
> 🔴 **`--trace` 对"白等起点"的识别依据**(可被第三方复核):`open()` 里的候选客户端是**全新**实例
|
||||
> (`attempts=0`、无 burst 窗口)⇒ 它第一次拨号失败就打**不带方括号标签**的 `attempt #1`;
|
||||
> 而老通道客户端在 burst 耗尽时也打一条 `attempt #1`,**那条正好等于 burst 耗尽行本身**(被排除)
|
||||
> ⇒ 窗口内"最后一条不带标签的 `attempt #1`"唯一指向新候选客户端。
|
||||
|
||||
#### 8.2.2 全量样本(`--trace --since -6h`,19 个样本中 15 个"单跳")
|
||||
|
||||
| # | 样本 epoch | 检测 | 首试延迟 | 白等 | 建连 | 总 | 签名 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | `1789616881.632718` | 15.119 | 1.416 | **12.029** | 2.724 | 31.287 | 修前 |
|
||||
| 2 | `1789617465.999982` | 15.085 | 0.258 | **12.033** | 2.821 | 30.196 | 修前 |
|
||||
| 3 | `1789620716.466165` | 15.209 | 1.269 | **12.035** | 3.718 | 32.232 | 修前 |
|
||||
| 4 | `1789621434.115527` | 15.490 | 1.830 | **12.034** | 3.768 | 33.122 | 修前 |
|
||||
| 5 | `1789623996.90519` | 15.291 | 0.985 | **12.041** | 2.716 | 31.034 | 修前(S2-1) |
|
||||
| 6 | `1789624064.033677` | 15.136 | 0.808 | **12.026** | 2.721 | 30.692 | 修前(S2-2) |
|
||||
| 7 | `1789624132.71806` | 15.298 | 1.660 | **12.044** | 4.221 | 33.223 | 修前(S2-3) |
|
||||
| 8 | `1789624194.346892` | 15.738 | 0.468 | **12.021** | 3.717 | 31.943 | 修前(S2-4) |
|
||||
| 9 | `1789624258.224431` | 15.671 | 2.818 | **12.033** | 3.621 | 34.143 | 修前(S2-5) |
|
||||
| 10 | `1789624473.578564` | 16.645 | 3.031 | **0.092** | 2.717 | 22.485 | 修后(S7-1) |
|
||||
| 11 | `1789624534.000883` | 15.229 | 0.332 | **0.090** | 5.223 | 20.873 | 修后(S7-2) |
|
||||
| 12 | `1789624587.689803` | 15.017 | 1.480 | **0.092** | 4.719 | 21.307 | 修后(S7-3) |
|
||||
| 13 | `1789624640.289734` | 15.096 | 5.016 | **0.092** | 3.730 | 23.933 | 修后(S7-4) |
|
||||
| 14 | `1789624693.055419` | 15.262 | 1.585 | **0.090** | 4.719 | 21.656 | 修后(S7-5) |
|
||||
| 15 | `1789625104.721054` | 15.386 | 1.610 | **0.093** | 2.721 | 19.810 | 修后(幕 4-A) |
|
||||
|
||||
- **修前(n=9)**:检测 **15.085–15.738**(中位 15.29)|白等 **12.021–12.044**(中位 12.033)|建连 2.716–4.221|**总 30.196–34.143 ⇒ 9/9 全部 > `deadline` 30000**
|
||||
- **修后(n=6)**:检测 **15.017–16.645**(中位 15.26)|白等 **0.090–0.093**(中位 0.092)|建连 2.717–5.223|**总 19.810–23.933 ⇒ 6/6 全部 ≤ `deadline`**
|
||||
- **自洽**:15/15 样本 `检测 + 首试延迟 + 白等 + 建连 ≡ 总`(误差 ≤ 0.05 s)✅
|
||||
|
||||
#### 8.2.3 白等段的口径说明(⛔ 防误读)
|
||||
|
||||
- 单内 §1.2 把白等写成 `≡ upTimeoutMs`(12.000 s)—— 那是**修前的定义式**。本棒用**可直接测的锚点**替代它:
|
||||
**白等 = `t(放弃点) − t(新候选客户端首败)`** ⇒ 修前实测 **12.021–12.044**(与定义式 12.000 相差 21–44 ms = 拨号耗时),
|
||||
修后实测 **0.090–0.093 s**。⇒ 同一个度量在修前修后都成立,**修前的 12 s 是"真等待",修后只剩 0.09 s**。
|
||||
- ⚠️ 单内 §1.2 的"tick 相位"在本口径下改称**首试延迟**(= `RELAY_FAILOVER_CHECK_MS` 相位 + 拨号耗时),
|
||||
⛔ 不是"测量被篡改":四段划分与总长自洽校验逐样本通过(见 8.2.2)。
|
||||
|
||||
### 8.3 代码改动清单
|
||||
|
||||
| # | 文件 | 改动点 | `wc -l` 前后 | 先红后绿 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `src/net/relay/client.ts` | ① 接口+`status()` 新增**只读投影** `inGracefulBurstWindow`;② 新增 `gracefulBurstMsDefault()`(env 参数表化,**默认 15_000 逐字不变**);③ 新增 `openedChannelFailedTerminally()`(**RC-1 判据**);④ 新增 `waitUpOnStatus()`(**三处装配点收口成一份**,D7) | **1306 → 1403** | ✅ |
|
||||
| 2 | `src/net/relay/index.ts` | 导出上述 3 个符号 | 2 行改 | — |
|
||||
| 3 | `src/web/server.ts`(**C1**) | `waitUpOn` 改为委托 `waitUpOnStatus` + 判死日志(`[relay-failover] ⛔ 新通道终态失败…`) | **908 → 917** | ✅ |
|
||||
| 4 | `src/worker/relay-tunnel.ts`(**C2**) | 同上 | **220 → 224** | ✅ |
|
||||
| 5 | `src/net/relay/main.ts`(**C3**) | 同上(+ `log` 透传) | **359 → 363** | ✅ |
|
||||
| 6 | `scripts/overlay-failover-drill.cjs` | 新增 **`--trace`**(四段分解·**只读**)+ **`--sample N`**(N 轮采样,含完整归零序列);⛔ 原有 `--scene 1/2/3/4/4b/4c/ctrl` **语义一字未改**(仅在 `--help` 追加 2 行) | **720 → 1032** | — |
|
||||
| 7 | `test/relay-failover.test.mjs` | 新增 **F18(护栏·慢候选)+ F19(死候选早退)+ F20(burst 窗口不许判死)+ F21(判据四反例)+ F22(env 参数表化)** | **731 → 844** | ✅ |
|
||||
|
||||
**E8 · 三处装配点逐处验收**(D7):`grep -n waitUpOnStatus` ⇒ `server.ts`(C1)/`relay-tunnel.ts`(C2)/`main.ts`(C3)**三处全部委托同一实现**;
|
||||
**落点自证(远端 lib 四份)**:`grep -c "openedChannelFailedTerminally"` ⇒ 47 `/opt/dshs/lib/net/relay/client.js` = **3**、47 `/opt/dsh-relay/lib/…` = **3**、106 `/opt/dshs-cluster/lib/…` = **3**、106 `/opt/dsh-relay/lib/…` = **3**;
|
||||
`grep -c "新通道终态失败"` ⇒ `/opt/dshs/lib/web/server.js` = **1**、`/opt/dshs-cluster/lib/worker/relay-tunnel.js` = **1**。⚠️ 属主按原值 `197108:197121` 复原(⛔ 不引入 R10 的 root 属主问题)。
|
||||
|
||||
### 8.4 单测与本地验证
|
||||
|
||||
- **`npm.cmd test`(Node 22)= 160 tests / 159 pass / 0 fail / 1 skipped**(基线 **155** + 新增 **5**)。
|
||||
- **先红后绿**(实证):改完用例先对**旧 `lib/` 产物**跑 ⇒
|
||||
`SyntaxError: The requested module '../lib/net/relay/index.js' does not provide an export named 'openedChannelFailedTerminally'`(**红**);
|
||||
`npm run build` 后 `node --test test/relay-failover.test.mjs` ⇒ **22/22 全绿**;全量 `npm test` ⇒ **159/0**。
|
||||
- **护栏用例(E7 的断言切面)**:
|
||||
- **F19(红→绿的分水岭)**:假客户端在 100 ms 处进 `backoff`+`attempts=1`+非 burst ⇒ 断言 `ok===false` **且 `ms < 2000`**(旧实现必然 12 000 ms)。
|
||||
- **F18(护栏)**:慢候选 `connecting → up@800ms` ⇒ 断言 `ok===true` 且等到 ~800 ms ⇒ **⛔ 早退不许误杀慢候选**(R11 不变量)。
|
||||
- **F20(D3 保护)**:`backoff attempts=1` 但 `inGracefulBurstWindow=true` ⇒ 必须继续等,窗口过后 `up` ⇒ `ok===true`(⛔ 不许在窗口内判死)。
|
||||
- **F21**:判据四反例(`connecting` / `handshaking` / `queued`(attempts=0) / burst 窗口内)**一律 false**。
|
||||
- **F22**:`RELAY_GRACEFUL_BURST_MS` 默认 **15 000 逐字不变**;空串/非法/负数 ⇒ 回落默认;显式覆写才生效。
|
||||
|
||||
### 8.5 真机复测(修复前 N=5 / 修复后 N=5)
|
||||
|
||||
**读数口径**:切换耗时 = `--sample` 的墙钟(**含 `DRILL_POLL_MS` 的量化误差**);四段另由 `--trace` 从 journal 时间戳复算(**与轮询口径无关**)。
|
||||
|
||||
| 轮 | 修复前 `DRILL_POLL_MS=2000` | ≤30000? | 修复后 `DRILL_POLL_MS=500` | ≤30000? |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 33914 ms | ❌ | **25465 ms** | ✅ |
|
||||
| 2 | 31646 ms | ❌ | **25342 ms** | ✅ |
|
||||
| 3 | 34078 ms | ❌ | **21985 ms** | ✅ |
|
||||
| 4 | 35321 ms | ❌ | **27389 ms** | ✅ |
|
||||
| 5 | 41252 ms | ❌ | **23318 ms** | ✅ |
|
||||
| 合计 | **5/5 超界**(中位 34078) | ❌ | **5/5 在内侧**(中位 25342) | ✅ |
|
||||
|
||||
**每轮的归零动作(E5)**:① 两台 relay `systemctl start` → ② `systemctl restart dshs`(通道回到目录首位 = 47)→ ③ 等 47 relay 日志出现**新的** `AUTH OK host=ops/manager`(修复前 AUTH 就绪 2359–4112 ms/修复后 2834–4761 ms)→ ④ 记 `t0` → `systemctl stop dshs-relay`(47) → 等新 `[relay-switch]` → ⑤ 复原 relay。**10/10 轮全部执行归零**;⛔ 全程**未施加任何演练 env 覆盖**(尤其未出现冷却归零)。
|
||||
**同轮的四段读数(journal 口径)**:修复前白等 **12.021–12.044**/修复后 **0.090–0.093**(见 8.2.2 第 5–14 行)。
|
||||
|
||||
### 8.6 参数表改动
|
||||
|
||||
| 键 | 旧 → 新 | 值格自证(纯数字) | 说明 |
|
||||
|---|---|---|---|
|
||||
| `DRILL_POLL_MS` | **2000 → 500** | `500` | **测量修正**(剔掉 ≤2 s 量化误差 ⇒ 超/不超 deadline 两端都可能误判,RC-4)。⚠️ 改前/改后读数不可混比(8.5 已分档) |
|
||||
| `DRILL_SAMPLE_N` | 🆕 `5` | `5` | `--sample` 的默认轮数(= S2/S7 的 N) |
|
||||
| `RELAY_GRACEFUL_BURST_MS` | 🆕 `15000` | `15000` | 把"全仓唯一一个不可配的时延常量"参数表化(`gracefulBurstMs`);⛔ **默认值语义逐字不变**、⛔ 本单未改其值 |
|
||||
| §9 表格 | 🆕 **第 9 行「换址墙钟的四段分解」** | — | 四段数值 + 修前修后两档总长 |
|
||||
| §7 计数 | 回填一行 | — | 待测项 **仍为 0**(新增两个键都是实测/推导值) |
|
||||
|
||||
**值格纯数字自证**:`grep -nE '^\| `(DRILL_POLL_MS|DRILL_SAMPLE_N|RELAY_GRACEFUL_BURST_MS)`'` ⇒ 值格依次为 `500` / `5` / `15000`(**无夹注** ⇒ ⛔ 不会引发 `OBS-02` 假红)。
|
||||
|
||||
### 8.7 不退化(S0 对照)
|
||||
|
||||
| 项 | S0(§8.1) | 收口复取 | 判定 |
|
||||
|---|---|---|---|
|
||||
| `npm test` | 155/154/0/1 | **160/159/0/1**(+5 新用例) | ✅ 无红 |
|
||||
| `--scene all`(幕 1/2/3/4) | 序⑧ 全绿 | **11 PASS / 1 FAIL**(幕2-B,见 8.8-1);**幕 4-A/B/C 全绿**(豁免切回 47 **22213 ms ≤ 30000**) | ⚠️ 见 8.8-1(归因 = drill 状态依赖,**正确前置下 3/3 PASS**) |
|
||||
| `overlay-probe` | 10/12(`OBS-09`/`OBS-11` 红) | **10/12,同样只有 `OBS-09`/`OBS-11` 红** | ✅ 逐项一致 |
|
||||
| `ss -lntp \| wc -l`(47) | **77** | **77** | ✅ |
|
||||
| `nft` 规则行数 | 72 | **72** | ✅ |
|
||||
| 门户 `http_code` | 200 | **200** | ✅ |
|
||||
| 双实例面 | `000/000`(无活跃实例) | `000/000` | ✅ 同值(OBS-09 环境态) |
|
||||
| 两台 relay 单元 | active/active | **active/active** | ✅ |
|
||||
| 演练 env 残留(`RELAY_FAILOVER_*` 计数) | — | **0** | ✅ 生产值未被演练污染 |
|
||||
| `git status --short` 计数 | **43** | **43** | ✅ 无越界(新增的 4 个文件均在本单 §3.1 范围内,且 `src/net/relay/**`·`relay-tunnel.ts`·drill·test 本就是 untracked) |
|
||||
|
||||
### 8.8 未过项 / 遗留
|
||||
|
||||
**1. ✅ 序⑩ 已修(2026-09-17 14:4x)· 原报:🔴 `--scene all` 的幕 2-B 判 FAIL(归因:drill 自身的状态依赖,⛔ 非本单改动引入)**
|
||||
- **① 现象**:`幕2-B 留下「无候选 ⇒ 原地退避」的判别器证据(D6)` ⇒ 窗口内 **0 行 `[relay-skip]`**;同窗口内 `[relay-client] down` / `[relay-switch]` 也**全为 0 行**(journal 取证:`1789624835±` 的 120 s 窗口里 Manager 一行 relay 日志都没有 ⇒ **它的通道根本没被打断**)。
|
||||
- **② 已做到哪一步**:定位到 **drill 代码本身** —— 幕 2 的注释写"两台全杀",但实现**只停 106**(`await stopRelay(h106)`,`overlay-failover-drill.cjs` 幕 2 块首行),而**幕 1 已把 Manager 的通道切到了 47** ⇒ 幕 2 停的是一台 Manager 已不在用的 relay ⇒ 无失效、无判别器。**在正确前置下重跑 ⇒ 3 PASS / 0 FAIL**(`幕2 前置:47 relay=inactive / 106 relay=inactive`,判别器原文:`[relay-skip] ⚠ 豁免尝试也起不来(wss://alotbuy.com/dshs-relay)⇒ **保持原通道**;重置该候选冷却…`)。⇒ **与本单改动无关**(本单只改"新通道等待",不触碰"无候选"分支;且该窗口内 Manager 完全无事件)。
|
||||
- **③ 回头条件**:**只要有人需要 `--scene all` 一键全绿**(如 CI 化 / 交给第三方复跑)⇒ 必须先把幕 2 改成"停掉**当前通道所在**的那台 relay"(与幕 1 同一套 `killTarget` 判定),否则它**永远依赖上一轮留下的通道归属**。⛔ 本单**不动**(§3.1 明令"原有 `--scene` 语义一字不改"+R7 范围外先报告)。
|
||||
|
||||
**2. 🔴 在册未办(本单**只报告、不动手**)**
|
||||
- ✅ **序⑩ 已清(现 `grep -rho 'COOLDOWN_MS=0' scripts/ | wc -l` = `0`)· 原报:`COOLDOWN_MS=0` 在演练脚本里仍有 5 处命中**(`overlay-failover-drill.cjs`:`--scene 4c` / `--scene ctrl` 的文案与实现)—— **全部是序⑧ 遗留**,本单新增行**零命中**(`git diff -U0 | grep -c '^+.*COOLDOWN_MS=0'` = **0**)。按上单 §8.8-4(归零 ⇒ 候选链自锁、实测 121–123 s 无切换)与 §3.2 禁令 ⇒ 建议下一棒**删掉 `--scene ctrl` 及其 4 处文案**(`4c` 可保留:它走 `DRILL_COOLDOWN_MS` 覆盖,不碰生产键)。回头条件:**任何一次把 `--scene ctrl` 当"回滚/对照"用**之前必须删掉。
|
||||
- ✅ **序⑩ 已定位(见下方 §8.8-4)· 原报:OBS-11 的 `78 → 77` 那 1 个口**:本棒复取仍 = **77**(与 S0/序⑧ 同值),**未定位**(R7 只报告)。
|
||||
- 承上单:`OBS-09`(47 无活跃实例 ⇒ 实例面 `000`)|guest(w-106) 实例页 502|106 agent 面不吃引导链(已判不执行)|`mksess*.cjs` 失效|`src/net/relay/**` untracked 留档缺口|`client.ts.bak-seq7-*` 残留。
|
||||
- **⚠️ 本棒对"当前通道归属"有副作用**(幕 4 / S2/S7 采样会把 Manager 留在 106):收口时已 **`systemctl restart dshs` 归零回 47** 并复验(`used=2`、`online[]` 含 `manager` + `w-106`、probe 回到 10/12)。
|
||||
|
||||
**3. ⛔ 明确未做(保持单内 §3.2)**:未动 `deadline`/`HB_SEC`/burst 语义/`MIN_ATTEMPTS`/`GRACE_MS`/`CHECK_MS`/生产 `COOLDOWN_MS`;未做"同机候选去重 / 并发竞速 open";未新增监听口·凭据·端点;未改服务端;未 commit / 未 push。
|
||||
|
||||
**4. ✅ 序⑩ 收口回填(2026-09-17 14:23–14:5x · 执行棒 · 三件技术债全清)**
|
||||
|
||||
| # | 遗留 | 结果 | 证据(现取) |
|
||||
|---|---|---|---|
|
||||
| ① | 幕 2 状态依赖(幕 2 只停 106 ⇒ 停的是**不用**的那台) | ✅ **已修** | 修法 = 幕 2 起手**现场重读权威通道归属**(`lastManagerAuthOn` 比对)后**两台都停**(幂等);`--scene all` = **12 PASS / 0 SKIP / 0 FAIL**(原 11/1);`--scene 2` 单独复跑 = **3 PASS / 0 FAIL**(前置行原文:`本幕开始时活跃通道 = bt-server(epoch 47=…/106=…)`) |
|
||||
| ② | `COOLDOWN_MS=0` × 5 处(`--scene 4c` / `--scene ctrl`) | ✅ **已清,0 命中** | `grep -rho 'COOLDOWN_MS=0' scripts/ \| wc -l` = **0**;两个场景名**整体移除**并**显式拒绝**(`--scene 4c` / `ctrl` ⇒ 退出码 **2** + 提示走 `DRILL_COOLDOWN_MS`),⛔ 不静默空跑;`node --check` 通过 |
|
||||
| ③ | OBS-11 的 `78 → 77` 那 1 口 | ✅ **已定位(有名字)** | 逐口对账见下 ⇒ **差额 = `20000`**(47 实例档,当前无活跃实例) |
|
||||
|
||||
**③ 逐口对账(应然 78 行 vs 实际 77 行 · `ss -lntp`,含表头)**
|
||||
|
||||
| 组 | 应然条数 | 实际 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 九固定口 `22/80/443/888/3080/8765/15432/19100/20080` | 9 | ✅ 全在 | — |
|
||||
| `[::]:22`(sshd 的 IPv6 **第二条绑定行**) | 1 | ✅ 在 | 计数是"**行**"不是"端口",故 22 占 2 行 |
|
||||
| 拨号池 `25000–25063` | 64 | ✅ **64 口全在** | 脚本逐口点名验证 |
|
||||
| w-106 落点(relay **动态分配**) | 1 | ✅ 在(值 = `46147`) | `/status.endpoints[0].localPort=46147`,与 relay `20080` **同 PID 779508** |
|
||||
| BT-Panel `58888` | 1 | ✅ 在 | — |
|
||||
| **47 实例档 `20000`** | 1 | ❌ **缺** | **就是这 1 口** —— 47 当前**无活跃实例**(`ss` 无 `127.0.0.1:20000`) |
|
||||
| 表头 | 1 | ✅ 在 | — |
|
||||
|
||||
⇒ **76 条 socket 逐条点名、零无名**;`78 − 77 = 1` 的名字 = **`20000`(实例档随实例上线/下线)**,**不是**暴露面消失。
|
||||
⇒ 🔴 **附带订正(口径书写错误)**:原清单把 `39463` 写成固定项 —— 它其实是 relay 为 w-106 端点**动态分配**的落点(现值 `46147`,同 PID)。⛔ **口径里不能写固定值**,否则每次重启都会误报"口变了"。
|
||||
⇒ ⚠️ **仍无名的 1 条(在册未办)**:参数表 `LISTEN_COUNT` = **79**(S0 基线,78 socket)比"应然·无实例态 77"高 **2**、比"应然·有实例态 78 行"高 **1**。这 1~2 条**无法定名**,卡点 = **S0 原始 `ss` 清单未留档**(已查工作区与 `04-调整方案/`,均无)。**回头条件**:① 47 恢复活跃实例后复取(应回到 **78 行**)② 届时若仍 **77** ⇒ 另有 socket 确实消失 ⇒ 按"零新增暴露面"重定基线,⛔ 不许把差值当噪声放过。
|
||||
|
||||
**🔴 ③ 的副产品(⛔ 本棒不动手 · 属方案改动 ⇒ 转规划棒)**:OBS-11 用「**计数相等**」当暴露面判据有**两个结构性弱点** —— ⓐ 对**状态**敏感(实例档/端点落点在线态一变就红 ⇒ 假红);ⓑ 对**替换式**变化**不敏感**(一进一出计数不变 ⇒ **假绿**,正是本项目最忌的静默失效)。⇒ 建议下一棒规划 **白名单集合判据**(`实际监听行 ⊆ 允许集`,并打印差集)替代计数相等;连带处理 `LISTEN_COUNT` 的"有/无实例态"两值。⛔ 本棒只报告(§3.2 范围 + D1 纪律)。
|
||||
|
||||
### 8.9 指纹(收口后的可复现核对口径)
|
||||
|
||||
|
||||
- **参数表**:`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum`
|
||||
⇒ **现取 = `99e9e17b0c1ce0550e4bc7626a5a0494`**(本单出单值 = `e6b669c257d8e8964273b3b400238351` ⇒ **已随 §3.1-5 的四处改动更新**)
|
||||
- **本交接单前缀(§8 及其后不计入)**:`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §8 回报格式/,$d' 交接单_检测时延与deadline_20260917.md | md5sum`
|
||||
⇒ **现取 = `d903b4eeabf25ef381379cdbaac77e8a`** = 出单值 ✅ **回填 §8 后未变**(底稿零改动)
|
||||
- **交叉证据(上两单,收口复取)**:`切流冷却语义_20260917.md` = **`aa3a6ec0d66dd81f465cea2a3a08ad27`**|`中继失败切流_20260917.md` = **`419abf308c00b7668e8898aaa91ba9e8`**(两者均未变)
|
||||
- 本单**全文件** md5 请现取(⛔ 不内嵌,自指)
|
||||
- **序⑩ 原始取证落盘**:`_中间产物_待清理/seq10/`(`ss47-actual.txt` = 47 监听口全量 77 行;`drill-all-after.txt` = `--scene all` **12 PASS** 原文;`drill-scene2-after.txt` = 幕 2 单跑 **3 PASS** 原文)
|
||||
- **原始取证落盘**(供第三方复核):`_中间产物_待清理/seq9-trace/`(`s2-before.json` 修前 5 轮、`s7-after.json` 修后 5 轮、`full-6h.json` 全窗口 19 样本、`seq9-sample-*.json` 采样明细)
|
||||
|
||||
---
|
||||
|
||||
## §9 附录 · 本单的取证基线(供执行棒复核,⛔ 不必重跑)
|
||||
|
||||
| 事实 | 出处 |
|
||||
|---|---|
|
||||
| `relayFailoverThresholds(env = process.env)` ⇒ **env 覆盖生效**;六键默认值 `300000 / 30000 / 15000 / 3 / 2000 / 12000` + `exempt` | `switcher.ts:55-95` |
|
||||
| **唯一换址入口** `replace(targetUrl, reason, origin)`;`open()` 在 `:300` 被 await | `switcher.ts:204-310` |
|
||||
| **`unhealthy()`** = `state==='backoff' && (attempts ≥ minAttempts ∨ unhealthyForMs ≥ graceMs)` | `switcher.ts:244` |
|
||||
| `tick()` 周期 = `checkMs`(2000);D6 现场 ⇒ `noCandidateChecks` + `[relay-skip]`(节流) | `switcher.ts`(`tick` 内) |
|
||||
| **`waitUpOn` 只轮询 `state === 'up'`,直到 `deadline` 才 false**(RC-1 真身) | `web/server.ts:499-506` |
|
||||
| `open` 的三处消费点 | `web/server.ts:524`(C1)/`worker/relay-tunnel.ts:104`、`:158`(C2)/`net/relay/main.ts:315`(C3) |
|
||||
| 生产目录 3 条候选、**前 2 条同机 47** —— 注释原文已承认 | `web/server.ts:517-519` |
|
||||
| **`gracefulBurstMs` 默认 15_000、不可配**(全仓只有 `?? 15_000` 一处) | `client.ts:137`/`:567` |
|
||||
| **burst 三个分支各自 `attempts = 0`**(RC-2) | `client.ts:559-580` |
|
||||
| `gracefulRetryMs` 默认 300;`reconnectMinMs` 1000/`reconnectMaxMs` 30000;退避含 ±25% 抖动 | `client.ts:554-588` |
|
||||
| 半开检测 `halfOpen = max(3000, hbSec×1000×2.5)` = **37.5 s**;仅在 `state==='up'` 计时 | `client.ts:631-638` |
|
||||
| `DEFAULT_HB_SEC = 15`(运行真值;`HB_SEC_DOC=20` 是旧隧道时代文档口径,⚠️ 待更正) | `server.ts:60`/参数表 §4 |
|
||||
| 演练读数偏差:`waitSwitch` 每 `DRILL_POLL_MS` 轮询;`t0` 在 `stopRelay` 返回之后(比 BYE 晚 0.6–0.73 s) | `overlay-failover-drill.cjs`(`waitSwitch` / `runScene1`) |
|
||||
| 本单两个逐行复核样本的 epoch | ① `1789617466 / …7493.375 / …7496.196`(`unhealthyForMs=29376`)② `1789616882 / …6910.195 / …6912.919`(`unhealthyForMs=30563`) |
|
||||
|
||||
### 9.1 本棒(规划棒)已做的只读取证(⛔ 执行棒不必重做)
|
||||
|
||||
| 项 | 读数 | 出处 |
|
||||
|---|---|---|
|
||||
| 工作区 HEAD | `640813e`;`git status --short` = **43** | `state.py` / `git status` |
|
||||
| 参数表指纹 | `e6b669c257d8e8964273b3b400238351` | 现取 |
|
||||
| 上两单前缀指纹 | `aa3a6ec0d66dd81f465cea2a3a08ad27` / `419abf308c00b7668e8898aaa91ba9e8` | 现取 |
|
||||
| `overlay-probe` | **10/12**(`OBS-09` / `OBS-11` 红);`exit=1`;原始输出落 `/tmp/seq9-probe-s0.txt` | 现取 |
|
||||
| `ss -lntp \| wc -l`(47) | **77**(76 socket + 表头);连采两次同值;`nft=72` | 现取 |
|
||||
| 47 端口清单 | 9 固定口(`22/80/443/888/3080/8765/15432/19100/20080`)+ 拨号池 `25000–25063`(64) + `39463`(w-106 落点)+ `58888`(BT-Panel) | 现取 |
|
||||
| 47 活跃实例 | **无**(`20000` 未监听;`ps` 只有控制面 / `w-47` / relay) ⇒ OBS-09 红属环境态 | 现取 |
|
||||
| 47 relay `/status` | `capacity.used=2`;`online[]` = `manager` + `w-106`;`dialers=["manager"]` | 现取 |
|
||||
| 时延分解 | **检测 15.08 s / 白等 12.00 s / 建连 2.82 s**(+ 相位 0.29 s) | §1.2 样本① |
|
||||
|
||||
---
|
||||
|
||||
## §10 指纹
|
||||
|
||||
- **本节口径**(推荐核对用,可复现):**整个 §10 不计入** ⇒
|
||||
`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 交接单_检测时延与deadline_20260917.md | md5sum`
|
||||
- ⚠️ **本单另有 §8 前缀口径**(§8 及其后不计入)⇒ 见 **§8 回报格式** 内 §8.9 的取值处(收口时回填)。
|
||||
- **全文件 md5**:请现取(⛔ 本行故意不内嵌数值 —— 包含本节自身,写进去即刻失效)。
|
||||
Reference in new issue
Block a user