- 变更规模:新增 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/ 知识文件,按口径入库)
119 lines
7.0 KiB
Markdown
119 lines
7.0 KiB
Markdown
# 交付物 · 端口/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、未动云安全组。
|
||
⛔ **未登记下一棒**。
|