本节摘要:v5 把系统拆成六个职责单一的进程(官方口径):migration、backend、trading-worker、scheduler-worker、celery-worker、celery-beat。本节先用职责表逐个拆解——每个进程管什么、不管什么、挂了会怎样;再用通信图说明它们如何协同:一切持久事实进 PostgreSQL,一切任务协同走 Redis 队列,进程之间不直接调用;然后给启动顺序与依赖关系;最后纠正四个常见误解(比如「backend 能直接下单吗」「migration 是不是常驻服务」),并用三类故障的定位走查把职责表变成排查工具。读完本节,
docker compose ps的每一行对你而言都应该是自解释的。
| 进程 | 职责 | 不管什么 | 挂了的影响 |
|---|---|---|---|
| migration | 首启建表、版本升级时执行数据库迁移(一次性任务) | 不处理业务请求 | 库结构不就绪,其他进程起不来或反复报错 |
| backend | HTTP API 与 Web UI 后端;校验请求、读写数据库、投递任务 | 不持有交易循环、不直接驱动订单 | 界面与外部调用不可用;已在跑的交易循环不受影响 |
| trading-worker | 交易长循环:驱动策略、管理订单生命周期、对接交易所适配 | 不服务 HTTP 请求 | 策略停摆、订单状态不再推进 |
| scheduler-worker | 调度循环:编排周期性工作(数据同步节奏等) | 不执行重活本身 | 周期任务断档,数据新鲜度下降 |
| celery-worker | 异步任务执行器:回测、数据处理等重负载 | 不做定时决策 | 回测提交后无响应、队列积压 |
| celery-beat | 定时节拍器:按计划向队列投递任务 | 不执行任务 | 周期任务不再按时触发 |
记忆法:两个「循环」(trading、scheduler)+ 两个「celery」(执行、节拍)+ 一「门面」(backend)+ 一「工匠」(migration)。前五个常驻,migration 用完即走。
职责表的第三列(挂了的影响)值得单独盯住:除 PostgreSQL 一行写着「全系统不可用」之外,没有任何一行是全局瘫痪——trading-worker 挂掉时界面照常、回测照常;backend 挂掉时交易照常。故障被限制在职责边界内,这是你能「睡着觉」的原因。
┌───────────────────────────────────────┐ │ PostgreSQL 18(唯一事实源) │ │ 状态 · 订单 · 行情 · 账户 · 任务结果 │ └────▲──────▲──────▲──────▲──────▲──────┘ │读写 │读写 │读写 │读写 │读写 backend ──────────┘ │ │ │ │ (HTTP 门面) trading- │scheduler│celery-│ migration │ worker │worker │worker │(一次性) │ 投递任务 │ │ │ │ ▼ │ │ │ │ ┌─────────────────────────────────────────────────┐ │ Redis 8(队列 / 缓存 / beat 节拍信号) │ └────▲──────────────────────────────────▲─────────┘ │ backend / beat 投递任务 │ worker 领取任务 └──────── celery-beat 按计划发节拍 ──────┘
三条读图规则:
这套结构把「计算」与「状态」彻底分离:任何进程重启,损失的是几秒计算,而不是事实。
零直连还有一层常被忽略的收益:替换自由。因为 backend 与 worker 之间只有队列契约,没有进程内调用,你可以单独升级 backend 而不惊动交易循环,也可以给 celery-worker 加实例而不动门面——第 2.2 节讲的「可重启、可扩容」红利,起点就是这张图里的「零直连」三个字。反过来,任何一处「为了方便直接调用」的私拉电线,都会让两个进程从此共进退,边界名存实亡。
| 顺序 | 组件 | 等什么 |
|---|---|---|
| 1 | PostgreSQL、Redis | 基础设施健康(健康检查通过) |
| 2 | migration | 数据库可达后执行迁移,完成后放行后续 |
| 3 | backend、trading-worker、scheduler-worker | 库结构就绪后并行启动 |
| 4 | celery-worker、celery-beat | 队列可达后启动(beat 开始按计划投递) |
这就是为什么第 1.1 节要求「看 migration 日志」——它是整条依赖链的闸门。Docker Compose 的依赖与健康检查声明(以官方 compose 文件为准)把这个顺序自动化了,但理解顺序本身,出问题时你才知道往哪看。
升级遵循同一条链:新版本镜像到位 ──▶ 基础设施变更(如有)──▶ migration 执行新的库结构迁移 ──▶ 应用进程滚动替换。其中 migration 仍是闸门:库结构没有就位之前替换应用进程,新代码会在旧结构上摔跟头。反过来,跳过 migration 直接升级应用,是升级故障里最常见的一种——症状往往延迟出现,比当场报错更难排查。
| 误解 | 事实 |
|---|---|
| 「backend 能直接下单」 | 不能。下单由 trading-worker 的长循环驱动;backend 只能创建/修改数据库里的意图与配置 |
| 「migration 是常驻服务」 | 不是。它是迁移完成即退出的进程;每次升级版本时再跑 |
| 「trading-worker 挂了,backend 也会挂」 | 不会。两者解耦;界面照常可用,只是交易停滞——这正是分边界的意义 |
| 「celery-beat 执行定时任务」 | 不执行。它只投递节拍,执行者永远是 celery-worker |
判断技巧:凡「谁做什么」的问题,先回到职责表找唯一负责人;凡「现在什么状态」的问题,去数据库查,别猜进程内存。
职责表的价值要到故障时刻才兑现。三类最常见故障各走一遍:
| 故障 | 第一现场 | 定位动作(示意) | 常见结论 |
|---|---|---|---|
| 页面打不开、接口超时 | backend | 看 backend 状态与日志、确认 PostgreSQL 健康 | backend 重启中,或数据库不可达拖垮门面 |
| 回测提交后毫无反应 | celery-worker | 看队列积压与 worker 日志 | worker 掉线或任务未入队,追投递方 |
| 策略不动、订单不推进 | trading-worker | 看循环日志与库内订单状态 | 循环停滞;重启前先确认数据库与队列健康 |
# bash —— 进程健康速查(示意,服务名以官方 compose 文件为准) docker compose ps # 第一步永远是全景 docker compose logs --tail 100 trading-worker # 症状指向谁就先看谁 docker compose logs --tail 100 celery-worker # 异步任务类问题看这里
拿第一行故障走完整链路:页面打不开 ──▶ docker compose ps 发现 backend 反复重启 ──▶ 日志显示连不上数据库 ──▶ ps 里 PostgreSQL 不健康 ──▶ 修 PostgreSQL,backend 自愈。注意最后一步:你不需要手动「重启 backend」——编排系统在替你做重启循环,你要做的是把它依赖的地基修好。这正是分层视角的价值:症状在门面,病因在地基,中间层只是传声筒。
日志关键词到责任进程的速查表(示意对照):
| 日志或界面关键词 | 责任进程 |
|---|---|
| HTTP 状态码、请求路径、鉴权失败 | backend |
| 订单状态流转、交易所回报 | trading-worker |
| 同步节奏、周期任务编排 | scheduler-worker |
| 回测进度、任务队列深度 | celery-worker |
| 定时触发的投递记录 | celery-beat |
| 建表、字段迁移 | migration |
两张表合用:症状表告诉你「先看谁」,关键词表告诉你「看到的这行日志归谁管」。两者都以职责表为底——边界清晰的系统,排查只是查表。
最后补三个关于进程本身的常见疑问:
| 疑问 | 简答 |
|---|---|
| 六个进程必须在六台机器上吗 | 不必;一键安装把它们放在同一台机器的六个容器里(第 1.1 节) |
| 能不能合并成两个进程省点内存 | 能跑,但故障影响面会重新粘在一起,六进程拆分换来的隔离全部归零 |
| worker 之间会抢任务吗 | celery-worker 天然按队列领取、不重复消费;有循环的 worker 则各管各的分片(以官方文档为准) |
边界画清楚了,但为什么非要把 HTTP API 和交易循环拆开不可?下一节正面回答这个问题——四个会杀死长循环的场景,以及这条哲学对任何自研系统的启发。