起因:用户 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 代码面)。
55 KiB
参数表 · 覆盖网络(唯一一张)
线:覆盖网络线 | 序:⑤(参数表 · 观测 · 权限评估)| 产出:执行棒 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/ nginx80·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.6PRESENCE_*五键 + §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校准)都会改指纹 ⇒ 改完请同步更新本值,并在工作区日志里记一笔"哪个键从什么换成什么"。