Files
dsh_shenxian/dsh-server-docs/04-调整方案/117-覆盖网络-参数表与观测口径.md
T
admin 04776af4b1 docs: 工作区根项目文档批量入仓(04-调整方案 113–128 / 交接单 T09–T21 / ops / archive)
起因:用户 2026-09-17 明确「所有文档都要同步,都放在开发仓库 docs 对应文件夹下」。
判据:工作区根 *.md 中在仓库(git ls-files --quotepath=false)搜不到的那些。

入仓 33 份(一律复制,工作区根原件保留不动,避免引用断链):
- 04-调整方案/113–128(16 份 · 原子占号后落盘):覆盖网络 传输方案取舍 / 应用场景与待完善清单 /
  插件化vs改内核 / 问题逐条推演 / 参数表与观测口径;会合中继拆分取证与改造方案;
  集群化改造方案 Manager-Worker;跨节点迁移与节点自举;项目代码分层范式与迭代风险评估;
  搬运与共享重建方案 guest w47→w106;方案规划方法提炼;文档无效信息审计报告;
  会话接续机制复盘与修复;会话接续规范;dsh 客户端化部署方案;dsh 桌面客户端开发方案
- 交接单/archive/交接单-已完成/T09–T21(13 份 · 覆盖网络线已完成单归档)
- ops/(2 份运行态指针:接续入口 / 接续包 · 覆盖网络线)
- archive/(2 份临时与内部简报)

已排除(无需重复入仓):覆盖网络线 10 份方案正文已入档案 103–112(文件名不同)。

登记:INDEX.md §二 新增 04-113–128 共 16 行 + §四 追加 T09–T21 说明 + 机器摘要行刷新
(⛔ 未跑 docs-index-stats.py --write:该脚本会按 \r\n 归一化全文件行尾,故改为字节级单行替换);
README.md 追加 1 条入仓指针;docs-manifest.json 复跑 scripts/docs-manifest.py 刷新。

验收:工作区根 47 份 .md —— 同名已入仓 8 / 本次内容一致 29 / 已知改名映射 10 / 未入仓 0;
git status 待提交清单只含本次新增与登记 3 件(未涉 src/ 与 relay 代码面)。
2026-09-17 18:24:19 +08:00

55 KiB
Raw Blame History

参数表 · 覆盖网络(唯一一张)

线:覆盖网络线 | 序:⑤(参数表 · 观测 · 权限评估)| 产出:执行棒 2026-09-17 唯一来源:覆盖网络_应用场景与待完善清单_20260916.md §五 第 5 项 + §四 P1 行 + 交接单_443兜底_20260917.md §8.8 本表要做的事:把散在 6 份文档里的输入参数 + 代码里已固化的常量收成一张可复算的表(每行带来源等级与来源定位),并填掉 443 单留下的 45% 口径空位。 ⛔ 本表不引入新模型、不引第三方依赖、不做架构改动;性质是「固化」,不是重新推演。 🔴 待测 项一个都不许编数 —— 编出来的数会让整张表失去"可复算"的资格。待测 行的值列留空。


§0 来源等级(四档,⛔ 不许混用)

等级 含义 判据
实测 本机 / 远端命令原文能复现的数 本表给出一条能跑的命令
推导 由表内其它行按公式算出来的数 本表给出复算式,独立手算能得同值
估值 文档里的行业口径 / 旧记录 / 推演假设 指到文件:行,且不得被当成实测引用
待测 本单取不到真值 值留空,写明"由序⑥ 用 3–5 台真机换掉"

⚠️ 旧记录里两个数本次已复核判为不可用(⛔ 别再引用): ① 47 规格"1.8 GB / 2 核"(09-08)⇒ 本次实测 1870 MB / 2 核(量级巧合,但必须用实测值复算); ② 跨云带宽 ~22 KB/s(覆盖网络_千台全场景推演_20260916.md:38)⇒ 本次实测上行下界 ≥ 192 KB/s(≈ 9×),原值作废。


§1 用法约定(机器可读 —— 探针脚本靠它取阈值)

  • 参数行格式:| `KEY` | 值 | 单位 | 等级 | 来源定位 | 复算式 |
  • 观测阈值行格式:| `OBS-NN` | 指标 | 阈值(引用键或字面值) | 判据 |
  • ✅ scripts/overlay-probe.cjs 的每一个阈值都从本表读,⛔ 脚本内不许有魔数(判据 = 交接单 §6 E6)。
  • 探针运行方式(一条命令,cwd = 工作区根): cd "E:/ProgramData/AI技能/aliyun-dsh-server" && node "D:/github/dsh_shenxian/scripts/overlay-probe.cjs"

§2 运行坐标

键 值 单位 等级 来源定位 复算式
SSH_TARGET_47 bt-server — 实测 ~/.ssh/config(HostName 47.77.182.89) ssh -p 22 bt-server hostname
SSH_TARGET_106 test106 — 实测 同上(HostName 106.54.21.172) ssh -p 22 test106 hostname
SSH_PORT 22 — 实测 交接单 §2 注(bt-server 配置写的 32022 已陈旧) ssh -p 22 bt-server true
W47_HOSTNAME iZrj99af19cibck1ge93tqZ — 实测 本次 S0 P6 ssh -p 22 bt-server hostname
W106_HOSTNAME VM-0-8-opencloudos — 实测 本次 S0 P8 ssh -p 22 test106 hostname
RELAY_STATUS_URL http://127.0.0.1:20080/status — 实测 relay ExecStart --port 20080 ssh -p 22 bt-server 'curl -s 127.0.0.1:20080/status'
PORTAL_URL http://127.0.0.1:3080/ — 实测 平台门户(⚠️ http2 on ⇒ 必须 --http1.1,否则假 404) ssh -p 22 bt-server 'curl -s --http1.1 -H "Host: alotbuy.com" 127.0.0.1:3080/'
PORTAL_HOST_HEADER alotbuy.com — 实测 同上 同上
RELAY_NETWORK_ID ops — 实测 /etc/dshs.env DSHS_OVERLAY_NETWORK_ID ssh -p 22 bt-server 'grep NETWORK_ID /etc/dshs.env'
SSH_TIMEOUT_MS 20000 ms 推导 探针脚本单次 ssh 的超时(⚠️ 放这里是为了让 overlay-probe.cjs 零魔数;实测单次 ssh 往返 ≈ 1 s,取 20× 余量) —

🆕 序⑦ 真机演练坐标(scripts/overlay-failover-drill.cjs 用它取数 ⇒ 脚本内零魔数):

键 值 单位 等级 来源定位 复算式
DRILL_RELAY_UNIT dshs-relay — 实测 两台机的 relay 单元名(systemctl cat dshs-relay) ssh -p 22 bt-server systemctl cat dshs-relay
DRILL_MANAGER_UNIT dshs — 实测 47 的 Manager 单元名(演练只读它的 journal 判别器) 同上
DRILL_POLL_MS 500 ms 推导 演练轮询 journal 的间隔(≤ RELAY_FAILOVER_CHECK_MS 的 5×,保证不漏一次切换)。🆕 序⑨:2000 → 500 —— 原 2000 让读数带 0–2000 ms 系统性高估,使「是否超 RELAY_FAILOVER_DEADLINE_MS」两端都可能误判(序⑨ RC-4)⇒ 这是测量修正,不是调参。⚠️ 改前/改后的读数不可混比 —
DRILL_SAMPLE_N 5 次 推导 🆕 序⑨:overlay-failover-drill.cjs --sample 的默认采样轮数(口径 = 每轮都做完整归零:两台 relay start → restart dshs → 等新的 AUTH OK host=ops/manager → 杀入口 → 等新 [relay-switch]) node scripts/overlay-failover-drill.cjs --sample 5
DRILL_COOLDOWN_OBSERVE_MS 90000 ms 推导 幕 3 的「冷却期内不回跳」观察窗;⛔ 必须 < RELAY_FAILOVER_COOLDOWN_MS,否则判据不成立 —
DRILL_SWITCH_MATCH_106 106.54.21.172 — 实测 断言 [relay-switch] 的目标是不是 106(用目录里那个 host,⛔ 不是 ssh 别名 test106) curl -s https://alotbuy.com/dshs-overlay/bootstrap
DRILL_KILLED_MATCH alotbuy.com — 实测 幕 1 被杀入口的标识(= 目录 relays[] 首位的 host);脚本据此判定「Manager 当前通道是否就是将被杀的那台」——不在 ⇒ 记 SKIP 而不是假装 PASS 同上
DRILL_DETECT_BUDGET_MS 120000 ms 推导 幕 1/幕 2 的观察窗;⛔ 必须 ≥ 静默失效检测时延(半开检测 = 2.5 × HB_SEC,即 75 s)—— 首轮实测用 1× deadline(30 s) 必然漏判 —

