docs(backend): 同步 worker 数注释(生产 4 -> 2,与 2 核匹配)

仅注释变更,**无行为变更**。EGG_WORKERS 实际由 deploy/.env.production 注入 +
deploy/start-backend.sh 兜底决定,本次把注释里写死的旧口径同步为 2,避免
「注释与配置漂移」再次误导后人。

- app.js:
  · 「生产显式设为 4」-> 2
  · 「4×20=80(安全)」->「2×20=40(安全)」
  · 「本地 dev 也会加载 .env.production,其中写着 4」-> 2
  · 「dev 下真 worker=1 却报 4×20=80」-> 2×20=40
- config/config.default.js:
  · 「生产值写在 deploy/.env.production(EGG_WORKERS=4)」-> 2(并注明 2026-09-29
    R40 由 4 校正为 2)
  · 「4 × 20 = 80」->「2 × 20 = 40」
  · pool 段「worker 数由 EGG_WORKERS 显式控制(生产 = 4)」-> 2
  · 「本机/多数服务器为 8 核 → 8 个 worker」->「核数越多起得越多(开发机 8 核 → 8 个)」
    (原文把"8 核"当成多数服务器的事实,与实测的 2 核服务器不符)

背景(R40 · BACKLOG C2):worker 数 ≈ CPU 核数。worker 数 > 核数对 CPU 型路径
(bcrypt 登录)**零收益** —— 加 worker 不加核(native bcrypt 走 libuv threadpool),
只增加 context switch 与内存(单 worker 空闲 RSS ≈108MB)。登录容量由物理核数
决定(实测 ≈12.6 次/核·秒)⇒ 2 与 4 的登录容量相同。连接池 2×20=40 ≤ 151-20 ✅

验证:node --check app.js / config/config.default.js 通过;
verify-c1c2.py 含「五处文档/代码注释已无旧口径「4」残留」断言(24/24 通过)。
This commit is contained in:
2026-09-29 12:34:38 +08:00
parent 0531131f0e
commit 070195a22c
2 changed files with 10 additions and 9 deletions

8
app.js
View File

