BossAgents 高并发架构重构方案(P2 · 并发专项)
版本:V1.0
日期:2026-08-17
作者:并发优化专项
关联:docs/design-p2.md(业务功能型 P2,与本方案并行不冲突)
目标:在 P0(止血)+ P1(瓶颈解耦)+ 多进程 cluster 基础上,把"单进程同步写 + 内存缓存 + 本地文件"升级为可水平扩展的架构,稳定支撑 1000+ 并发在线,并消除单点数据损坏风险。
0. 已交付基础(P0 / P1 / cluster,均已上线)
| 阶段 | 提交 | 解决的能力 |
|---|---|---|
| P0 止血 | 83065cff | callLLM 信号量(30并发/300队列)、请求体上限50MB、WS连接5000/帧1MB、SSE上限8000+确认TTL回收、前端重试去重、WS优先+轮询降级 |
| P1 瓶颈解耦 | 0692a9aa | 服务端 Gzip、前端 TTL 缓存、LLM 产物缓存(进程内)、DB busy_timeout 5s→15s、serializeSCSAI 信号量(默认=1)、大图流式 |
| P1-1 加固 | aed65886 | Gzip 健壮性(end(cb) 归一化 + headersSent 防护) |
| P2 前哨 | b435cfc2 | 静态分级 Cache-Control、ETag+304、keep-alive 60s、前端 If-None-Match |
| P2 多进程 | 707a7e34 | cluster 多核(CLUSTER_WORKERS=N,默认 1 零回归;后台服务收敛到 master,worker 跑无状态 HTTP) |
P0/P1/cluster 仍未解决的根因:
- DB 同步写——better-sqlite3 是单进程单线程同步引擎,所有写操作阻塞 Node 事件循环;多 worker 虽靠 WAL 并发,但单实例写入仍是串行天花板,且代码里存在大量"旁路直连"重复打开库文件。
- LLM 缓存仅进程内——
CLUSTER_WORKERS>1时每个 worker 各持一份缓存,且重启即失;跨进程无统一限流。 - 数据损坏风险——日志已出现
database disk image is malformed(多进程 + 异常下 SQLite 单文件损坏),单 SQLite 文件是可靠性单点。 - 大文件本地盘——对象存储缺失,大图/文件仍落本地磁盘,单实例磁盘与内存压力。
1. 基础设施约束(已与用户确认)
| 组件 | 现状 | 对方案的影响 |
|---|---|---|
| Redis | 可用 | 可立即用于 LLM 缓存/限流、WS 消息总线、会话共享 |
| MySQL | 可用 | 可承载 DB 迁移(替代 SQLite 同步写) |
| PostgreSQL | 不可靠/不可用 | 不采用,DB 迁移目标定为 MySQL |
| 对象存储 | 暂无 | 大文件解耦列为 Phase 3,现阶段用本地盘 + 流式顶着(P1-6 已做) |
结论: P2 全部可落地项均基于 Redis + MySQL,无需新增未知基础设施。
2. 现状调研关键发现(写方案前的代码审计)
2.1 数据库访问层(最复杂、迁移阻力最大)
- 统一层存在但未被全面采用:
server/sqlite-compat.js:better-sqlite3 真磁盘单例引擎,WAL +busy_timeout=15000(P1 改),多进程安全靠 WAL。server/shared/db.js:async 友好的统一封装(init/query/execute/findOne/exec),execute返回{changes, lastInsertRowid}。- 大量"旁路直连"绕过统一层(迁移必须收口):
server/digital-staff/index.js:1367→require('better-sqlite3')server/routes/feedback-routes.js:35→require('better-sqlite3')兜底server/routes/industry-presets.js:14→const BetterSqlite3 = require('better-sqlite3')server/boss-scheduler/workers/cost-optimizer.js:28→require('better-sqlite3')server/routes/platform-asset.js:16→try { require('better-sqlite3') }- 另有多处通过
db-adapter.js/global._pvDb/database模块间接拿连接(见shared/db.js的 3 条降级路径)。 - 仅 8 处
boss-scheduler/workers/*规范引用shared/db。 - 可靠性告警: 运行日志
server/data/kb/server_run.log已出现database disk image is malformed(boss_analytics.db / boss_tenants.db),证明单 SQLite 文件在多进程/异常下确有损坏,迁移 MySQL 诉求真实。
2.2 LLM 调用层(改造阻力最小)
- 所有 LLM 调用汇聚到
server/services/llm.js的callLLM(约 20 处调用方:core/、services/、routes/* 均只调用不实现)。 - 已有进程内缓存
_llmCache(TTL 5min / 容量 1000,命中先于信号量获取)——P1 加的。 - 结论: Redis 缓存 / 跨进程限流 只需改
llm.js一处。
2.3 文件 / 对象存储
serveStatic已对 >1MB 文件走createReadStream().pipe(res)流式(P1-6),内存可控。- 上传入口分散,无统一对象存储抽象——需先建"存储适配层"才能接对象存储。
2.4 WS 网关
server/services/ws-bridge.js随 cluster 主进程启动(P2 cluster 把后台服务收敛到 master,_runBackground),多 worker 通过共享端口的upgrade复用,当前已够用。
3. 五项改造设计
P2-A LLM 网关 + Redis 缓存/限流 ★ 优先级最高 · 低风险
目标: 把进程内 LLM 缓存升级为跨进程共享缓存;加跨进程全局并发限流(令牌桶),防止 CLUSTER_WORKERS>1 后多 worker 叠加打爆上游 LLM。
改动文件: server/services/llm.js(单点)
设计:
- 缓存后端替换:新增
server/services/llm-cache-redis.js,实现与现有_llmCache同签名的get(key)/set(key,val,ttl)/del,内部用ioredis(或redis)客户端。在callLLM中:_llmCache→await llmCacheRedis.get(cacheKey);命中即返回(仍在_llmAcquire之前,不占进程内信号量);未命中走原逻辑,成功后await llmCacheRedis.set(cacheKey, result, TTL)。 - 跨进程限流:保留进程内
_llmAcquire作为"单 worker 兜底",新增 Redis 令牌桶llmRateLimiter:acquire()用EVAL脚本原子扣减llm_tokens(LLM_MAX_CONCURRENCY * CLUSTER_WORKERS为总桶),超时/无令牌则快速失败或排队(复用现有LLM_QUEUE_CAP语义)。 - 降级:Redis 不可用时自动回退进程内缓存 + 进程内信号量(不影响可用性,仅失去跨进程共享)。
风险: 低。Redis 故障有降级;缓存 key 已含 model+prompt+params 深度序列化,不会串结果。
回滚: 环境变量 LLM_CACHE_BACKEND=memory(默认)即回退到现有进程内实现,零改动。
验证: 起 2 个 worker,对相同 prompt 并发请求,观察 Redis 命中率 + 上游 LLM 调用次数 = 1;压测下 llm_tokens 不超总桶。
P2-B DB 直连收口 + 写串行化 ★★ 中风险(MySQL 迁移前置)
目标: 消除所有 better-sqlite3 旁路直连,统一走 shared/db,避免多实例 + 多进程重复打开库文件(malformed 根因之一);在统一层内加写串行化与队列上限。
改动文件: server/shared/db.js、各旁路直连文件(见 §2.1 清单)、配套 lint 脚本
设计:
- 收口脚本:新增
server/scripts/enforce-db-access.cjs,扫描全仓require('better-sqlite3')与非常规 DB 入口,列出必须改造的文件清单(一次性审计工具,CI 可复用)。 - 逐个改造:把 §2.1 列出的直连改为
require('../shared/db')(相对路径按文件位置调整),用db.execute/query替代db.prepare().run()/.all()。 - 统一层写串行化:
shared/db内部对execute加进程级互斥(better-sqlite3 本就单连接串行,加 mutex 是为了与未来 MySQL 异步写对齐 API);保留同步返回{changes, lastInsertRowid}(不自作主张改 async,避免破坏调用方)——通过"库内单连接顺序执行"即可,无需队列。 - 多进程写安全:依赖 WAL(已开)+ 适当
busy_timeout(已 15s)。若仍出现锁等待,P2-C MySQL 阶段彻底解决。
风险: 中。直连文件多、语义不一(有的用 stmt.run、有的用 db.exec、有的用事务),改造需逐文件回归测试。不改变 shared/db 的同步 API 形态,因此调用方无需改调用方式,风险可控。
回滚: 逐文件改动,每个文件独立提交;出问题 git revert 单个文件即可。
验证: 跑 enforce-db-access.cjs 确认 0 处旁路直连;全站冒烟(启动 + 核心写路径:Part/BOM/任务创建)无回归。
P2-C MySQL 迁移 ★★★ 高风险 · 分层路线
目标: 用 MySQL 替换 better-sqlite3 同步写,彻底消除"单进程同步写阻塞事件循环"与"SQLite 单文件损坏"两大根因。
前置条件: 必须先完成 P2-B(所有调用方收口到 shared/db),否则无法统一切换引擎。
改动文件: server/shared/db.js(引擎抽象)、server/sqlite-compat.js(保留为 fallback)、建表 SQL 方言层、数据迁移脚本、配置 config.yaml
分层路线(不可一刀切):
- 引擎抽象:
shared/db增加DB_ENGINE配置(sqlite|mysql)。MySQL 实现用mysql2/promise,对外暴露相同 API(query/execute/findOne/exec/transaction)。 - SQL 方言兼容层:抽出
server/db/dialect.js,集中处理差异:
INTEGER PRIMARY KEY AUTOINCREMENT→INT PRIMARY KEY AUTO_INCREMENTdatetime('now')→NOW()- 类型:
TEXT→TEXT/VARCHAR、INTEGER→INT/BIGINT、REAL→DOUBLE - 布尔/JSON 列映射
- 分页
LIMIT ? OFFSET ?(通用,基本不变)
- 建表收敛:所有
CREATE TABLE语句收口到db migration文件(不再散落各 route/service 的db.exec),首次启动按引擎执行对应方言。 - 数据迁移:新增
server/scripts/migrate-sqlite-to-mysql.cjs——读 SQLite 全表 → 批量 INSERT MySQL(先修malformed库:用.recover导出再迁)。 - 灰度切换:
DB_ENGINE=mysql环境变量切换;上线前在预发双写验证(SQLite 仍为主、MySQL 异步镜像),确认一致后切读;回滚DB_ENGINE=sqlite即可。
风险: 高。SQL 方言差异、事务语义、JSON 列、多租户 enterprise_id 隔离、已有 malformed 库需先修复、大数据量表迁移耗时。需预发完整回归 + 数据校验。
回滚: 环境变量一键切回 sqlite;迁移脚本幂等可重跑。
验证: 预发环境 DB_ENGINE=mysql 跑全量 e2e + 并发压测(1000 在线);数据一致性对账脚本比对 SQLite/MySQL 行数。
P2-D 对象存储解耦 Phase 3 · 待基础设施
目标: 大图/大文件从本地磁盘迁移到对象存储,降低单实例磁盘/内存压力,支撑水平扩展。
前置条件: 生产需接入对象存储(OSS/COS/MinIO)。当前无,列为 Phase 3 待定。
设计预览: 新增 server/services/storage.js 抽象(put/get/delete,后端 local | s3),上传路由与 serveStatic 改为走抽象层;local 后端即现有本地盘 + 流式,s3 后端接对象存储 + 签名 URL。
当前顶替方案(已具备): serveStatic 流式(P1-6)+ 静态资源长缓存/ETag(P2 前哨),单机下足够。
P2-E WS 网关独立进程 可选增强
目标: 把 WS 长连接从主进程解耦为独立进程,进一步隔离"长连接内存/连接数"与"HTTP 请求处理"。
依赖: Redis(pub/sub 作为 HTTP worker → WS 进程的推送总线)。
设计预览:
- 独立进程
server/ws-gateway.js跑ws-bridge,监听独立端口(如 3007)。 - HTTP worker 需推送时:
redis.publish('ws:push', JSON.stringify({userId, msg}));WS 进程订阅并broadcastToUser。 - 小程序/网页端连
wss://...:3007。 - 当前
ws-bridge随 cluster 主进程已可用,此项为可选增强,仅在连接数/内存成为瓶颈时推进。
4. 优先级与里程碑
| 里程碑 | 项 | 优先级 | 风险 | 依赖 | 收益 | 状态 |
|---|---|---|---|---|---|---|
| M1 | P2-A LLM Redis 缓存/限流 | ★ 最高 | 低 | Redis(已有) | 跨进程缓存共享 + 全局限流,立即降上游压力 | ✅ 已落地 8d7ac594 |
| M2 | P2-B DB 直连收口 | ★★ | 中 | 无 | 消除 malformed 根因,为 MySQL 铺路 | ✅ 已落地 d2676410(审计 0 旁路) |
| M3 | P2-C MySQL 引擎抽象骨架 | ★★★ | 高 | M2 完成 + MySQL(已有) | 彻底解耦同步写 + 数据可靠性 | 🟢 骨架+迁移工具已落地(待预发灰度) |
| M3.5 | P2-C 全量切换 — sqlite-compat 加 MySQL 后端 | ★★★ | 高 | M3 完成 + MySQL(已有) | 让 80+ 同步调用方零改动切到 MySQL | 🟢 骨架已落地(待预发灰度) |
| M4 | P2-D 对象存储 | Phase 3 | 中 | 需接入对象存储 | 大文件水平扩展 | ⏳ 待基础设施 |
| M5 | P2-E WS 独立进程 | 可选 | 中 | Redis(已有) | 长连接/请求处理隔离 | ⏳ 按需 |
推荐推进顺序:M1 → M2 → M3(M4/M5 按需)。
4.1 M3 引擎抽象骨架(已落地,未上生产)
用户确认:"先做 M3 引擎抽象骨架(推荐)——server/shared/db 增加 mysql 引擎后端(默认仍 sqlite 零回归),可本地验证引擎抽象正确性,暂不上生产;生产切换需预发灰度。风险可控、不破坏现有行为。"
新增/改动文件:
server/db/dialect.js(新建):纯函数toMySQL/stripPragma/detectUncovered,同时覆盖运行时 SQL 与建表 DDL——AUTOINCREMENT→AUTO_INCREMENT、INTEGER PRIMARY KEY [AUTOINCREMENT]→INT PRIMARY KEY AUTO_INCREMENT、TEXT PRIMARY KEY/UNIQUE→VARCHAR(512) ...(MySQL 不支持 TEXT 主键)、COLLATE NOCASE/WITHOUT ROWID/行内--注释移除、CREATE INDEX IF NOT EXISTS去IF NOT EXISTS、DEFAULT (datetime('now'))→DEFAULT CURRENT_TIMESTAMP、datetime('now')→NOW()、INSERT OR IGNORE/REPLACE、LIMIT -1移除。不依赖任何 DB 驱动,可离线单测。server/db/mysql-engine.js(新建):用mysql2/promise封装,对外暴露init/query/findOne/execute/exec/transaction/isConnected/close,与shared/db方法表面一致;pool 可注入(mock)便于离线验证;mysql2仅DB_ENGINE=mysql时惰性加载。server/shared/db.js(改):DB_ENGINE开关(sqlite默认 /mysql)。默认路径行为 100% 保留原实现(零回归);mysql 路径委托mysql-engine。新增transaction与engineName()导出。server/scripts/migrate-sqlite-to-mysql.cjs(新建):生产切换迁移工具——把单个 SQLite 文件(库结构+数据)镜像到 MySQL;幂等可重跑(默认IF NOT EXISTS+INSERT IGNORE,--force才 DROP 覆盖);建表 DDL 经 dialect 翻译;--dry-run离线预览翻译后 DDL(不连 MySQL);损坏库给出.recover修复指引;产出"未覆盖语法清单"。server/scripts/verify-m3-engine.cjs(新建):离线验证(39 项断言全过)——node --check+ dialect 运行时/DDL 转换 + mock pool 验证 query/execute/exec/transaction + sqlite 默认零回归 + 迁移脚本 dry-run 集成。
MySQL 连接配置(仅 DB_ENGINE=mysql 时生效):
MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD / MYSQL_DATABASE / MYSQL_POOL_SIZE。
运行验证:
node server/scripts/verify-m3-engine.cjs→ 退出码 0(39 项断言)node server/scripts/migrate-sqlite-to-mysql.cjs --db server/data/core_runtime.db --dry-run→ 135 张表 DDL 翻译后零残留AUTOINCREMENT/COLLATE NOCASE/WITHOUT ROWID/TEXT PRIMARY KEY(实测 102 处VARCHAR(512) PRIMARY KEY、32 处AUTO_INCREMENT、164 处DEFAULT CURRENT_TIMESTAMP)。
4.2 M3.5 全量切换骨架(sqlite-compat → MySQL 后端,已落地)
用户确认:M3.5 —— 给 server/sqlite-compat.js 加 MySQL 后端,让 80+ 以同步方式调用 db.prepare(sql).all() 的代码零改动即可切到 MySQL,实现"全量切换"。
核心难点: sqlite-compat 暴露的是同步 API(better-sqlite3 同步);MySQL(mysql2)是异步的。要让同步调用方零改动,必须有一个"把异步 MySQL 包装成同步调用"的桥。
解法:worker 线程 + Atomics.wait 同步桥。 主线程发请求后 Atomics.wait 阻塞当前线程;worker 线程持有 mysql2 连接池,处理完 Atomics.add 自增共享计数器唤醒主线程。单线程内任一时刻只有一个在途查询,契合同步语义。
新增/改动文件:
server/db/mysql-sync-bridge.js(新建):主线程侧同步桥。connect(config, dbPath)(同步阻塞直到连接就绪,失败抛错 failfast)/query(schema, sql, params)(同步)/end()。sanitizeSchema(dbPath)把 SQLite 文件路径推导为合法 MySQL schema 名(core_runtime.db→boss_core_runtime),每个 SQLite 文件映射到独立 MySQL schema,隔离不同库、避免同名表碰撞。server/db/mysql-sync-bridge-worker.js(新建):worker 线程,持有按 schema 懒加载的mysql2连接池(CREATE DATABASE IF NOT EXISTS自动建库),multipleStatements开启。server/db/mysql-compat-engine.js(新建):把"同步 query 后端"包装成与 better-sqlite3 完全一致的 API 表面(prepare().run/all/get、exec、pragma、transaction、save、close、closeReal),SQL 经dialect.toMySQL翻译。注入 mock 后端即可离线验证。server/sqlite-compat.js(改):新增SQLITE_COMPAT_BACKEND开关(默认sqlite,零回归)。仅当=mysql时惰性requirebridge/engine(不加载 worker_threads / mysql2);createDatabase走_createMysql。连接失败直接抛错,不静默回退 sqlite(避免数据分歧被掩盖)。删除了原仓库里未被任何地方引用的死代码server/mysql-compat.js(其方言与dialect.js重复,且把所有库映射到同一 MySQL 库会导致同名表碰撞)。server/db/dialect.js(改):补充REAL→DOUBLE、TEXT DEFAULT(...)→VARCHAR(512) DEFAULT(...)(MySQL 的 TEXT 不能有默认值),并把VARCHAR(n) DEFAULT CURRENT_TIMESTAMP修正为DATETIME DEFAULT CURRENT_TIMESTAMP(CURRENT_TIMESTAMP 只能用于时间列)。server/scripts/verify-m3-5.cjs(新建):离线验证(30 项断言全过)——node --check×5 + dialect 增强 + engine API 表面(注入 mock)+sanitizeSchema+sqlite-compat默认路径真实读写零回归 + mysql 模式无服务时createDatabase抛错(failfast 不回退)。
全量切换配置(仅 SQLITE_COMPAT_BACKEND=mysql 时生效):
SQLITE_COMPAT_BACKEND=mysql + MYSQL_HOST / MYSQL_PORT / MYSQL_USER / MYSQL_PASSWORD / MYSQL_POOL_SIZE。
运行验证:
node server/scripts/verify-m3-5.cjs→ 退出码 0(30 项断言)node server/scripts/verify-m3-engine.cjs→ 退出码 0(M3 既有 39 项无回归)
8. 生产切换 Runbook(M3 骨架 → 可一键切换)
目标:在预发/生产通过环境变量 DB_ENGINE 把"经由 server/shared/db 的访问路径"从 SQLite 切到 MySQL,且可一键回滚。
8.1 适用范围(重要,先读)
- M3 已抽象的范围:
server/shared/db这一统一层(boss-scheduler 6 个 worker + 测试套件使用),由DB_ENGINE控制。 - M3.5 已抽象的范围(全量切换):
server/sqlite-compat.js(80+ 同步调用方),由SQLITE_COMPAT_BACKEND控制。M3.5 已落地——通过 worker 线程 + Atomics.wait 同步桥,把异步 MySQL 包装成与 better-sqlite3 一致的同步 API,调用方零改动。每个 SQLite 文件映射到独立 MySQL schema(core_runtime.db→boss_core_runtime),避免同名表碰撞。 - 结论:两份开关(
DB_ENGINE+SQLITE_COMPAT_BACKEND)相互独立,可分别灰度。M3 走shared/db路径,M3.5 走sqlite-compat路径。两者都默认sqlite,零回归。
8.2 切换步骤(以 core_runtime.db 为例,全量切换走 M3.5)
- 备份:
cp server/data/core_runtime.db server/data/core_runtime.db.bak - (若损坏)修复:日志出现
database disk image is malformed时先sqlite3 core_runtime.db ".recover" | sqlite3 core_runtime.db.recovered,再用.recovered重跑。 - 配 MySQL 环境变量:
MYSQL_HOST/MYSQL_PORT/MYSQL_USER/MYSQL_PASSWORD,MYSQL_POOL_SIZE(建议 8)。 - Dry-run 核对:
node server/scripts/migrate-sqlite-to-mysql.cjs --db server/data/core_runtime.db --dry-run—— 人工核对翻译后 DDL 与"未覆盖语法清单"。 - 执行迁移:
node server/scripts/migrate-sqlite-to-mysql.cjs --db server/data/core_runtime.db(重跑用--force覆盖)。⚠️ 注意:M3.5 的同步桥按 schema 自动CREATE DATABASE IF NOT EXISTS,迁移时目标库/schema 命名需与之对齐(每个 sqlite 文件一个 schema,名为boss_<文件名去.db>)。 - 预发灰度:预发实例设
SQLITE_COMPAT_BACKEND=mysql+MYSQL_*,启动验证初始化/查询;做双写或数据对账(行数比对)。首次createDatabase会同步建连,连不上会直接抛错(不静默回退),便于快速暴露配置问题。 - 切流:确认无误后把该配置提升到生产。
- 回滚:把
SQLITE_COMPAT_BACKEND改回sqlite(默认即 sqlite),立即退回 SQLite,MySQL 侧数据忽略即可。
8.3 已知限制 / 待人工核对
- 方言层只覆盖高频可逆差异;
ON CONFLICT(UPSERT)、||字符串连接、named 占位符、GLOB、递归 CTE、触发器/视图 等不在自动转换范围(迁移脚本会列出detectUncovered清单)。 TEXT主键统一映射为VARCHAR(512):若实际主键值 > 512 字符需手动调整列长。带默认值的TEXT列会转为VARCHAR(512)(默认保留),长文本默认值可能被截断——灰度时需核对。- 建表 SQL 仍散落各 route/service,
dialect仅翻译运行时与迁移时的 SQL,未做建表收敛(属 P2-C 第 3 步,下阶段)。 - 严格 SQL 模式下,
''写入 INT 列等 SQLite 宽松行为在 MySQL 会报错,迁移时需关注(脚本按列名精确搬运原值)。 - M3.5 同步桥注意事项:同步桥在调用线程上
Atomics.wait阻塞等待 MySQL——高并发下若 MySQL 响应慢,会阻塞 Node 事件循环(与 better-sqlite3 同步写同一类风险,只是换成了远程 MySQL)。因此 M3.5 真正的收益是"消除单文件损坏 + 并发写吞吐提升",但同步阻塞特性仍在,需配合连接池与超时(当前单次查询超时 8s)使用;若需彻底异步化,须将调用方逐步改为 async(更大改造,超出本期)。
5. 风险与回滚总览
- 统一回滚旋钮:所有改造均通过环境变量开关(
LLM_CACHE_BACKEND/DB_ENGINE/CLUSTER_WORKERS),默认走现有实现,零回归。 - 数据安全风险(仅 M3):迁移前必须备份 SQLite + 修复
malformed;迁移脚本幂等可重跑;预发双写校验后再切。 - Redis 依赖(M1/M5):客户端内置降级(Redis 不可用 → 进程内实现),不引入可用性单点。
- 逐文件提交:P2-B 收口逐文件独立提交,单文件问题可
git revert。
6. 验证计划(通用)
- 单元/机制:独立脚本验证核心机制(沿用 P0/P1 已验证手法——从源码
eval真实函数 + 最小 HTTP 探针,避免整站启动拉外部依赖)。例如 P2-A 验证 Redis 缓存命中后上游 LLM 调用次数=1;P2-B 验证收口脚本扫描 0 旁路直连。 - 语法校验:每个改动文件
node --check(前端.mjs按 ESM 校验)。 - 全站冒烟:预发环境启动,确认初始化、路由、DB、算法注册表无报错(P1 已用启动日志验证过加载期无错)。
- 并发压测:用
autocannon/wrk对核心路径(LLM 对话、数字员工任务、静态资源)压测,目标 1000+ 并发在线 下 P99 延迟与错误率达标;对比 M1/M2/M3 前后指标。 - 数据一致性:M3 迁移后用对账脚本比对 SQLite 与 MySQL 行数/关键字段,确认无丢失。
- 回滚演练:每次切换前确认环境变量旋钮可一键回退,并在预发演练回滚路径。
7. 当前可立即推进的项
按风险/收益,建议从 M1(P2-A LLM Redis 缓存/限流) 起步:
- 仅改
server/services/llm.js一处(加 Redis 缓存后端 + 跨进程令牌桶),已有进程内实现作为降级,默认开关回退,零回归。 - Redis 生产已确认可用,无需新增基础设施。
- 完成后即获得:跨 worker 缓存共享(省 LLM 费用)、全局并发限流(防多 worker 叠加打爆上游)。
M2(DB 收口)紧随其后,为 M3(MySQL 迁移)铺路;M3 是收益最大但风险最高的工程,须在预发完整回归。
文档状态:V1.2 含 M1/M2 落地提交 + M3 引擎抽象骨架 + M3.5 全量切换骨架(sqlite-compat → MySQL 同步桥)落地标记。
关联提交:P0 83065cff / P1 0692a9aa / P1-1 aed65886 / P2前哨 b435cfc2 / P2 cluster 707a7e34 / M1 8d7ac594 / M2 d2676410 / M3骨架 6cb593e6 / M3生产可切换 cad28d4a / M3.5 见 git log
BossAgents