§3 输入参数(清单 §三 点名的 5 类;散落点收口)

散落点计数(P10,⛔ 只计数不重读):调研_游戏网络特征…:19 处 / 千台全场景推演…:18 / 瓶颈落地方案…:9 / 游戏专项…:7 / 清单…:7 / 百台规模推演…:2 / 骨干层方案…:2 ⇒ 合计 64 处命中,正是"不可复算"的成因。

3.1 设备占比(分层)

键 值 单位 等级 来源定位 复算式
DEV_L1_PUBLIC_IP 10 台 估值 覆盖网络_百台规模推演_20260916.md:53 —
DEV_L2_HOLE_PUNCHABLE 30 台 估值 覆盖网络_百台规模推演_20260916.md:54 —
DEV_L3_RELAY_ONLY 45 台 估值 覆盖网络_百台规模推演_20260916.md:55(原文区间 40–55,取中值) (40 + 55) / 2
DEV_SHARE_L3 0.45 — 估值 覆盖网络_百台规模推演_20260916.md:119「L3 占 45%(v1 只算 15%)」+:132 45 / 100(口径 = 全网节点数的比例)
FLEET_TARGET 1000 台 估值 覆盖网络_千台全场景推演_20260916.md(标题与全文口径) —
FLEET_RELAY_DEMAND 450 台 推导 本表 §5 FLEET_TARGET × DEV_SHARE_L3 = 1000 × 0.45

3.2 打洞率

键 值 单位 等级 来源定位 复算式
HOLE_PUNCH_RATE_INDUSTRY 0.90–0.94 — 估值 覆盖网络_全球架构复盘_20260916.md:38(业内 UDP 打洞 ≈ 94%) —
HOLE_PUNCH_RATE_BY_CLASS 90% / 40% / 10% — 估值 方案规划方法_覆盖网络线提炼_20260916.md:45(三档标注实例) —
HOLE_PUNCH_RATE_LOCAL 分层(n=3 台):① 云机×云机(47↔106)—— 均为 L1 直连,不是打洞场景;② 国内家宽/办公 NAT(本机)↔ 47 / ↔ 106 —— 对端 → 本机方向 10/10 收包 = 可打洞;本机 → 云机方向取不到(云安全组拦 UDP 入站)。两对中两对可打洞 — 实测 本单 §8.4 E6 + overlay-holepunch.cjs --stun(⚠️ 首选路径的 47 UDP 观察器实测不可用:零收包 ⇒ 降级为第三方 STUN,见 §8.4 OBS-4D) 判定式=对(47,本机)、对(106,本机) 各「至少一个方向成功」⇒ 2/2;⚠️ 样本 n=3 对,云节点占 2/3,不代表家宽场景

3.3 每玩家带宽

键 值 单位 等级 来源定位 复算式
PER_PLAYER_BW_TEXT 0.5 KB/s 估值 覆盖网络_调研_游戏网络特征与群聊上限_20260916.md:15 —
PER_PLAYER_BW_BATTLE 2–5 KB/s 估值 同上 :15(另一口径 :43「单玩家 2–20 KB/s」) —
PER_PLAYER_BW_SIEGE 10–20 KB/s 估值 同上 :15 —
PER_PLAYER_BW_LOCAL 9.8(n=10 玩家档)/ 3.9(n=50 玩家档)—— 判据见来源列;聚合天花板 ≈ 200–350 KB/s KB/s 实测 本单 §8.4 E7 + overlay-wan.cjs --players(合成 200 B 消息 × 5/20/50 msg/s × 10/50 玩家 × 60 s,经 relay 真机间) 判据=p95 ≤ 2×p50 且丢包 = 0;⚠️ 原始丢包含尾部在途伪影(≈rate × RTT);扣掉后满足的最高档 = 10 玩家 @50 msg/s = 9.77 KB/s/玩家(50 玩家档最高 3.91)。边界:本值 = 传输层上限;游戏协议的真实需求仍是估值(PER_PLAYER_BW_TEXT/BATTLE/SIEGE)——⛔ 不得读成"实测出的游戏需求"

3.4 消息频率 / 扇出

键 值 单位 等级 来源定位 复算式
MSG_RATE_GLOBAL 50 msg/s 估值 覆盖网络_千台全场景推演_20260916.md:75 —
FANOUT_AVG 50 — 估值 同上 :75("均扇出 50") 50 × 50 = 2500 投递/秒
ROOM_FANOUT_LIMIT_100 100 人 估值 覆盖网络_调研_游戏网络特征与群聊上限_20260916.md:97(≤100 纯扇出即可) —
PRESENCE_FANOUT_1000ROOM 16700 次/秒 推导 覆盖网络_千台全场景推演_20260916.md:79 千人房 presence ≈ 1000 × 16.7 次/秒(原文直接给 16,700)

3.5 心跳 / 链路质量

键 值 单位 等级 来源定位 复算式
HB_SEC 15 s 实测(代码常量=运行真值) src/net/relay/server.ts:60 DEFAULT_HB_SEC = 15;运行期由 HELLO_ACK 下发(server.ts:816) grep -n DEFAULT_HB_SEC src/net/relay/server.ts
HB_SEC_DOC 20 s 估值(文档口径,与代码不一致) 覆盖网络_千台全场景推演_20260916.md:39「心跳间隔 20 s(沿用现有隧道自愈定时器)」 ⚠️ 该行属旧隧道时代口径 —— 现已换 relay(hbSec=15)⇒ 文档待更正,参数以 HB_SEC=15 为准
HALF_OPEN_MS 37500 ms 推导 src/net/relay/client.ts:602 max(3000, HB_SEC × 1000 × 2.5) = 15 × 1000 × 2.5
IDLE_TIMEOUT_MS 45000 ms 实测(代码常量) src/net/relay/server.ts:58 grep -n DEFAULT_IDLE_TIMEOUT_MS src/net/relay/server.ts
JITTER_LIMIT_MS 20 ms 估值(业界口径) 覆盖网络_调研_游戏网络特征与群聊上限_20260916.md:32+覆盖网络_千台全场景推演_20260916.md:90 —
JITTER_LINK_MEASURED 3(判定值 = `p95( ΔRTT );另记 mdev = 1.37–1.38 ms、avg` = 148.3 ms、丢包 1.3–1.7%) ms 实测
RELAY_RTT_W106 336 → 保留数值,但加口径修正:该值是心跳往返(server.ts:810:测 RTT / 察觉半开 / 保 NAT 表项),含应用层处理与验签,⛔ ≠ 网络 RTT ms 实测(口径受限) 本单 §8.4 E5(b) 三方对比:ICMP 148 ms | TCP 握手 median 153 ms(min 138.5)| relay 心跳 344–376 ms ⇒ 差 2.3×,触发"加口径备注"判据 判据(S3(b)):relay rttMs 与 ICMP 差距 > 2× ⇒ 加备注 + 在 OBS 侧登记"relay rttMs 不得当链路 RTT 用"
RELAY_RTT_MANAGER 7 ms 实测 /status → sessions[hostId=manager].rttMs 同上
RELAY_TTFB_S 0.68 s 实测 本次 S0 P7a:47 → relay 回环口 → w-106 任一端口,%{time_starttransfer}(6 次样本 0.676–1.083) ssh -p 22 bt-server 'curl -s --http1.1 -o /dev/null -w "%{time_starttransfer}\n" http://127.0.0.1:44133/'
WAN_UP_BOUND_KBPS 192 ⇒ 已被上行实测取代(值作废,保留作历史) KB/s 实测(下界) 作废 原 S0 P7 样本被 405 提前截断(131072 B / 0.685 s,含 RTT)⇒ 只是下界,且下界偏低 64× 用 WAN_STEADY_THROUGHPUT 的上行值 12213 作准
WAN_STEADY_THROUGHPUT 352(106→47)/ 12213(47→106) —— 取绑定方向(对端→中继机,即"内容从 worker 上来"那条)= 352 KB/s 实测 本单 §8.4 E4 + overlay-wan.cjs --download|--upload(各 5 样本) 口径三要素:① 谁到谁 = 106 的 w-106p:19777 ↔ 47 本机 relay 回环端点;② 经 relay(非裸链路);③ 稳态段 30 s(弃前 3 s,爬升段字节单独计)。中位数 352(min 345.6 / max 352);上行侧 12213(min 11784 / max 12452)。⚠️ 非对称的成因 = 106(云轻量)公网出带宽封顶 ≈ 2.8 Mbps,实测三向互证:/status 会话计数 in=1.905 GB + 106 侧进程 rchar=1.905 GB + 下载恒定 165×64 KB/30 s
WAN_UP_BOUND_KBPS 192 ⇒ 已被上行实测取代(值作废,保留作历史) KB/s 实测(下界) 作废 原 S0 P7 样本被 405 提前截断(131072 B / 0.685 s,含 RTT)⇒ 只是下界,且下界偏低 64× 用 WAN_STEADY_THROUGHPUT 的上行值 12213 作准