@@ -67,8 +67,8 @@ class AppBootHook {
// //
// 🔴 2026-09-21 修正(并发压测结论):原实现用 `os.cpus().length` 估 worker 数, // 🔴 2026-09-21 修正(并发压测结论):原实现用 `os.cpus().length` 估 worker 数,
// 这是**误估** —— 真实 worker 数由 egg-scripts 的 --workers / EGG_WORKERS 决定 // 这是**误估** —— 真实 worker 数由 egg-scripts 的 --workers / EGG_WORKERS 决定
// (生产显式设为 4,见 config.default.js「集群 worker 数」段),与 CPU 核数无必然关系。 // (生产显式设为 2,见 config.default.js「集群 worker 数」段),与 CPU 核数无必然关系。
// 误估会在 8 核机上把「4×20=80(安全)」误报成「8×20=160(越限)」, // 误估会在 8 核机上把「2×20=40(安全)」误报成「8×20=160(越限)」,
// 既制造假告警、也会掩盖真实的越限,使这条断言失去作用。 // 既制造假告警、也会掩盖真实的越限,使这条断言失去作用。
// 改为优先读 EGG_WORKERS(worker 进程继承 master 的环境变量),回退才用 CPU 数, // 改为优先读 EGG_WORKERS(worker 进程继承 master 的环境变量),回退才用 CPU 数,
// 并在日志中显式标注估算依据,便于排障时判断该行可信度。 // 并在日志中显式标注估算依据,便于排障时判断该行可信度。
@@ -81,10 +81,10 @@ class AppBootHook {
const usingExplicit = Number.isFinite(explicitWorkers) && explicitWorkers > 0; const usingExplicit = Number.isFinite(explicitWorkers) && explicitWorkers > 0;
// 🔴 2026-09-21 二次修正:egg-bin dev / test 会**显式传** `workers: 1` // 🔴 2026-09-21 二次修正:egg-bin dev / test 会**显式传** `workers: 1`
// (node_modules/egg-bin/scripts/start-cluster.js),此时即便环境里存在 // (node_modules/egg-bin/scripts/start-cluster.js),此时即便环境里存在
// EGG_WORKERS(本地 dev 也会加载 deploy/.env.production,其中写着 4), // EGG_WORKERS(本地 dev 也会加载 deploy/.env.production,其中写着 2),
// 也不是真实 worker 数 —— 真实值是 1。故必须先按 app.config.env 判定 // 也不是真实 worker 数 —— 真实值是 1。故必须先按 app.config.env 判定
// 单 worker 的运行形态,否则本地仍会打出一条假数字日志(实测:dev 下 // 单 worker 的运行形态,否则本地仍会打出一条假数字日志(实测:dev 下
// 真 worker=1 却报 4×20=80)。 // 真 worker=1 却报 2×20=40)。
const envName = (this.app.config && this.app.config.env) || ''; const envName = (this.app.config && this.app.config.env) || '';
const singleWorkerEnv = envName === 'local' || envName === 'unittest'; const singleWorkerEnv = envName === 'local' || envName === 'unittest';
let workers; let workers;

View File

@@ -186,15 +186,16 @@ module.exports = (appInfo) => {
// 🔴 集群 worker 数(2026-09-21 并发压测结论,必须显式设定) // 🔴 集群 worker 数(2026-09-21 并发压测结论,必须显式设定)
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// 背景:`egg-scripts start` 未传 --workers 时,egg-cluster 会按 os.cpus().length // 背景:`egg-scripts start` 未传 --workers 时,egg-cluster 会按 os.cpus().length
// 起同等数量的 app worker —— 本机/多数服务器为 8 核 → 8 个 worker。 // 起同等数量的 app worker —— 核数越多起得越多(开发机 8 核 → 8 个 worker)。
// 而每个 worker 的连接池上限是 DB_POOL_MAX(默认 20),8 × 20 = 160 // 而每个 worker 的连接池上限是 DB_POOL_MAX(默认 20),8 × 20 = 160
// **已经超过** MySQL max_connections(151)。高峰期会出现 connection acquire // **已经超过** MySQL max_connections(151)。高峰期会出现 connection acquire
// timeout,在考生侧表现为「交卷失败」,且服务端不报业务错,极难定位。 // timeout,在考生侧表现为「交卷失败」,且服务端不报业务错,极难定位。
// //
// 落点:worker 数由环境变量 EGG_WORKERS 控制(egg-scripts start 把它作为 // 落点:worker 数由环境变量 EGG_WORKERS 控制(egg-scripts start 把它作为
// --workers 的默认值,见 node_modules/egg-scripts/lib/cmd/start.js:31)。 // --workers 的默认值,见 node_modules/egg-scripts/lib/cmd/start.js:31)。
// 生产值写在 deploy/.env.production(EGG_WORKERS=4),并由 start-backend.sh // 生产值写在 deploy/.env.production(EGG_WORKERS=2,与服务器 2 核匹配;
// 显式传参作双保险。4 × 20 = 80 ≤ 151,为管理后台/统计等其余连接留足余量。 // 2026-09-29 R40 由 4 校正为 2),并由 start-backend.sh 显式传参作双保险。
// 2 × 20 = 40 ≤ 151,为管理后台/统计等其余连接留足余量。
// //
// ⚠️ 不要试图在此处写 config.cluster.workers —— egg-cluster 只读 // ⚠️ 不要试图在此处写 config.cluster.workers —— egg-cluster 只读
// startCluster(options.workers)(options.js:24 → master.js:267), // startCluster(options.workers)(options.js:24 → master.js:267),
@@ -247,8 +248,8 @@ module.exports = (appInfo) => {
// 🔴 压测调优(2026-08-11):单 worker 默认 10 连接,在「无带宽限制 + 极端并发」 // 🔴 压测调优(2026-08-11):单 worker 默认 10 连接,在「无带宽限制 + 极端并发」
// 下分片合并/写入会短时占用大量连接。提高到 20(可用 DB_POOL_MAX 覆盖), // 下分片合并/写入会短时占用大量连接。提高到 20(可用 DB_POOL_MAX 覆盖),
// 并放宽 acquire 到 45s,避免瞬时峰值触发 "connection acquire timeout"。 // 并放宽 acquire 到 45s,避免瞬时峰值触发 "connection acquire timeout"。
// 注意:这是「每 worker」上限;worker 数由 EGG_WORKERS 显式控制(生产 = 4, // 注意:这是「每 worker」上限;worker 数由 EGG_WORKERS 显式控制(生产 = 2,
// 见本文件上方「集群 worker 数」段),总连接数 = worker 数 × max = 4 × 20 = 80, // 见本文件上方「集群 worker 数」段),总连接数 = worker 数 × max = 2 × 20 = 40,
// 须 ≤ MySQL max_connections(默认 151)。启动期由 app.js didReady 自举断言 // 须 ≤ MySQL max_connections(默认 151)。启动期由 app.js didReady 自举断言
// 按**实际 worker 数**复核(不再用 os.cpus() 误估),超限只 WARN 不阻断。 // 按**实际 worker 数**复核(不再用 os.cpus() 误估),超限只 WARN 不阻断。
max: parseInt(process.env.DB_POOL_MAX, 10) || 20, max: parseInt(process.env.DB_POOL_MAX, 10) || 20,