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:34main()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_serverexecutorwebsocket_serverscheduler_server 等服务。
L03

各服务分工

进程/服务职责本教程
rest_serverFastAPI REST API(建图/执行/商店)Day 18
executor执行引擎——真正跑 GraphDay 12-13
websocket_server实时推送执行进度到前端Day 15
scheduler_server定时/周期执行Day 14
database_manager集中数据库访问
notification_server邮件/Discord 通知
redis / rabbitmq缓存/事件 & 任务队列L06
Supabase(kong/auth/db/studio)认证 + Postgres
frontendNext.js 可视化构建器L07
clamav上传文件病毒扫描
连"病毒扫描"(clamav)都有——因为用户能上传文件,得防恶意文件。这份服务清单说明它是个认真的生产平台,考虑了安全、扩展、可观测的方方面面。
L04

为什么拆这么多进程

  • 独立伸缩:执行引擎(executor)是最吃资源的,可以单独多开几个实例扛并发;REST 服务开一两个就够。分开进程才能各自伸缩。
  • 故障隔离:某个执行卡死/崩溃,不影响 REST 服务继续接请求。
  • 职责单一:每个进程干一件事,代码清晰、易维护。
  • 异步解耦:REST 收到"执行请求"后不自己跑,而是丢进队列(RabbitMQ),executor 进程去消费——REST 立刻返回,执行在后台进行。
📝 举个例子:100 个用户同时点"运行"会发生什么 • REST 进程收到 100 个 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 干活/跑图 Redis 事件总线 publish 进度 WebSocket 推给前端 "接单/干活分离" + "广播进度"
上排:前端→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 实时推进度 → 返回结果。
← Day 03 Graph Day 05 · 执行旅程 →