3.6 presence(节点在线态 · 序⑲)—— 帧率类参数

键 值 单位 等级 来源定位 复算式
PRESENCE_BATCH_MS 1000 ms 实测(代码常量=运行真值) src/net/relay/server.ts:106 DEFAULT_PRESENCE_BATCH_MS = 1_000 grep -n DEFAULT_PRESENCE_BATCH_MS src/net/relay/server.ts
PRESENCE_GRACE_MS 10000 ms 实测(代码常量=运行真值) src/net/relay/server.ts:89 DEFAULT_PRESENCE_GRACE_MS = 10_000 grep -n DEFAULT_PRESENCE_GRACE_MS src/net/relay/server.ts
PRESENCE_OFFLINE_DEBOUNCE_MS 30000 ms 实测(代码常量=运行真值) src/net/relay/server.ts:97 DEFAULT_PRESENCE_OFFLINE_DEBOUNCE_MS = 30_000 grep -n DEFAULT_PRESENCE_OFFLINE_DEBOUNCE_MS src/net/relay/server.ts
PRESENCE_TTL_MS 45000 ms 推导(代码常量 × 心跳) 服务端 src/net/relay/server.ts:604;客户端保守档 src/net/relay/client.ts:329 DEFAULT_PRESENCE_TTL_MS = 45_000(两处同值,实测一致) HB_SEC × 1000 × DEFAULT_PRESENCE_TTL_FACTOR = 15 × 1000 × 3(因子 = 3 见 server.ts:115)
PRESENCE_SUB_MAX 0 个 实测(代码常量;0 = 不限) src/net/relay/server.ts:122 DEFAULT_PRESENCE_SUB_MAX = 0 grep -n DEFAULT_PRESENCE_SUB_MAX src/net/relay/server.ts

⚠️ 这五个键只驱动在线态事件(谁在线 / 何时改口),⛔ 与 RELAY_FAILOVER_* / HB_SEC / burst 无耦合 —— 序⑲ 全程一字未动(E11 的 D1 自证)。 ⚠️ PRESENCE_TTL_MS 是安全网(兜"漏掉 close 事件"那条路),⛔ 不是常规下线路径:常规下线走 PRESENCE_GRACE_MS + PRESENCE_OFFLINE_DEBOUNCE_MS = 40 s。 ⚠️ 口径来源 = D3(最终一致:允许 5–15 s 陈旧),⛔ 三个阈值均按代码常量取,不许改口径去凑判据(E5)。 🔴 值格必须纯数字(夹注 / 混写单位 ⇒ 探针 NaN ⇒ 假红,序⑫ 已踩)。


§4 代码已固化常量(实测=直接可 grep 的源码真值)

键 值 单位 等级 来源定位 复算式
RELAY_PORT 20080 — 实测 src/net/relay/server.ts:52+单位文件 ExecStart grep -n DEFAULT_RELAY_PORT src/net/relay/server.ts
RELAY_BIND 127.0.0.1 — 实测 主单元 ExecStart(无 --host ⇒ 默认回环) ssh -p 22 bt-server "ss -lntp | grep 20080"
RELAY_INSTANCE_BASE 19000 — 实测 主单元 ExecStart --base 19000 同上
RELAY_INSTANCE_SPAN 3000 — 实测 主单元 ExecStart --span 3000 ⇒ 可声明窗口 [19000, 22000) 同上
AUTH_DEADLINE_MS 5000 ms 实测 src/net/relay/server.ts:54 grep
AUTH_WINDOW_MS 60000 ms 实测 src/net/relay/server.ts:56 grep
CAPACITY_RETRY_AFTER_MS 5000 ms 实测 src/net/relay/server.ts:63 DEFAULT_CAPACITY_RETRY_AFTER_MS grep
SHUTDOWN_GRACE_MS 300 ms 实测 src/net/relay/server.ts:69 grep
MAX_STREAMS_PER_PORT 64 条 实测 src/net/relay/server.ts:71 grep
QUEUE_MAX_BYTES 1048576 B 实测 src/net/relay/server.ts:73(1 << 20) grep
WS_PEER_HIGH_WATER 262144 B 实测 src/net/relay/server.ts:76(256 * 1024) grep
CLIENT_HIGH_WATER 262144 B 实测 src/net/relay/client.ts:230 grep
CLIENT_QUEUE_MAX 1048576 B 实测 src/net/relay/client.ts:229 grep
DIRECTORY_REFRESH_SECONDS 300 s 实测 src/net/relay/directory.ts:60 grep
DIRECTORY_FETCH_TIMEOUT_MS 5000 ms 实测 src/net/relay/directory.ts:521 grep
DIRECTORY_MAX_ENTRIES 8 条 实测 src/net/relay/directory.ts:63 MAX_ENTRIES grep
DIRECTORY_MAX_ENTRY_LEN 512 字符 实测 src/net/relay/directory.ts:64 MAX_ENTRY_LEN grep
DIALER_POOL 64 口 实测 src/net/relay/dialer.ts:55 DEFAULT_POOL+/etc/dshs.env DSHS_RELAY_DIAL_POOL grep+ssh -p 22 bt-server 'grep DIAL_POOL /etc/dshs.env'
DIAL_PORT_BASE 25000 — 实测 /etc/dshs.env DSHS_RELAY_DIAL_PORT_BASE 同上
DIAL_PORT_SPAN 1000 — 实测 /etc/dshs.env DSHS_RELAY_DIAL_PORT_SPAN(⚠️ 实绑只到 base+POOL) 同上
DIAL_POOL_BOUND 25000–25063 — 实测 47 上 ss -lntp(64 口全绑)+journal [relay-dialer] 本机落点池就绪:64 个口 ssh -p 22 bt-server "ss -lntp | grep -c 250"
W47_AGENT_PORT 19100 — 实测 /etc/dshs.env DSHS_CLUSTER_AGENT_URL=http://127.0.0.1:19100 ssh -p 22 bt-server 'grep AGENT_URL /etc/dshs.env'
LOCAL_INSTANCE_PORT 20000 — 实测 47 上 ss -lntp(127.0.0.1:20000) ssh -p 22 bt-server "ss -lntp | grep 20000"
PEER_AGENT_PORT 19000 — 实测 /status → endpoints[]/online[](对端 w-106 的 ports=19000/21000) ssh -p 22 bt-server 'curl -s 127.0.0.1:20080/status'
PEER_INSTANCE_PORT 21000 — 实测 同上 同上
RELAY_FAILOVER_MIN_ATTEMPTS 3 次 实测 src/net/relay/switcher.ts#relayFailoverThresholds(env 可覆写;置 0 = 总开关关闭) grep -n RELAY_FAILOVER_MIN_ATTEMPTS src/net/relay/switcher.ts
RELAY_FAILOVER_GRACE_MS 15000 ms 实测 同上(与 MIN_ATTEMPTS 取或:106 侧首连窗口长,只看次数会误切) grep
RELAY_FAILOVER_COOLDOWN_MS 300000 ms 实测 同上(与 DIRECTORY_REFRESH_SECONDS 对齐;冷却期内不回跳) grep
RELAY_FAILOVER_DEADLINE_MS 30000 ms 实测 同上(验收判据:从不健康到切换完成的允许上限) grep
RELAY_FAILOVER_CHECK_MS 2000 ms 实测 同上(健康巡检周期;独立于目录刷新周期) grep
RELAY_FAILOVER_UP_TIMEOUT_MS 12000 ms 实测 同上(只用于换址:新通道必须真到 up 才算「建起来了」)。⚠️ 序⑨:死候选提前失败后它不再是恒等代价(实测死候选 ≈ 0.1–1 s 就返回,慢候选仍享受完整 12 s) grep
RELAY_GRACEFUL_BURST_MS 15000 ms 🆕 实测 序⑨ 参数表化:client.ts 的 gracefulBurstMs("计划内下线 ≠ 故障"的快速重试窗口)原先是全仓唯一一个不可配的时延常量(只有 ?? 15_000 一处),现可经 env 覆写;⛔ 默认值语义逐字不变。⚠️ 它就是检测段 15.0 s 地板的真身:窗口内 attempts 恒为 0 ⇒ unhealthy() 只能靠 graceMs 成立(序⑨ §1.2-RC-2)⇒ 改它 = 改"计划内重启不触发切流"的窗口,先回写交接单(D3) grep -n gracefulBurstMsDefault src/net/relay/client.ts
RELAY_FAILOVER_EXEMPT 1 — 实测 同上(🆕 序⑧:一跳豁免总开关,1=开 / 0=关;RELAY_FAILOVER_MIN_ATTEMPTS=0 是另一层,⛔ 别混) grep -n RELAY_FAILOVER_EXEMPT src/net/relay/switcher.ts
DRILL_COOLDOWN_MS 90000 ms 推导 🆕 序⑧:演练期冷却覆盖值(经 dshs.service.d/zz-drill-override.conf 注入)——⛔ 生产默认恒为 RELAY_FAILOVER_COOLDOWN_MS=300000。取值口径:必须 ① ≫ 静默失效检测时延(实测 ≈ 29 s,否则"冷却未到期"判据根本来不及观测)② ≪ DIRECTORY_REFRESH_SECONDS(300 s)(让冷却先过期、目录巡检后到) —
DRILL_NO_SWITCH_OBSERVE_MS 45000 ms 推导 🆕 序⑧:幕 4b 的**"预期不切换"观察窗**;⛔ 必须 ≥ 失效检测时延(否则 D6 现场还没形成)且 < DRILL_COOLDOWN_MS(否则会跨过冷却期满、把"自然回归"误判成"切了") —

