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

119 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 交付物 · 端口/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()` 实现:
```js
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、未动云安全组。
⛔ **未登记下一棒**。