Day 04 / 共 20 天 · 第 1 周 核心概念
运行平台
概念够了,让它跑起来。AutoGPT Platform 是个生产级 SaaS,由一堆进程和中间件组成。今天用 docker compose 启动它,看这套多进程架构。
📍 你在整门课的位置 · 第 1 周 核心概念
D3 Graph 图→
D4 运行平台→
D5 执行旅程→
D6 Block 基类
💡 今天的类比世界观:多进程平台 = 一家分工明确的"餐厅"
docker compose 一键拉起的一堆进程,就像开一家餐厅:各服务 = 不同岗位(前台点单 / 后厨执行 / 收银计费);RabbitMQ = 后厨的叫号排队系统(订单排好队一个个做、不丢单);Redis = 传菜口的小票夹 / 临时台面(快速存取、各岗位互相通气);拆多进程 = 分工,炒菜的不用管收银,一个忙不拖累另一个。今天都用"餐厅"来想。
👶 小白:为什么不写成一个大程序,非要拆这么多进程、还要 Redis 和 RabbitMQ?
👨🏫 老师:一个人既炒菜又收银又洗碗,客一多就乱套、一个环节卡死整店瘫痪。拆进程就是分工 + 隔离故障:执行进程崩了不影响 API 还能接单;高峰期哪个岗位忙就单独加人(多开该进程)。RabbitMQ 保证订单不丢、按序处理;Redis 让各岗位快速通气。这正是"生产级 SaaS"和"玩具脚本"的差别。
L01
docker compose 启动
# autogpt_platform/README.md:14
cd AutoGPT/autogpt_platform
cp .env.default .env
docker compose up -d
# 浏览器打开 http://localhost:3000 ← 可视化构建器
# 或分开跑(开发):
make start-core # 只起 Supabase + Redis + RabbitMQ
make run-backend # 后端
make run-frontend # 前端
读法:一条
docker compose up 拉起整个平台——后端服务群 + 前端 + 数据库 + 队列 + 认证全家桶。打开 3000 端口就是可视化画布,能拖 Block 搭 Agent。为什么用 docker compose?
因为平台依赖一堆外部服务:Postgres(存图/执行记录)、Redis(缓存/事件)、RabbitMQ(任务队列)、Supabase(认证)。手动一个个装配置极其麻烦。docker compose 用一个 yml 文件把所有服务的镜像、配置、网络、依赖关系声明好,一键拉起。对开发者,这是"生产级多服务应用"的标准启动方式。
L02
多进程编排
🤔 痛点:为什么不把后端写成"一个程序"就好,非要拆成一堆进程?
一个进程最省事。但想象:1000 个用户同时点"运行 Agent",执行引擎被塞爆、CPU 打满——如果 REST 接口和执行引擎挤在同一个进程里,那连"查看我的图"这种轻请求都会被卡死,整站瘫痪。怎么让"重活"不拖垮"轻活"?
💡 本质:按职责拆进程 + 用队列/事件解耦,各自独立伸缩与隔离
把后端切成 REST、执行引擎、WebSocket、调度器等独立进程,它们之间不直接调用,而是通过 RabbitMQ(派活)和 Redis(广播进度)通信。谁忙就单独给谁多开实例,谁崩了也不连累别人——这就是生产级 SaaS 的架构底座。
后端不是单个进程,而是一群。app.py:34 的 main() 用 run_processes() 一次拉起所有子进程(app.py:49):
# app.py:49 —— 一次启动所有后端进程
run_processes(
DatabaseManager(), # 数据库访问
Scheduler(), # 定时任务(Day 14)
NotificationManager(), # 通知
WebsocketServer(), # 实时推送(Day 15)
AgentServer(), # REST API(Day 18)
ExecutionManager(), # 执行引擎(Day 12)★核心
# ... CoPilot 相关进程
)
读法:后端被拆成多个独立进程,各管一摊:REST 服务、执行引擎、WebSocket、定时器、通知……它们通过 Redis/RabbitMQ 通信(L06)。docker-compose 里对应
rest_server、executor、websocket_server、scheduler_server 等服务。L03
各服务分工
| 进程/服务 | 职责 | 本教程 |
|---|---|---|
rest_server | FastAPI REST API(建图/执行/商店) | Day 18 |
executor | 执行引擎——真正跑 Graph | Day 12-13 |
websocket_server | 实时推送执行进度到前端 | Day 15 |
scheduler_server | 定时/周期执行 | Day 14 |
database_manager | 集中数据库访问 | — |
notification_server | 邮件/Discord 通知 | — |
redis / rabbitmq | 缓存/事件 & 任务队列 | L06 |
Supabase(kong/auth/db/studio) | 认证 + Postgres | — |
frontend | Next.js 可视化构建器 | L07 |
clamav | 上传文件病毒扫描 | — |
连"病毒扫描"(clamav)都有——因为用户能上传文件,得防恶意文件。这份服务清单说明它是个认真的生产平台,考虑了安全、扩展、可观测的方方面面。
L04
为什么拆这么多进程
- 独立伸缩:执行引擎(executor)是最吃资源的,可以单独多开几个实例扛并发;REST 服务开一两个就够。分开进程才能各自伸缩。
- 故障隔离:某个执行卡死/崩溃,不影响 REST 服务继续接请求。
- 职责单一:每个进程干一件事,代码清晰、易维护。
- 异步解耦:REST 收到"执行请求"后不自己跑,而是丢进队列(RabbitMQ),executor 进程去消费——REST 立刻返回,执行在后台进行。
📝 举个例子:100 个用户同时点"运行"会发生什么
• REST 进程收到 100 个
• RabbitMQ 里排了 100 个任务;假设开了 4 个 executor 实例,每个一次处理 5 个 → 并发跑 20 个,其余在队列排队;
• 某个 executor 因为 OOM 崩了 → 它没 ack 的任务自动重回队列被别的 executor 接走,REST 和其他用户毫无感知。
这就是"接单/干活分离 + 队列削峰"带来的高并发与容错。
POST .../execute → 每个都只做"建记录+扔进 RabbitMQ",几毫秒返回执行 id,100 个请求瞬间接完;• RabbitMQ 里排了 100 个任务;假设开了 4 个 executor 实例,每个一次处理 5 个 → 并发跑 20 个,其余在队列排队;
• 某个 executor 因为 OOM 崩了 → 它没 ack 的任务自动重回队列被别的 executor 接走,REST 和其他用户毫无感知。
这就是"接单/干活分离 + 队列削峰"带来的高并发与容错。
"接单"和"干活"分开
想象一个餐厅:服务员(REST)接单后不亲自做菜,把订单贴到出单口(RabbitMQ 队列),厨师(executor)取单做菜。这样服务员能快速接很多单(不被做菜阻塞),厨师忙不过来就多雇几个。"请求"和"执行"通过队列解耦——这是高并发 SaaS 的标准架构。你在前端点"运行",REST 秒回"已排队",executor 在后台慢慢跑,进度通过 WebSocket 实时推给你。
L05
中间件三件套
REST app(api/rest_api.py:208)装了几个中间件:
- SecurityHeadersMiddleware:加安全响应头。
- GZip 压缩:响应 >50KB 才压——专为
/api/blocks(列出 300+ 块,响应很大)优化。 - CORS:跨域控制(前端和后端不同端口)。
统一异常映射(
rest_api.py:309)值得一提:把各种内部异常映射成合适的 HTTP 状态码——NotFound→404、NotAuthorized→403、UserPaywalledError→402(付费墙)、PreconditionFailed→428。402 这个状态码很少见,正是"你余额不够,请充值"的信号——呼应它是计量收费平台(Day 16)。L06
Redis / RabbitMQ 的角色
- RabbitMQ(任务队列):REST 把"图执行请求"发到队列,executor 进程消费。保证任务不丢(executor 挂了消息重回队列)、不超载(一个 executor 只取它能跑的量)。
- Redis(缓存 + 事件总线):① 缓存(如 Block 清单);② 事件总线——executor 每次节点状态变化就 publish 事件到 Redis,WebSocket 进程订阅后转发给前端(Day 15);③ 分布式锁(保证同一执行不被两个进程重复跑)。
上排:前端→REST→RabbitMQ→executor,REST 接单即返回,executor 从队列取活跑图(一对一派活)。下方:executor 把进度 publish 到 Redis,WebSocket 广播给所有在看的前端(一对多广播)。
队列 vs 事件总线,别混
RabbitMQ 队列:"任务分发"——一个任务只被一个 worker 消费(谁抢到谁干)。Redis 事件总线(Pub/Sub):"消息广播"——一个事件被所有订阅者收到(进度推给所有看这个执行的前端)。队列用于"派活"(一对一),事件总线用于"广播进度"(一对多)——两种不同的通信模式,各司其职。这和 OpenHands、CrewAI 的事件系统思路一致。
L07
前端与 API 生成
前端是 Next.js + TypeScript 的可视化构建器(Day 03 讲过画布对应 Graph 模型)。有个工程细节:前端的 API 客户端是自动生成的——后端出 OpenAPI 规范,前端用 Orval 工具生成 TS 客户端(pnpm generate:api)。
为什么自动生成 API 客户端?
后端有几十个 API 端点。如果前端手写每个请求的调用代码(URL、参数、返回类型),既费力又容易和后端不一致(后端改了个字段,前端忘了同步就出 bug)。从后端的 OpenAPI 规范自动生成前端客户端——后端一改,重新生成,前端类型自动同步。"单一真相来源 + 代码生成"消除前后端不一致——大型全栈项目的标配。
L08
今日小结 + 动手
🧠 今天你应该能回答
- 怎么启动平台?为什么用 docker compose?
- 后端为什么拆成多进程?各进程职责?
- "接单"和"干活"为什么要分开(队列解耦)?
- RabbitMQ 和 Redis 各扮演什么角色?
- 402 状态码意味着什么?前端 API 客户端为什么自动生成?
✋ 动手
P=autogpt_platform
sed -n '14,70p' $P/README.md # 启动方式
grep -n 'run_processes\|ExecutionManager\|WebsocketServer' $P/backend/backend/app.py
grep -nE 'rest_server|executor|rabbitmq|redis|clamav' $P/docker-compose.yml | head
明天预告 · Day 05(第1周收官):把前四天串成故事线——一次执行的完整旅程:从前端点"运行"→ REST 扣费/入队 → executor 拓扑执行各 Block → 数据流动 → WebSocket 实时推进度 → 返回结果。