# 交付物 · 端口/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、未动云安全组。 ⛔ **未登记下一棒**。