BossAgents 高并发架构重构方案(P2 · 并发专项)

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 仍未解决的根因:

  1. DB 同步写——better-sqlite3 是单进程单线程同步引擎,所有写操作阻塞 Node 事件循环;多 worker 虽靠 WAL 并发,但单实例写入仍是串行天花板,且代码里存在大量"旁路直连"重复打开库文件。
  2. LLM 缓存仅进程内——CLUSTER_WORKERS>1 时每个 worker 各持一份缓存,且重启即失;跨进程无统一限流。
  3. 数据损坏风险——日志已出现 database disk image is malformed(多进程 + 异常下 SQLite 单文件损坏),单 SQLite 文件是可靠性单点。
  4. 大文件本地盘——对象存储缺失,大图/文件仍落本地磁盘,单实例磁盘与内存压力。

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:1367require('better-sqlite3')
  • server/routes/feedback-routes.js:35require('better-sqlite3') 兜底
  • server/routes/industry-presets.js:14const BetterSqlite3 = require('better-sqlite3')
  • server/boss-scheduler/workers/cost-optimizer.js:28require('better-sqlite3')
  • server/routes/platform-asset.js:16try { 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.jscallLLM(约 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(单点)

设计:

  1. 缓存后端替换:新增 server/services/llm-cache-redis.js,实现与现有 _llmCache 同签名的 get(key)/set(key,val,ttl)/del,内部用 ioredis(或 redis)客户端。在 callLLM 中:_llmCacheawait llmCacheRedis.get(cacheKey);命中即返回(仍在 _llmAcquire 之前,不占进程内信号量);未命中走原逻辑,成功后 await llmCacheRedis.set(cacheKey, result, TTL)
  2. 跨进程限流:保留进程内 _llmAcquire 作为"单 worker 兜底",新增 Redis 令牌桶 llmRateLimiteracquire()EVAL 脚本原子扣减 llm_tokensLLM_MAX_CONCURRENCY * CLUSTER_WORKERS 为总桶),超时/无令牌则快速失败或排队(复用现有 LLM_QUEUE_CAP 语义)。
  3. 降级: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 脚本

设计:

  1. 收口脚本:新增 server/scripts/enforce-db-access.cjs,扫描全仓 require('better-sqlite3') 与非常规 DB 入口,列出必须改造的文件清单(一次性审计工具,CI 可复用)。
  2. 逐个改造:把 §2.1 列出的直连改为 require('../shared/db')(相对路径按文件位置调整),用 db.execute/query 替代 db.prepare().run()/.all()
  3. 统一层写串行化shared/db 内部对 execute进程级互斥(better-sqlite3 本就单连接串行,加 mutex 是为了与未来 MySQL 异步写对齐 API);保留同步返回 {changes, lastInsertRowid}不自作主张改 async,避免破坏调用方)——通过"库内单连接顺序执行"即可,无需队列。
  4. 多进程写安全:依赖 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