🆕 序⑦ 新增 5 键(中继失败切流):默认值 = 源码真值(可直接 grep); ⛔ 值格必须纯数字(夹注 ⇒ NaN ⇒ 假红,见 §8.8-2 教训); 🔑 回滚开关 = RELAY_FAILOVER_MIN_ATTEMPTS=0 ⇒ 监管器永不触发,行为回到「原地退避重试」的现状(§7 回滚第 1 层)。

🆕 序⑧ 新增 3 键(切流冷却语义拆分):RELAY_FAILOVER_EXEMPT(代码侧,默认 1)+ DRILL_COOLDOWN_MS / DRILL_NO_SWITCH_OBSERVE_MS(只在演练脚本里用)。 🔑 本单首选回滚点 = RELAY_FAILOVER_EXEMPT=0 ⇒ 只关掉豁免、序⑦ 的切流能力全部保留; ⛔ 它不是 MIN_ATTEMPTS 的同义词(后者关整个监管器)。 ⚠️ 演练期改冷却只能经 DRILL_COOLDOWN_MS 注入 drop-in;⛔ RELAY_FAILOVER_COOLDOWN_MS 的代码默认值不动(D9)。

⚠️ 键名口径:LOCAL_* = 中继机(47)本机的实例/代理口;PEER_* = 对端节点(今天= w-106)的等价口。 (键名里不带 106 是为了让 overlay-probe.cjs 的零数字纪律成立 —— 见交接单 §6 E6。)


§5 本单新增:45% 口径填值(443 单 §8.8 留下的空位)

443 单 §8.8 登记行原文:「兜底启用后,relay 容量须按 45% 的节点走中继核算(异构纪律,⛔ 不是同构的 15%)」。 口径落点(按交接单 §4.1-3):填单台中继的 --max-hosts,⛔ 不填"全网 45%" —— 后者只作校验用。

5.1 输入(全部来自本表其它行,⛔ 无外部魔数)

键 值 单位 等级 来源定位 复算式
MEM_TOTAL_MB 1870 MB 实测 S0 P6:free -m 第 1 行 ssh -p 22 bt-server 'free -m | head -2'
MEM_AVAILABLE_MB 1002 MB 实测 S0 P6:free -m 的 available 列 同上
MEM_BUDGET_MB 1002 MB 推导 本表 = MEM_AVAILABLE_MB(47 同时跑 PG+Manager+nginx,故只取 available,⛔ 不用 total)
CPU_CORES 2 核 实测 S0 P6:nproc ssh -p 22 bt-server nproc
FD_LIMIT 262144 个 实测 S0:/proc/<relay pid>/limits Max open files ssh -p 22 bt-server 'cat /proc/$(systemctl show -p MainPID --value dshs-relay)/limits | grep "open files"'
RELAY_RSS_KB 72888 KB 实测 S0:ps -o rss= -p <relay pid>(used=2 时,含 Node 基座) ssh -p 22 bt-server 'ps -o rss= -p $(systemctl show -p MainPID --value dshs-relay)'
MEM_PER_HOST_MB 0.06 MB/台 实测(原为推导 2) 本单 §8.4 E8 + relay-mem-calibrate.mjs:本机独立 relay 实例 + 同进程合成 client N=2/10/25/50/100/150,每点 7 次采样取中位数 ⇒ 线性回归 RSS(N) = 48078 + 46.7·N KB,R² = 0.942(≥ 0.9 ⇒ 有效) 46.7 KB/台 × 1.2 余量 = 56.0 KB = 0.0547 MB ⇒ ceil 到 0.01 MB 位 = 0.06。⚠️ 原推导 2 MB 高估 36×:它把 per-stream 的 256 KB 高水位算进了 per-host;本值是空闲会话口径,带流量的 per-stream 开销未测(登记于 §9)
FD_PER_HOST 4 个/台 推导 1 条 WS + 每声明端口 1 个回环 listener + 2 个临时 ⇒ 上界 4 保守取上界
DESIGN_MARGIN 0.45 — 估值 443 单 §8.8 原文(异构纪律;⛔ 非同构 15%) —

5.2 推导式(⛔ 可独立手算,E2 判据)

C_MEM       = floor(MEM_BUDGET_MB / MEM_PER_HOST_MB)   = floor(1002 / 0.06)    = 16700     # ← 序⑥ S7 重算(原 floor(1002/2)=501)
C_FD        = floor(FD_LIMIT / FD_PER_HOST)            = floor(262144 / 4)    = 65536
C_RELAY     = min(C_MEM, C_FD)                          = 16700      # 带宽仍不参与取 min,见 §5.4
RELAY_MAX_HOSTS = floor(C_RELAY × DESIGN_MARGIN)        = floor(16700 × 0.45)  = 7515      # ← 原 225
键 值 单位 等级 来源定位 复算式
C_MEM 16700 台 推导 本表 floor(1002 / 0.06)
C_FD 65536 台 推导 本表 floor(262144 / 4)
C_RELAY 16700 台 推导 本表 min(16700, 65536)
RELAY_MAX_HOSTS 7515 台 推导 本表 floor(16700 × 0.45)(⚠️ 本单元格必须是纯数字 —— 探针 KEY_RE/cleanValue 不认识"(原 225)"这类夹注,会把整格当值 ⇒ NaN ⇒ OBS-02 假红;旧值 225 记在此列)。🔴 🔴 序⑥ S7 回头条件已触发并执行(2026-09-17):MEM_PER_HOST_MB 2 → 0.06、WAN_STEADY_THROUGHPUT 待测 → 352/12213 ⇒ 已重下发 47 与 106 两台的 capacity.conf(--max-hosts 7515),OBS-02 复验 PASS

