Files
dsh_ai1net_server/交付物/端口与容量瓶颈评估-20260925.md
T

118 lines
7.0 KiB
Markdown
Raw Normal View 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()` 实现:
```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、未动云安全组。
⛔ **未登记下一棒**。