ASL Tool 3 Development Plan: - Architecture blueprint v1.5 (6 rounds of architecture review, 13 red lines) - M1/M2/M3 sprint checklists (Skeleton Pipeline / HITL Workbench / Dynamic Template Engine) - Code patterns cookbook (9 chapters: Fan-out, Prompt engineering, ACL, SSE dual-track, etc.) - Key patterns: Fan-out with Last Child Wins, Optimistic Locking, teamConcurrency throttling - PKB ACL integration (anti-corruption layer), MinerU Cache-Aside, NOTIFY/LISTEN cross-pod SSE - Data consistency snapshot for long-running extraction tasks Platform capability: - Add distributed Fan-out task pattern development guide (7 patterns + 10 anti-patterns) - Add system-level async architecture risk analysis blueprint - Add PDF table extraction engine design and usage guide (MinerU integration) - Add table extraction source code (TableExtractionManager + MinerU engine) Documentation updates: - Update ASL module status with Tool 3 V2.0 plan readiness - Update system status document (v6.2) with latest milestones - Add V2.0 product requirements, prototypes, and data dictionary specs - Add architecture review documents (4 rounds of review feedback) - Add test PDF files for extraction validation Co-authored-by: Cursor <cursoragent@cursor.com>
5.3 KiB
5.3 KiB
🎯 系统级异步架构风险剖析与演进技术蓝图 (V2.0 定稿版)
文档性质: 架构决策与研发执行规范 面向受众: 架构师、技术负责人、中高级后端研发 背景: 随着 ASL 工具 3 等批量耗时任务的引入,系统正从“单体异步”转向“分布式扇出 (Fan-out)”架构。 核心目标: 解决多实例部署下的计数丢失、状态撕裂与事件孤岛风险,建立工业级的分布式任务处理标准。
💡 一、 问题的本质:架构的分水岭
在分布式系统设计中,异步任务的复杂度随业务颗粒度呈指数级跃迁。我们必须将任务划分为两个完全不同的等级:
| 维度 | Level 1:单体任务 (现有标准) | Level 2:分布式扇出 (演进方向) |
|---|---|---|
| 典型案例 | 工具 C 解析 1 个 Excel | 工具 3 批量提取 100 篇文献 |
| 工作流 | 1 个触发 -> 1 个 Worker -> 结束 | 1 个触发 -> 1 个 Manager -> N 个子 Worker |
| 多实例安全性 | 高。依靠 pg-boss 行锁。 | 低。子任务跨机器,必须处理原子性聚合。 |
| 容错代价 | 小。失败重跑 40 秒。 | 极大。若无扇出,第 99 篇失败会导致前 98 篇全废。 |
| 系统影响 | 局部影响。 | 全系统级风险(API 熔断、数据库连接耗尽)。 |
🔍 二、 核心风险深度剖析 (The Risks)
在多 SAE 实例(Multi-Pod)部署环境下,若不严格执行 V2.0 规范,将面临以下系统性崩溃风险:
1. 统计数据的“幻影覆盖” (Race Condition)
- 现象: 当 100 个子任务在不同 Pod 同时完成时,如果采用 count = count + 1 的读写逻辑,多个进程会读到相同的旧值并覆盖写入。
- 后果: 进度条卡死、统计金额错误、任务永远无法触发“完成”回调。
2. “最后一个人关灯”难题 (The Terminator Problem)
- 现象: 缺乏全局协调逻辑。Manager 派发完任务就结束了,子任务各自为政。
- 后果: 系统不知道“这组任务”什么时候算真正结束,无法自动触发后续的报告生成或通知发送。
3. SSE 实时日志的“物理隔绝” (Event Silos)
- 现象: 用户的浏览器连接在 Pod A,但执行任务的 Worker 运行在 Pod B。Pod B 产生的日志在 Pod A 的内存里完全不存在。
- 后果: 页面显示“处理中”但日志区一片空白,用户因感知不到进度而频繁刷新,造成更大的后端冲击。
🛠️ 三、 全系统演进执行建议 (The Guidelines)
为了消除上述风险,全平台所有异步模块必须强制对齐以下 4 项架构红线:
🚨 规范 1:强制执行数据库级原子操作
禁止在异步代码中使用任何内存层面的数学运算来更新数据库。
- 错误写法: data: { count: task.count + 1 }
- 正确标准 (Prisma): ```typescript await prisma.task.update({ where: { id: taskId }, data: { successCount: { increment: 1 } } });
🚨 规范 2:引入“Last Child Wins”收口机制
在分布式环境下,必须由最后一个完成任务的进程负责“关灯(翻转父任务状态)”。
- 执行逻辑: 每个子任务在执行完【原子递增】后,必须同步读取更新后的结果。
- 判定公式: if (updatedTask.successCount + updatedTask.failedCount === updatedTask.totalCount)
- 后续: 若条件成立,该实例负责将 Task.status 改为 completed 并触发 SSE 完成事件。
🚨 规范 3:从 EventEmitter 转向跨实例消息总线
彻底封杀多实例环境下的单机 EventEmitter 实时推送。
- Postgres-Only 方案: 充分利用 PostgreSQL 的 LISTEN/NOTIFY 机制。
- 工作流: 1. Worker 发送 NOTIFY channel_name, payload。 2. 所有 API 节点在启动时 LISTEN 该频道。 3. 收到通知的 API 节点检查本地内存,若存在对应 taskId 的 SSE 客户端,则执行推送。
🚨 规范 4:极端场景下的“背压限制”与超时阻断
- 全局限流: 针对昂贵的外部 API(如 MinerU),必须使用 pg-boss 的 teamConcurrency 进行数据库级全局限流,严禁使用单机 P-Queue。
- 超时阻断: 所有跨网络请求必须强制设置 timeout(建议 ≤ 90s),防止外部接口假死扣住 pg-boss 队列名额,导致系统死锁。
📅 四、 路线图:如何平滑过渡?
- 实验场 (M1/M2): 以“ASL 工具 3”作为首个 V2.0 规范试点,沉淀出通用的 FanOutHelper 和 ListenNotifyService。
- 基建化: 将上述成功代码抽离,封装入 common/jobs 和 common/streaming 能力层。
- 全量覆盖: 发布《Postgres-Only 异步任务处理指南 v2.0》,要求后续所有涉及“批量处理”的模块(如 IIT Agent、批量报告生成)严格照此执行。
🏁 架构师寄语
工具 3 的出现不是增加了复杂度,而是帮我们掀开了分布式环境下一直被掩盖的风险盖子。 与其在上线后通过熬夜排查“幽灵 Bug”,不如现在多审核一次,在文档阶段就打好地基。 请研发团队认真研读此蓝图,这套规范将让我们的系统从“能跑通”进化到“金身不坏”。