校验(三条,全部满足才生效)—— 序⑥ S7 重跑记录

  • ① 防自锁:RELAY_MAX_HOSTS > used × 4 ⇒ 7515 > 4 × 4 = 16 ✅(验收时 used = 4)
  • ② 设计余量自洽:7515 / 16700 = 45.0% ≈ DESIGN_MARGIN ✅
  • ③ 千台需求校验:FLEET_RELAY_DEMAND = 450 ≤ RELAY_MAX_HOSTS = 7515(原为 450 > 225)⇒ 🔴 结论变更:"容量上必须 ≥2 台中继"这条理由随实测消失(单台容量已足够)。2 台的依据改为 ① 清单 §3 关键决定「有公网 IP 的节点自动升格中继候选」② 跨机真容灾(106 已升格,见 §8 ④)。⚠️ "零余量"这一旧事实不再成立,代之以下面那条:绑定约束从"内存"搬到了"带宽/时延"(见 §5.4)。

5.3 --max-hosts 的分母口径(⛔ 必须写清,否则数字会被误读)

--max-hosts 是每台中继的在册会话(host)数上限,它不是"全网 45%",分母是这一台中继;FLEET_RELAY_DEMAND(450)只是校验上界。今天生产上只有 1 台中继(47)⇒ 本值直接生效。

5.4 为什么带宽不参与 min(⛔ 不是漏了)—— 序⑥ S7 用实测重判

  • 判据(S7-2 原文):若新实测吞吐使"225 台(今为 7515 台)的控制面 + 实例面流量"逼近实测吞吐 ⇒ 带宽进 min。
  • 控制面(用实测值重算):1 / HB_SEC = 0.067 次/秒/台 × 7515 台 ≈ 504 次/秒;每台每秒控制面流量 ≈ 100 / HB_SEC = 6.7 B/s ⇒ 7515 台 ≈ 50 KB/s,对比绑定方向实测 352 KB/s(余量 7×)⇒ 控制面不绑定。
  • 实例面:不是"稳态负载"而是"单次传输",正确的判据是时延不是容量 —— 单次 46.3 MB 冷启动 ÷ 352 KB/s ≈ 135 s(原推算按 192 KB/s 是 247 s,按 22 KB/s 旧值则是 36 min)。⇒ ⛔ 仍不进 min,但登记为实例面单次传输的时延上界,并给出唯一的改进方向(提升对端公网出带宽,而非加中继)。
  • ⚠️ 本条的边界:352 KB/s 是 106(云轻量)出带宽封顶造成的,不是 relay 栈上限(上行方向实测 12213 KB/s 证明栈本身没到这个量级)。

5.5 /status.capacity 的语义纠正(P2 实测 vs 交接单期望)

事实 原文
max = 0 时只有 {max, used},没有 free src/net/relay/server.ts:502-506:this.maxHosts > 0 ? {max, used, free} : {max, used};实测 /status = "capacity": {"max": 0, "used": 2}
free 只有在 max > 0 时出现 同上 ⇒ E3 的 free = max - used 判据只在设值后成立(设值前 free 字段不存在,不是 null/不是 0)
满载时的 free 恒为 0 server.ts:761:capacity: { max, used, free: 0 }(拒绝载荷里)
used = 在册会话数(= host 数,不是流数、不是端口数) server.ts:505:used: this.sessions.size
retryAfterMs 口径 = 满载时给对端的排队建议 CAPACITY_RETRY_AFTER_MS = 5000 ms;客户端不消耗退避地排队(client.ts:532 / 881-896,附A 已核)

5.6 443 单 §8.7③ 要求的回答:relays[] 顺序语义

本单必须回答:443 单留下的「若将来 relays[] 引入非首位更优的显式优先级语义 ⇒ 需重新定义同源优先与它的先后关系」。

回答:本单不引入该语义。relays[] 的顺序语义固定为「主入口首位」—— 首位是主入口,其余是兜底候选;不存在"非首位更优"的显式优先级字段。⛔ 本表不新增任何优先级键(新增即等于把这条语义改了)。


§6 观测阈值(探针脚本的唯一取数来源;⛔ 脚本内无魔数 = E6)

ID 指标 阈值 判据
OBS-01 relay 在册会话数(used) MIN_HOSTS used ≥ MIN_HOSTS
OBS-02 capacity.max / free RELAY_MAX_HOSTS max = RELAY_MAX_HOSTS 且 free = max - used
OBS-03 身份强制与受信签名者 MIN_TRUSTED_SIGNERS identityRequired = true 且 trustedSigners ≥ MIN_TRUSTED_SIGNERS
OBS-04 通过节点凭据校验的注册数 MIN_IDENTITY_OK identityOk ≥ MIN_IDENTITY_OK
OBS-05 吊销清单规模 MAX_REVOKED_HOSTS revokedHosts ≤ MAX_REVOKED_HOSTS
OBS-06 判别器计数三件套存在 (字段存在性) typeof dial/dialDenied/dialFailed === 'number'
OBS-07 鉴权失败累计 MAX_AUTH_FAILED authFailed ≤ MAX_AUTH_FAILED
OBS-08 端点表全在线 (无 false) endpoints[].online 全为 true 且非空
OBS-09 双实例探活(w-47 实例面 / w-106 实例面经 relay) PROBE_CODE_SET 两个 HTTP 码均 ∈ PROBE_CODE_SET
OBS-10 门户可达 PORTAL_CODE = PORTAL_CODE
OBS-11 零新增暴露面(监听集合 / nft 入站 accept 集合 / relay 只绑回环) LISTEN_REQUIRED / LISTEN_ALLOWED / LISTEN_ALLOWED_RANGES / DIAL_POOL_BOUND / NFT_ALLOW_INBOUND / RELAY_BIND 三集包含式:必在 ⊆ 实际 ⊆ 必在 ∪ 允许 ∪ 区间(含拨号池) ∪ 派生,差集逐条点名;且 nft 入站 accept ⊆ NFT_ALLOW_INBOUND;RELAY_PORT 只出现在 RELAY_BIND 上。🔴 序⑫:旧口径「计数相等」已退役 —— 它对实例/端点在线态敏感 ⇒ 假红,对"一进一出"替换式变化不敏感 ⇒ 假绿
OBS-12 relay 进程内存 RELAY_RSS_MAX_KB RSS ≤ RELAY_RSS_MAX_KB
OBS-13 presence 帧率(稳态) PRESENCE_STEADY_FRAMES_MAX / PRESENCE_SAMPLE_HITS_MIN 两次 /status 采样的增量:Δpushed ≤ 0 且 ΔstatusHits ≥ 1(活性证明:⛔ 不许"因为读不到所以看起来是 0");并附口径一致(presenceTiming 逐项 == PRESENCE_*)+ 判别器齐全(subs/pushed/snaps/rejected/statusHits 皆 number)+ snaps ≤ pushed
OBS-14 首帧即全量 SNAP(⛔ 无 N+1) (snaps / pushed / subs 三者关系) snaps ≤ pushed 且 subs > 0 ⇒ snaps ≥ 1(有订阅者却一帧 SNAP 都没发 ⇒ 首帧走"逐台拉",判 FAIL)
OBS-15 在线态表不撒谎 + 陈旧度 PRESENCE_STALE_P95_MAX_MS / PRESENCE_TTL_MS 活跃会话(lastSeenAgoMs ≤ PRESENCE_TTL_MS)必须被 presence[] 覆盖且 online = true;且 p95(lastSeenAgoMs over devices>0) ≤ PRESENCE_STALE_P95_MAX_MS

阈值取值(同为参数行,⛔ 不是脚本魔数)

