Files
dsh_ai1net_server/交付物/端口与容量瓶颈评估-20260925.md
T
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

7.0 KiB
Raw Blame History

交付物 · 端口/443 澄清 + 容量与瓶颈评估(2026-09-25)

工作区:E:/ProgramData/AIProject/aliyun-dsh-server|基线:D:/github/dsh_shenxian 触发:用户质疑「现在不是可以覆盖网络走 443 端口吗」⇒ 该质疑成立,本件更正我上一轮的表述; 并补答上一问(服务器压力 / 支持多少人 / 瓶颈在哪)。


§1 更正:开端口是可选项,不是可用性的前置条件

🔴 我上一轮把"优化项"说成了"前置条件",这是错的。准确表述:

走什么 要不要开端口 作用
中继通道 wss://ai1net.com/dshs-relay(443)+ relay-direct.ai1net.com(443 兜底) ⛔ 不用 功能全可用:房间 / 消息 / 历史 / 补拉 / 在线态 / 插件全部走它
P2P 直连 UDP 21100–21115(dgram) ✅ 要对端 /32 放行入站 ⛔ 不增加任何功能,只省服务器带宽

实测依据:中继监听 127.0.0.1:20080,由 nginx 前置到 443(server.ts); relay-direct 的注释原文 = 「443/TCP 兜底 · 去 CF」(addr-override.ts) ⇒ 它是第二个中继入口(绕开 Cloudflare 直连服务器 IP),⛔ 不是 P2P。

⇒ 结论:群聊现在就能用,一个人都不用等端口开。 我上一轮说"不做 P2P 等于白做"——那句 指的是带宽优化白做,不是聊天不可用。表述有误导,此处更正。

§2 那为什么 P2P 非用 UDP、443 顶不了?

三条,都是机制性的(⛔ 不是配置问题):

  1. 443 是"连服务器":能连上的只有在那儿监听的服务。中继是星型——所有流量经服务器 转一次。要"两节点直达",得双方能往对方的 IP:端口发包。
  2. 两台的 443 都被 nginx 占了(门户 / 子域 / 中继都在这一个口上)⇒ 没法再让"另一个节点" 去监听 443。
  3. UDP 打洞 vs TCP 打洞:NAT 穿透只有 UDP 可靠(TCP 打洞对 NAT 行为极敏感,业界 成功率远低)。所以打洞代码用的是 node:dgram(punch.ts 明写「只用 Node 内建 dgram」)。

§3 开端口到底值不值(我的修正建议)

面 P2P 收益(实测) 值不值
内容面(插件静态内容 / 大文件跨节点分发) 中继带宽省 75%(4 节点组)– 93.75%(16 节点组)= 1 − 组数/节点数;⚠️ 真机现值 = 0(端口没开) ✅ 值得开(已建好却拿不到收益)
消息面(群聊消息本身) 消息是小包(上限 64 KiB,通常几百字节)⇒ 省下的绝对带宽很小 ❌ 不值得专为它开

🔴 修正后的建议:开端口这件事按"内容分发"来算账,⛔ 不要按"群聊"来算账 —— 群聊消息走 443 中继,够用且简单。若你近期不打算做跨节点内容分发,可以完全不开。

§4 顺带答上一问:服务器压力 / 支持多少人 / 瓶颈在哪

§4-1 实测读数(106 真机,连接数三档)

连接数 自研(native)P99 外部连接层(Centrifugo)P99 自研内存边际 连接层内存边际
1,000 45.3 ms 28.9 ms 7.5 KiB/连接 70.1 KiB/连接
5,000 256.3 ms 68.1 ms 同上 同上
10,000 421.1 ms 111.9 ms 总量 131.8 MiB 总量 750.2 MiB
  • 判据对照:实时档预算 100 ms、聊天档预算 1,000 ms。
  • ⇒ 自研在 ~2–3k 连接就越过实时档预算;10k 时聊天档仍可(421 ms < 1,000 ms)。
  • ⇒ 外部连接层在 10k 仍在实时档内,代价是内存 11.9×(用户已拍板采用,第 12 棒起)。
  • ✅ 心跳超时率 0、upgrade 全成功(1k/5k/10k 三档)⇒ 连接数供给不是问题。

§4-2 一个群能装多少人

  • 声明上限 = 2,000 人/房(已固化;口径 = 档案 109「100–2,000 人纯扇出可行」)。
  • 内核扇出本身极便宜:2,000 连接下 hub 内扇出 P99 = 0.023 ms;预算判定 P99 = 0.030 ms; 写库恒 1 条 INSERT(⛔ 不随成员数放大)⇒ 不是瓶颈。
  • 🔴 真正的成本在"每消息 × N 个连接各写一次":2,000 人的房,一条消息 = 2,000 次 write()。

§4-3 瓶颈排序(从最该处理的开始)

# 瓶颈 性质 现状
1 🔴 扇出写路径无背压控制 本轮新发现的隐患 未修(见 §4-4)
2 单进程全量扇出排队 已实测(P99 ≈ 事件循环滞后) 已用外部连接层缓解;根治靠多进程/多机
3 write() 绝对总量 分片实验证明"切细分时"无效 需并行,参数调不动
4 存在感惊群 已有 1 s 批合并 + 收敛 实测优于朴素实现 17–3,333×
5 插件桥轮询(本轮新增) 随实例数线性 见 §4-5

§4-4 🔴 本轮查出的真实隐患:慢客户端会顶爆内存

src/im/ws.ts 的 send() 实现:

send(text) {
  if (closed || socket.destroyed) return false
  try { socket.write(textFrame(text)); return true }   // 🔴 返回值被丢弃
  catch { return false }
}

问题:socket.write() 返回 false 的含义是"缓冲已满,请等 drain"。这里既没看返回值、 也没 pause/destroy ⇒ 慢客户端(网速差 / 故意不读)会让数据无界堆在内存里。 且 delivered 计数把它记成成功(其实是"已排队",不是"已送达")。

为什么这是第一优先:它是单点即可打垮服务器的路径(一个慢连接 × 高频房间), 比"能撑多少连接"更要紧;且属修 bug(技术项,可自决),⛔ 不需要扩容就能改善。

修法(待开工):write() 返回 false ⇒ 记一次"背压",累计超阈值就 destroy() (具名断开原因);delivered 语义改为"已写入缓冲",另出 backpressure 计数。 ⚠️ 阈值取值要有依据(不能拍脑袋)⇒ 需先量一轮"正常客户端"的缓冲水位。

§4-5 本轮新增的负载(诚实报告 · 我自己引入的)

插件侧 SDK 的 subscribe() 用轮询(默认 1.5 s):

  • 每实例每订阅房 = 1 请求 / 1.5 s;若装 3 插件 × 每插件订阅 5 房 ⇒ 10 req/s/实例。
  • 随实例数线性:100 实例 ⇒ 1,000 req/s;1,000 实例 ⇒ 10,000 req/s(每请求 1 次 DB 查询)。
  • ⚠️ 这个负载与消息量无关(空房也要轮询)⇒ 属"静默增长"型。
  • 缓解:间隔建议 ≥3 s;或后续把"订阅"改成平台推送(需反向通道,未建)。

§5 一句话总结

群聊今天就能用(走 443 中继),端口是可选优化;容量上连接层已能撑 10k 在线 (外部连接层 111.9 ms @10k);最该动的不是扩容,而是补上扇出的背压控制。

§6 边界

⛔ 本件零代码改动(纯评估)、未部署、未 commit/push、未动 47/106、未动云安全组。 ⛔ 未登记下一棒。