分层路线(不可一刀切):

  1. 引擎抽象shared/db 增加 DB_ENGINE 配置(sqlite | mysql)。MySQL 实现用 mysql2/promise,对外暴露相同 APIquery/execute/findOne/exec/transaction)。
  2. SQL 方言兼容层:抽出 server/db/dialect.js,集中处理差异:
  • INTEGER PRIMARY KEY AUTOINCREMENTINT PRIMARY KEY AUTO_INCREMENT
  • datetime('now')NOW()
  • 类型:TEXTTEXT/VARCHARINTEGERINT/BIGINTREALDOUBLE
  • 布尔/JSON 列映射
  • 分页 LIMIT ? OFFSET ?(通用,基本不变)
  1. 建表收敛:所有 CREATE TABLE 语句收口到 db migration 文件(不再散落各 route/service 的 db.exec),首次启动按引擎执行对应方言。
  2. 数据迁移:新增 server/scripts/migrate-sqlite-to-mysql.cjs——读 SQLite 全表 → 批量 INSERT MySQL(先修 malformed 库:用 .recover 导出再迁)。
  3. 灰度切换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.jsws-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——AUTOINCREMENTAUTO_INCREMENTINTEGER PRIMARY KEY [AUTOINCREMENT]INT PRIMARY KEY AUTO_INCREMENTTEXT PRIMARY KEY/UNIQUEVARCHAR(512) ...(MySQL 不支持 TEXT 主键)、COLLATE NOCASE/WITHOUT ROWID/行内 -- 注释移除、CREATE INDEX IF NOT EXISTSIF NOT EXISTSDEFAULT (datetime('now'))DEFAULT CURRENT_TIMESTAMPdatetime('now')NOW()INSERT OR IGNORE/REPLACELIMIT -1 移除。不依赖任何 DB 驱动,可离线单测。
  • server/db/mysql-engine.js(新建):用 mysql2/promise 封装,对外暴露 init/query/findOne/execute/exec/transaction/isConnected/close,与 shared/db 方法表面一致;pool 可注入(mock)便于离线验证;mysql2DB_ENGINE=mysql 时惰性加载。
  • server/shared/db.js(改):DB_ENGINE 开关(sqlite 默认 / mysql)。默认路径行为 100% 保留原实现(零回归);mysql 路径委托 mysql-engine。新增 transactionengineName() 导出。
  • 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.dbboss_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/getexecpragmatransactionsaveclosecloseReal),SQL 经 dialect.toMySQL 翻译。注入 mock 后端即可离线验证。
  • server/sqlite-compat.js(改):新增 SQLITE_COMPAT_BACKEND 开关(默认 sqlite,零回归)。仅当 =mysql惰性 require bridge/engine(不加载 worker_threads / mysql2);createDatabase_createMysql。连接失败直接抛错,不静默回退 sqlite(避免数据分歧被掩盖)。删除了原仓库里未被任何地方引用的死代码 server/mysql-compat.js(其方言与 dialect.js 重复,且把所有库映射到同一 MySQL 库会导致同名表碰撞)。
  • server/db/dialect.js(改):补充 REALDOUBLETEXT 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.dbboss_core_runtime),避免同名表碰撞。
  • 结论:两份开关(DB_ENGINE + SQLITE_COMPAT_BACKEND)相互独立,可分别灰度。M3 走 shared/db 路径,M3.5 走 sqlite-compat 路径。两者都默认 sqlite,零回归。

8.2 切换步骤(以 core_runtime.db 为例,全量切换走 M3.5)

  1. 备份cp server/data/core_runtime.db server/data/core_runtime.db.bak
  2. (若损坏)修复:日志出现 database disk image is malformed 时先 sqlite3 core_runtime.db ".recover" | sqlite3 core_runtime.db.recovered,再用 .recovered 重跑。
  3. 配 MySQL 环境变量MYSQL_HOST/MYSQL_PORT/MYSQL_USER/MYSQL_PASSWORDMYSQL_POOL_SIZE(建议 8)。
  4. Dry-run 核对node server/scripts/migrate-sqlite-to-mysql.cjs --db server/data/core_runtime.db --dry-run —— 人工核对翻译后 DDL 与"未覆盖语法清单"。
  5. 执行迁移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>)。
  6. 预发灰度:预发实例设 SQLITE_COMPAT_BACKEND=mysql + MYSQL_*,启动验证初始化/查询;做双写或数据对账(行数比对)。首次 createDatabase 会同步建连,连不上会直接抛错(不静默回退),便于快速暴露配置问题。
  7. 切流:确认无误后把该配置提升到生产。
  8. 回滚:把 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. 验证计划(通用)

  1. 单元/机制:独立脚本验证核心机制(沿用 P0/P1 已验证手法——从源码 eval 真实函数 + 最小 HTTP 探针,避免整站启动拉外部依赖)。例如 P2-A 验证 Redis 缓存命中后上游 LLM 调用次数=1;P2-B 验证收口脚本扫描 0 旁路直连。
  2. 语法校验:每个改动文件 node --check(前端 .mjs 按 ESM 校验)。
  3. 全站冒烟:预发环境启动,确认初始化、路由、DB、算法注册表无报错(P1 已用启动日志验证过加载期无错)。
  4. 并发压测:用 autocannon / wrk 对核心路径(LLM 对话、数字员工任务、静态资源)压测,目标 1000+ 并发在线 下 P99 延迟与错误率达标;对比 M1/M2/M3 前后指标。
  5. 数据一致性:M3 迁移后用对账脚本比对 SQLite 与 MySQL 行数/关键字段,确认无丢失。
  6. 回滚演练:每次切换前确认环境变量旋钮可一键回退,并在预发演练回滚路径。

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

← 返回案例列表
分享:
🤖 Try Now →
🤖
🎁