键 值 单位 等级 来源定位 复算式
MIN_HOSTS 2 台 实测 S0 P2 /status.online[](manager + w-106) online.length
MIN_TRUSTED_SIGNERS 1 个 实测 S0 P2 trustedSigners = 1 —
MIN_IDENTITY_OK 2 次 实测 S0 P2 identityOk = 2(⛔ 不是 3:序③ 注释里的 3 是三台节点当时的口径) —
MAX_REVOKED_HOSTS 0 个 实测 S0 P2 revokedHosts = 0+/etc/dshs/revocations.json 空清单 —
MAX_AUTH_FAILED 50 次 推导 累计值会随时间涨(⚠️ 探针自己也污染它,见交接单 §5 S6);取"远大于稳态自然增长、又能挡住暴力破解"的档 经验上界
PROBE_CODE_SET 200,401 — 实测 S0 P7a:两个面在无凭据时回 401;带门户 Host 时回 200 curl -w '%{http_code}'
PORTAL_CODE 200 — 实测 S0 P2/S6:curl -s --http1.1 -o /dev/null -w '%{http_code}' -H "Host: alotbuy.com" 127.0.0.1:3080/ 同左
LISTEN_COUNT 79 行 实测 ⛔ 已退役(序⑫ 集合判据替代)—— 仅对账用,不得再作为判据。原 S0 P5:ss -lntp | wc -l(与 443 单收口态逐字一致 ✅;⚠️ 该值含"47 有活跃实例"那一档,故与无实例态恒差 1 层) 同左
NFT_RULES 72 行 实测 ⛔ 已退役(序⑫ 集合判据替代)—— 仅对账用,不得再作为判据。原 S0 P5:nft list ruleset | wc -l(与 443 单收口态逐字一致 ✅;⚠️ 行数不是暴露面:47 的 input 链一条规则都没有、policy=accept) 同左
RELAY_RSS_MAX_KB 800000 KB 推导 由 §5 MEM_BUDGET_MB × 0.8 得 ≈ 800 MB(不随 MEM_PER_HOST_MB 变,故序⑥ 校准后仍保留该值)。⚠️ 原注"每 host 2 MB 假设的哨兵"已作废:实测斜率 46.7 KB/台 ⇒ 7515 台时预计 RSS ≈ 48078 + 46.7×7515 ≈ 390 MB,本哨兵(781 MB)仍留 2× 余量;且因 per-stream 开销未测(§9),运行期真正的操作约束就是本哨兵,不再是 --max-hosts MEM_BUDGET_MB × 1000 × 0.8

序⑲ 新增阈值键(OBS-13/14/15 的取数来源)

键 值 单位 等级 来源定位 复算式
PRESENCE_STEADY_FRAMES_MAX 0 帧 实测(口径 = 交接单 §6 E1) 稳态"变化驱动"⇒ 无状态变化则一帧都不推 判据本身(帧数,⛔ 不是时长)
PRESENCE_SAMPLE_HITS_MIN 1 次 推导 探针自己连读两次 /status ⇒ ΔstatusHits 至少 1;用来把"读不到"与"确实为 0"分开(本线反复踩的假绿) 探针一次采样 = 1 次读
PRESENCE_SAMPLE_GAP_MS 3000 ms 推导 必须 ≥ 3 × PRESENCE_BATCH_MS(否则批窗口还没过去,增量无意义) PRESENCE_BATCH_MS × 3 = 1000 × 3
PRESENCE_STALE_P95_MAX_MS 15000 ms 推导(= 心跳周期) D3:允许 5–15 s 陈旧;心跳每 HB_SEC 刷新一次 lastSeen ⇒ 稳态陈旧度的上界就是心跳周期 HB_SEC × 1000 = 15 × 1000

🆕 序⑫ 新增 4 键(OBS-11 集合判据的白名单,⛔ 取代 LISTEN_COUNT / NFT_RULES) ⚠️ 值格必须纯数字/纯 地址:端口(夹注 ⇒ NaN ⇒ 假红);⛔ 值内不得含 |、*、反引号 (探针 cleanValue() 会剥掉 * 与反引号 ⇒ 写 *:443 会被改成 :443 ⇒ 判据全红)。

键 值 单位 等级 来源定位 复算式
LISTEN_REQUIRED 0.0.0.0:22,[::]:22,0.0.0.0:80,0.0.0.0:443,0.0.0.0:888,127.0.0.1:3080,127.0.0.1:20080 — 实测 🆕 序⑫ S1:必在集(缺一条 ⇒ FAIL)。口径 = 平台工作必需 且 不受实例/端点在线态影响的固定口。值按 47 上 ss -lntp 原文逐条写;⚠️ 3080 的口在 47 上是 127.0.0.1:3080(⛔ 不是 0.0.0.0:3080 —— 原规划稿示例写错,以原文为准) ssh -p 22 bt-server "ss -lntp"
LISTEN_ALLOWED 0.0.0.0:58888,0.0.0.0:8765,127.0.0.1:15432,127.0.0.1:19100 — 实测 🆕 序⑫ S1:允许集(非必在) —— 出现合法、消失不红(只是某个可选项没开:宝塔面板口 / 管理 UI / 控制面 PG / 本机 worker agent) 同上
LISTEN_ALLOWED_RANGES 127.0.0.1:20000-20999,127.0.0.1:21000-21999 — 实测 🆕 序⑫ S1:区间允许集 = 两台 worker 的实例端口区间。⚠️ span 取 1000(⛔ 不是规划稿示例的 100):dshs-worker 单元实测 DSHS_INSTANCE_PORT_BASE=20000(47,经 /proc/<pid>/environ)/=21000(106,经 /etc/dshs-worker.env)+ 两者 DSHS_INSTANCE_PORT_SPAN=1000 ⇒ 并集 [20000,22000) 正好落在 relay 声明窗口 [19000,22000)(--base 19000 --span 3000)之内。拨号池⛔不在此键内(复用 DIAL_POOL_BOUND 派生,避免两处漂移) ssh -p 22 bt-server "tr '\0' '\n' < /proc/$(ss -lntpH 'sport = :19100' | grep -oP 'pid=\K[0-9]+' | head -1)/environ | grep INSTANCE_PORT"
NFT_ALLOW_INBOUND tcp:22,tcp:80,tcp:443,tcp:888,tcp:3080,tcp:58888 — 实测 🆕 序⑫ S4:入站 accept 白名单(nft -j 归一后 acceptSet ⊆ 本键)。归一形态 = <proto>:<dport>;「无 dport 匹配的 accept 规则」归一为 <proto>:any —— ⚠️ 47 的 input 链目前 0 条规则 ⇒ 集合为 ∅、本键暂无标记位;将来若出现必须显式加进来(⛔ 不许放宽判据) ssh -p 22 bt-server "nft -j list ruleset"

📌 成员归属判据(序⑫ S1 明文要求,防下一棒再吵):

  • 必在(LISTEN_REQUIRED)= 它不在 ⇒ 平台本身坏了(sshd / nginx 80·443 / 宝塔 888 / 平台门户 3080 / relay 回环口)
  • 允许(LISTEN_ALLOWED + _RANGES)= 它不在 ⇒ 只是某个可选项没开(面板口 / 管理 UI / PG / worker agent / 实例档 / 拨号池)
  • 🔑 "有/无实例态"⛔不编码进参数表(那等于把"对状态敏感"这条缺陷写进单一来源 = 净退化,违 R11)⇒ 实例档直接进允许区间:出现不报、消失不红。

⚠️ 附注(序⑥ S3(b) 明文要求登记的 OBS 侧口径):relay /status.sessions[].rttMs 是心跳往返 (含应用层处理与验签),⛔ 不得当链路 RTT 用,⛔ 更不得据它下"跨云不可玩"的结论 —— 实测三方对比:ICMP 148 ms | TCP 握手 median 153 ms | relay 心跳 344–376 ms(差 2.3×)。 真判据请用 overlay-jitter.cjs --icmp/--tcp。


§7 待测项汇总(⛔ 一个都不许编数;与表内 待测 单元格一一对应)

🔢 计数口径(E1 判据):表内 待测 单元格 4 个 → 0 个(序⑥ 全部换成实测,见下表「结果」列)。 第 5 条是「推导值待校准」(MEM_PER_HOST_MB)—— 序⑥ 已校准为 实测 0.06 MB/台。

