计划:单台 VPS 承载 10 万用户,并为 CDN 做好准备

文档版本 2026-05-29 · 目标:当前这台 VPS 要能扛住 10 万 MAU 而不倒下,应用则在时机成熟时无缝迁往 CDN。

目标数字

Metric取值Source
MAU100,000目标
DAU30,000占 MAU 的 30%,工具类应用的常态
并发 WS10 000(峰值 1.5 万)白天约为 MAU 的 10%,峰值 15%
每日操作数90,000每位日活用户 3 次开启或呼叫
HTTP API 每秒请求数(平均)3-5主要是 key_create、host_sync 和 crash_report
HTTP API 每秒请求数(峰值)30-50在批量发布并更新号码之后

当前架构

A 阶段——Android 客户端 现在,v6.66

Goal: 减少客户端产生的多余请求,让服务器少干点活。

A1. 对待处理主机密钥的 HTTP 重试采用指数退避

File: HostService.kt:1424-1432。目前 syncPendingHostKeys() 每 60 秒被调用一次,打向 Api.keyCreate ,对每个待处理密钥都不做任何退避。

存放于 Prefs per-localId: attempt_at_idattempt_count_id。退避:30 秒 → 60 秒 → 2 分钟 → 5 分钟 → 15 分钟 → 30 分钟(封顶)。成功后重置。

A2. 对待处理访客捆绑包的 HTTP 重试退避

File: Vault.kt:213。逻辑相同。

A3. 递增 overridesVersion 前先比对

File: GuestConn.kt (5 处以上)。把新的 parseNumbers 与旧的比较,用哈希或直接比对。相同则不递增 AppState.overridesVersion。这样就免去了每次 WebSocket 初始化时的重组浪潮——80 次 SharedPreferences 读取 × 20 张卡片。

B 阶段——服务器防护与 CDN 就绪 NOW

Goal: 即便客户端行为异常也保护服务器不受风暴冲击,并把 APK 地址与域名解耦。

B1. 对 host_hello 与 guest_hello 限流

In server.php case 'host_hello' / 'guest_hello':如果同一个 device_id / user_key 在不到 N 秒前发过 hello,就直接关闭连接、不碰数据库。N = 5 秒。

用于防范逻辑损坏的客户端每秒发上百个 hello。

B2. 按 host_id 限制 pending_host_msgs

In enqueueHostMsg:如果该 host_id 已经超过 200 行,就用以下语句删掉最旧的一条 DELETE FROM pending_host_msgs WHERE host_id=? ORDER BY id ASC LIMIT 1。用于防止恶意活动导致队列无限增长。

B3. CDN 就绪:把 APK 地址挪进配置

In _config.php:

$GLOBALS['apk_url'] = 'https://entrixy.com/entrixy.apk';
$GLOBALS['download_page_url'] = 'https://entrixy.com/download';

In api/version_check.php: 'download_url' => $GLOBALS['apk_url'].

迁往 CDN 时,只需改动一行,位于 _config.php to https://apk.entrixy.com/entrixy.apk (Cloudflare R2 配自定义域名)。与应用发布无关。

B4. CDN 上的 APK——完整性校验

客户端在下载时已经校验 APK 签名(调试密钥库,见 build.gradle.kts:23-29)。对 CDN 而言这已足够:即便 CDN 节点掉包,签名也对不上,Android 会拒绝安装。 客户端无需额外改动。

C 阶段——横向扩展 Workerman 当我们触到天花板时

当前的 count=1 Workerman 轻松扛住约 1 万条并发 WebSocket——server.php 第 40 行就用它启动。第一个瓶颈会是消息交换高峰时 PHP 的 CPU 时间:每秒 1 万个 pong 再加上各类操作。

C1. Workerman count=2–4,用 Redis pub/sub 共享状态

Today $hosts, $guests, $calls 是单个进程内的内存数组。要跑多个进程,就得把它们搬到 Redis:

消息落到工作进程 W1 而收件人在 W2 时,通过 Redis pub/sub 转发。

Effect: 按 VPS 核心数线性扩展。四核大约支撑 4 万并发。

C2. 备选方案:单机,在 Workerman 前放 nginx 或 HAProxy,按 client_ip 分片

灵活性差些,但不需要 Redis。每位用户总是落到同一个工作进程。可一旦发送方与接收方分处不同进程,这套就失效了。

D 阶段——何时迁往 CDN LATER

  1. 建 Cloudflare R2 存储桶 → 上传 entrixy.apk.
  2. 自定义域名 apk.entrixy.com → 通过 Cloudflare CDN 接入。
  3. In _config.php change $apk_url 改成新的。
  4. 在部署脚本里,于 assembleRelease add r2 cp app-release.apk apk-bucket/entrixy.apk.

现在不该做什么

眼下正在做什么

  1. A 阶段全部完成 → 发布 v6.66。
  2. B 阶段全部完成,不重启 WebSocket 服务器,以便确认前先行测试。
  3. C 阶段在独立分支上进行,需要 VPS 上有 Redis,本身就是一项单独任务。
  4. D 阶段已在上文说明,需要时再执行。