# 键 换测条件 归属 序⑥ 结果
1 HOLE_PUNCH_RATE_LOCAL 3–5 台真机(分层:家宽 / CGNAT / 移动 / 企业网) 序⑥ ✅ 实测(分层):n=3 对,2/2 对可打洞(对端→本机 10/10);⚠️ 首选观察器不可用(云安全组拦 UDP 入站)⇒ 走第三方 STUN;不代表家宽场景
2 PER_PLAYER_BW_LOCAL 同上 序⑥ ✅ 实测:9.8 KB/s/玩家(n=10)/ 3.9(n=50);聚合天花板 200–350 KB/s
3 WAN_STEADY_THROUGHPUT 两端可控载荷(本次两个面在无凭据时只回 24–68 B) 序⑥ ✅ 实测:352 KB/s(106→47)/ 12213 KB/s(47→106),5 样本中位数,经 relay,稳态段 30 s
4 JITTER_LINK_MEASURED 同上;⚠️ 这是游戏可玩性的决定项(调研…:118) 序⑥ ✅ 实测:`p95(
5 MEM_PER_HOST_MB(推导值待校准) 压到 ≥ 20 台再量 RSS 斜率 序⑥ ✅ 实测:N=2..150 六点,斜率 46.7 KB/台,R²=0.942;原推导 2 MB 高估 36×

✅ 序⑨ 收口(2026-09-17 14:xx):无新增待测项(表内 待测 仍为 0 个)。本轮新增的两个键 (DRILL_SAMPLE_N = 5 / RELAY_GRACEFUL_BURST_MS = 15000)都是实测/推导值,⛔ 不含 待测; DRILL_POLL_MS 由 2000 改 500 属测量修正(剔掉 ≤2 s 量化误差),⛔ 不是待测项换测。 另:序⑨ 的四段分解登记在 §9 第 9 行(属"已知边界/实测记录",不进本表 §7 的"待测"口径)。

合计:待测 4 项 → 0 项;待校准推导 1 项 → 已校准。(E1 判据达标)

🆕 序⑦(2026-09-17)复算:仍为 待测 0 项 —— 序⑦ 新增的 RELAY_FAILOVER_* 5 键全部取默认值(= 源码真值),不产生新的「待测」;同时 §9 第 5 行(无失败切流)改判已闭环。⛔ §9 第 6 行(per-stream 内存)仍未测,仍是序⑦ 之后的回头条件。

🆕 序⑧(2026-09-17)复算:仍为 待测 0 项 —— 序⑧ 新增 3 键(RELAY_FAILOVER_EXEMPT = 源码默认值 1; DRILL_COOLDOWN_MS / DRILL_NO_SWITCH_OBSERVE_MS = 演练脚本自用的推导值)均不产生「待测」。 ⚠️ 两条口径要记住:① DRILL_COOLDOWN_MS 是演练期覆盖值,⛔ 不是生产值(生产恒为 RELAY_FAILOVER_COOLDOWN_MS=300000); ② 幕 4b 的结论标注了非生产冷却值方可复现(D9)。§9 第 5 行追加"冷却语义拆分已闭环";§9 第 6 行仍未测。

🔴 回头条件(已执行,2026-09-17 序⑥ S7):MEM_PER_HOST_MB 与 WAN_STEADY_THROUGHPUT 双双换成实测 ⇒ §5.2 已重算并重下发两台中继的 capacity.conf(225 → 7515),三条校验已重跑,OBS-02 复验 PASS。⛔ 未触发项:无(两项都换成了实测)。


§8 权限影响评估(R5;只有产出,无动作)

交接单 §4.1-7 的固定 6 列:对象 / 是否扩大权限面 / 扩大到哪一类 / 是否已可收窄 / 证据 / 结论。 ⛔ 结论列只允许「收窄」或「维持」;若某项确需扩大 ⇒ 停下报告(命中 R5)。

# 对象 是否扩大 扩大到哪一类 是否已可收窄 证据(代码行 / 命令原文) 结论
① 虚拟网卡驱动(打洞所需) 否(本单零动作) 若将来做 ⇒ 权限位(管理员/内核态驱动) ✅ 可收窄:不做虚拟网卡(纯 relay 中继形态已闭环),要打洞也只走 UDP 用户态(无驱动) 本表 §3.2 HOLE_PUNCH_RATE_LOCAL = 待测;现网 2 节点全走 relay(/status.networks[0].sessions = [manager, w-106]);⚠️ 我方 relay 只做 TCP/WS,代码里没有任何 TUN/TAP 调用(grep -rn "tun|tap|ip tuntap" src/net/relay/ = 0 命中) 维持
② 骨干节点开端口 否 — ✅ 已收窄:骨干成员只拨出、不开入站(worker 永远只拨出,106 入站 = 0) 主单元 ExecStart 无 --host ⇒ 默认绑 127.0.0.1;实测 ss -lntp 中 20080 只出现在 127.0.0.1:20080;覆盖网络_骨干层方案_20260916.md「骨干不得被默认征用」 维持
③ nft 打洞规则 否(本单零动作) 若将来做 ⇒ 入站面(放行 UDP 入站) ✅ 可收窄:现实测 nft list ruleset = 72 行且与 443 单收口态逐字一致 ⇒ 打洞规则一条都没加 S0 P5:ssh -p 22 bt-server 'nft list ruleset | wc -l' → 72;对比交接单 §6 E9 的收口态 72 维持
④ 第二中继机(L3 跨机真容灾) 否(已实施;R5 要求的"实施态"逐项如下) 若实施 ⇒ 入站面(新机新公网口)+凭据面(新节点密钥签发) ✅ 已实施且两项都没扩大:新增监听口 = 0(relay 仍只绑 127.0.0.1:20080;对外复用 106 既有 nginx 的 443,证书 = 本机既有 Let's Encrypt(SAN 覆盖 IP 106.54.21.172),⛔ 未下发任何证书/私钥)|新增凭据 = 0(relay 侧只需公钥类文件:relay-keys.json、overlay-signers.json、revocations.json;⛔ node-*.key 与 overlay-signer-key.pem 一律留在 47)|106"入站 = 0"这条已收窄成果:443 本来就开着(宝塔 nginx),本次只在既有 server 块内加一条 location ⇒ ss -lntp 计数未变 systemctl cat dshs-relay(106)|ss -lntp|106 的 extension/106.54.21.172/relay-b.conf 维持
⑧ 临时 UDP 入站面(序⑥ S4 打洞探测的观察器) 是(临时,已关闭) 入站面(UDP 高位口) ✅ 已收窄:对象 = 47 的 0.0.0.0:21100/21101(overlay-holepunch.cjs --observer),开放时长 ≈ 75 s、进程退出即释放;关闭证据 = ss -lunp | grep 2110[01] ⇒ 空。⚠️ 实测该口零收包(连同机发出的都收不到)⇒ 47 的云安全组拦 UDP 入站(⛔ 不是 nft:input policy = accept)⇒ S4 改走第三方 STUN ss -lunp(空)|本单 §8.4 E6 维持
⑨ 106 的 443 relay 路由(序⑥ S8 新增 location /dshs-relay) 是(新增可路由路径,⛔ 未新增监听口) 入站路径面(不是新口) ✅ 已收窄:只放行 /dshs-relay 一个端点(⛔ 不写 location /、不复制站点任何路径;未知路径 404);relay 侧仍强制 HMAC +节点凭据(identityRequired = true) 本单 §8.6 + curl -H "Upgrade: websocket" … https://106.54.21.172/dshs-relay = 101|/nope = 404 维持(监听口零新增)
⑤ 443 兜底入口(复核是否真零扩大) 否 — ✅ 已收窄(相对 sshd 时代净减 1 个公网口) 实测:32022 已回收(ss -lntp 无该口);443 由 nginx 复用(ss -lntp 显示 0.0.0.0:443 + nginx 4 个进程),未新增监听;/etc/dshs.env 的 DSHS_OVERLAY_BOOTSTRAP_SEEDS 含 relay-direct.alotbuy.com/dshs-relay,而 DSHS_OVERLAY_ADDR_OVERRIDES=relay-direct.alotbuy.com=47.77.182.89 ⇒ 走的是既有 443 维持
⑥ relay 拨号白名单 dialers(R5 引入) 否 — ✅ 已收窄:白名单是准入收窄(默认拒绝,⛔ 不是放开);且构造时定型、运行期不可改 src/net/relay/server.ts:355 private readonly dialers;:404 normalizeDialers() 在构造期;:747 const wantDialer = this.dialers.get(network)?.has(hostId) === true(默认拒绝);drop-in dialers.conf = Environment="DSHS_RELAY_DIALERS=ops:manager"(只有 1 个拨号方) 维持
⑦ 每机独立密钥与信任根保管 否 — ✅ 已收窄:私钥只在本机、信任根在离线签发;relay 侧只有公钥与签名者集合 /etc/dshs.env DSHS_OVERLAY_NODE_KEY_FILE=/etc/dshs/node-manager.key(私钥路径仅本机);drop-in identity.conf 里只有 DSHS_OVERLAY_ROOT_PUBKEYS=<公钥> + DSHS_OVERLAY_SIGNER_SET_FILE ⇒ 私钥从不出现在 relay 的配置面;:399 this.trustedSignerKeys/:401 this.requireIdentity 均 readonly 维持

两条附注

  • ⚠️ 本表 §5.1 的「每 host 0.06 MB」是空闲会话的实测斜率 ⇒ 它不覆盖 per-stream 缓冲(见 §9 新登记行)。
  • ⛔ 本表口径仍成立:零新增公网端口、零新增入站面、零凭据外发;序⑥ 的两处"潜在扩大项"(④ 第二中继 / ⑧ 临时 UDP / ⑨ 443 路由)逐条列在表内,结论均为维持或收窄。

§9 未纳入本表的已知边界(登记,不改)

# 边界 为什么不动 归属
1 引导链缓存两支无"答出者"信息 ⇒ 切兜底有 ≤ DIRECTORY_REFRESH_SECONDS(300 s)收敛期 交接单 §8.7④ 明确"本单把 300 s 收进参数表并写明该收敛期,⛔ 不改缓存结构" 已闭环(本表 §4)
2 106 的 agent 面不吃引导链(DSHS_RENDEZVOUS_URL 被当 relay URL 直接用) 交接单 §2 P8:只记录、⛔ 不许顺手撤(撤掉 = tunnel===undefined 生产回归) 若要改,归序⑥
3 D2 字面判据不可满足(CF 泛解析) 未做灰云记录 ⇒ 属"解析层也不经 CF"的更大改动 序⑥ 后候选
4 relay 进程没有 MemoryMax(只有本表的软口径 --max-hosts) 加 cgroup 内存上限 = 改单元语义 + 可能 OOM-kill relay(新失败模式)⇒ 超出本单范围,且 R11(不许净变差) 序⑥ 候选(与 MEM_PER_HOST_MB 校准一起做)→ ⚠️ 序⑥ 只做了测量与登记,仍未加 MemoryMax(同上理由,且实测后 --max-hosts 已非绑定约束)
5 🔴 无"失败切流"(序⑥ E9 后半判红的根因):客户端把 relay url 在首次解析后钉死 —— main.js --client 无重解析;worker 走 DSHS_RENDEZVOUS_URL 同样不吃引导链;只有 Manager 的拨号通道有周期性重解析(web/server.ts#refreshOverlay),而它的换址条件是"目录里的地址变了",与"当前 relay 挂了"无关 修它 = 新功能(需"连接失败后重解析 + 排除已失败 relay"),⛔ 属单外发现,只报告不动手(R7) ✅ 已闭环(序⑦ · 2026-09-17):候选集不再退化成单点(listOverlayRelayCandidates);三处客户端(Manager 拨号 / worker 实例面 / relay --client)接同一个 RelayFailoverSupervisor;阈值见本表 §4 RELAY_FAILOVER_*。⛔ 服务端零改动(D8)|序⑧:冷却语义拆分已闭环(2026-09-17)—— 冷却表结构化(键仍按 url,新增 kind)+ replace() 增 origin(health 可一跳豁免 / directory ⛔ 不可)+ 豁免有界(每 url 每冷却周期一次)+ 双开关与判别器(RELAY_FAILOVER_EXEMPT、exemptSwitches、|豁免 日志标记)|真机判据 = 演练幕 4 系列(--scene 4 / 4b / 4c)
6 per-stream 内存开销未测:MEM_PER_HOST_MB = 0.06 MB/台 只是空闲会话斜率;带流量时每条流最多 QUEUE_MAX_BYTES(1 MB) 缓冲 需带流量的压测(S6 的已定口径是"每个声明 1–2 个端口的空闲会话") 序⑦ 候选(回头条件:--max-hosts 若重新收紧,必须先有本数)
7 106 的第二中继不提供 /dshs-overlay/bootstrap(目录由 47 的 127.0.0.1:3080 签发;实测 3080 只绑回环、106→47:3080 TCP_BLOCKED) 两条绕法都有代价:走 47:443(同一失败域,等于没增益)/搬证书私钥(命中 R5 扩大) 已登记;功能影响 = 0(客户端 resolveOverlayRelay ③ 对取不到的 origin continue,且 relays[] 首位是主入口)
8 106 的 443 vhost 归宝塔面板管理:本次把 relay location 放进 extension/106.54.21.172/*.conf(面板重写 vhost 主文件不会丢它) ⛔ 若将来面板重建该站点,需复查 nginx -T | grep dshs-relay 运维注意(已写进 relay-b.conf 头部注释)
9 换址墙钟的四段分解(序⑨ 实测 · 2026-09-17,n=5+n=5 真机样本,逐行对齐 journal 时间戳):检测 15.0–16.6 s(= RELAY_GRACEFUL_BURST_MS 地板 + 0–0.7 s 重试相位)+ 首试延迟 0.3–5.0 s(RELAY_FAILOVER_CHECK_MS 相位 + 拨号耗时)+ 白等 12.02 s → 0.09 s(修前 waitUpOn 对"必然失败的同机候选"吃满 upTimeoutMs;修后终态早退)+ 建连 2.7–5.2 s ⇒ 总 30.7–34.1 s → 20.9–23.9 s(RELAY_FAILOVER_DEADLINE_MS 30000 内侧 5/5) 白等段已修(waitUpOnStatus 终态早退,三处装配点共用一份);检测段 ⛔ 不改(D3:15 s 的 burst 窗口是"计划内下线不是故障"的有意设计,改它 ⇒ 每次 relay 重启都切流) ✅ 序⑨ 已闭环(本表 §4 的 RELAY_GRACEFUL_BURST_MS / RELAY_FAILOVER_UP_TIMEOUT_MS;复算工具 = scripts/overlay-failover-drill.cjs --trace)

§10 指纹

  • 本节口径(推荐核对用,可复现):整个 §10 不计入 ⇒ 复核命令 sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum ⇒ 13de5f9b77c486d71e5b83ec909b17b2(序⑲ 执行棒收口值 · 2026-09-17 18:1x;本轮变更 = 新增 §3.6 PRESENCE_* 五键 + §6 新增 OBS-13/14/15 三行与 PRESENCE_STEADY_FRAMES_MAX / PRESENCE_SAMPLE_HITS_MIN / PRESENCE_SAMPLE_GAP_MS / PRESENCE_STALE_P95_MAX_MS 四阈值键;上一版 = 8f08e74b026e6e5b5e1b3db813f031ae(序⑰ 收口)→ 99e9e17b0c1ce0550e4bc7626a5a0494(序⑨ 收口)→ e6b669c257d8e8964273b3b400238351(序⑧ 收口)→ 24cf2efdbcdcbe61267126ed65dba006(序⑦ 收口)→ 9641f3d67fbc2e67cadf6f78e516f24c → 44af9ea5ca15ae21f2604a9cd3a935b8 → 序⑥ 收口 = db1317c2f7aaef7b47785c1f4fc9de03)
  • 全文件 md5:请用 md5sum 参数表_覆盖网络_20260917.md 现取 —— ⛔ 此处故意不内嵌数值:它包含本节自身,写进去即刻失效(自指)。

§11 补记(2026-09-17 11:1x,在 §10 指纹口径之外)

  • ✅ "第 4/5 台真机来源"已拍板(登记给 §9 第 5 行那条线的同批遗留):用户原话「1 本机内存大 可以模拟多台」⇒ 选 本机模拟多台,放弃"用户自备设备"与"新开云主机"。
  • 本机实测:总内存 47.6 GB / 空闲 27.4 GB / 32 核;relay 单实例 ≈ 48 MB ⇒ 可模拟数十台。⚠️ 共用同一出口 IP ⇒ 对"切流逻辑"够用,对"家宽 / 运营商 NAT 差异"无增量。
  • ⛔ 不改本表任何数值:HOLE_PUNCH_RATE_LOCAL(分层实测 2/2)与其样本口径保持原样;本补记只登记"多实例可作第 4/5 台"这一环境决定。
  • 🔒 本补记位于 §10 之后 ⇒ 复核指纹 db1317c2f7aaef7b47785c1f4fc9de03 仍然有效(后续引用无需换值)。
  • 谁能改这张表:任何一次实测替换(待测 换真值 / MEM_PER_HOST_MB 校准)都会改指纹 ⇒ 改完请同步更新本值,并在工作区日志里记一笔"哪个键从什么换成什么"。