自托管 AI-Trader 如何把 API 与后台 worker 拆成独立进程启动并确认单实例运行?
【免费下载链接】AI-Trader"AI-Trader: 100% Fully-Automated Agent-Native Trading"项目地址: https://gitcode.com/GitHub_Trending/aitrad/AI-Trader
如果你自托管 AI-Trader,会遇到一个明确的部署问题:FastAPI 服务既要对 HTTP 请求低延迟响应,又要周期性地拉取行情价格、结算 Polymarket 仓位、压缩利润历史这些重活。这个仓库给出的方案是把两者拆成两个进程:API 进程默认只做 HTTP 服务(见 main.py 中AI_TRADER_API_BACKGROUND_TASKS默认 false 的分支),后台循环放进独立的 worker.py,并且 worker 自带 Redis / 文件两级单实例锁。本文的操作路径是:装依赖、配环境 → 启动 API 进程 → 启动 worker 进程 → 用日志和锁文件确认只有一个 worker 在跑、任务确实在执行。
拆分方式:两个入口进程,任务清单共享
两个入口都依赖 tasks.py 中的同一份后台任务注册表BACKGROUND_TASK_REGISTRY,包含 14 个任务:
prices、profit_history、polymarket_settlement、challenge_settlement、team_mission_form、team_contribution_score、team_mission_settlement、signal_quality_score、agent_metric_snapshots、network_edges、market_news、macro_signals、etf_flows、stock_analysis
API 进程:main.py 通过
background_tasks_enabled_for_api()判断是否在本进程内启动后台任务,该开关读取AI_TRADER_API_BACKGROUND_TASKS,默认 false。关闭时启动日志会明确提示:API background tasks disabled. Run `python service/server/worker.py` to process prices, profit history, settlements, and market intel.worker 进程:worker.py 的模块注释写明它就是为此设计的——“Run this separately from the FastAPI process so HTTP requests are not competing with price refreshes, profit-history compaction, and market-intel snapshots.”。它启动时若未设置
AI_TRADER_BACKGROUND_TASKS,会默认启用全部任务(DEFAULT_BACKGROUND_TASKS)。
也就是说,默认配置下正确的拓扑是:API 进程 HTTP-only,worker 进程负责所有后台循环。
准备环境
安装后端依赖(service/requirements.txt 包含
fastapi、uvicorn[standard]、redis等):pip install -r service/requirements.txt按 README.md 的 “Self-hosting (database)” 一节配置数据库:复制
.env.example为.env,二选一——- PostgreSQL:设置
DATABASE_URL=postgresql://...(共享或生产部署推荐); - SQLite:留空
DATABASE_URL,使用DB_PATH(默认service/server/data/clawtrader.db,仅限本地快速启动)。
优先级规则:
DATABASE_URL非空时用 PostgreSQL,DB_PATH被忽略。两个进程会连接同一个数据库,所以必须使用同一份.env环境。- PostgreSQL:设置
(可选)配置 Redis,用于跨机器的 worker 单实例锁。config.py 读取以下变量:
REDIS_ENABLED=true REDIS_URL=redis://... # 你的 Redis 地址 REDIS_PREFIX=ai_trader # 键前缀,默认 ai_trader不配置 Redis 也能跑,worker 会自动回退到本机文件锁,见下文。
启动 API 进程
在仓库根目录执行(main.py 的__main__分支直接以host="0.0.0.0", port=8000调用 uvicorn):
python service/server/main.py启动后先观察两类日志判断 API 侧状态:
- 数据库与缓存状态行,如
Database ready: backend=...、Cache ready: enabled=... configured=...; - 确认走到了 HTTP-only 分支,即出现上文那句
API background tasks disabled. Runpython service/server/worker.py...。如果没看到这句而是看到Background tasks started: N,说明AI_TRADER_API_BACKGROUND_TASKS被设成了真值,此时 API 进程已经在跑后台任务,不要再启动 worker.py,否则同一组任务会在两个进程里重复执行(worker 锁只约束 worker 进程,不约束 API 进程)。
验证 API 可用:/health端点定义在 routes_market.py,返回{'status': 'ok', 'timestamp': <UTC 时间>}:
curl http://127.0.0.1:8000/health启动 worker 进程
另开一个终端(同一仓库根目录)执行:
python service/server/worker.py启动后的预期日志(按 worker.py 中的logger输出顺序):
- 单实例锁获取成功,二选一:
Acquired worker singleton lock via Redis(配置了 Redis 时);Acquired worker singleton lock via file(无 Redis 或 Redis 客户端不可用时)。
Worker database ready: {...},确认 worker 连上了同一个数据库。- 每启动一个任务输出一行
Starting background task: <任务名>,任务名与AI_TRADER_BACKGROUND_TASKS中的清单一一对应。
如果看到No background tasks enabled; set AI_TRADER_BACKGROUND_TASKS to a comma-separated task list.,说明你显式设置的AI_TRADER_BACKGROUND_TASKS里没有任何注册表中的合法任务名,需要检查拼写。
按需裁剪任务:AI_TRADER_BACKGROUND_TASKS是逗号分隔的任务名列表,只写你需要的子集,例如只跑价格刷新和利润历史:
AI_TRADER_BACKGROUND_TASKS=prices,profit_history python service/server/worker.py确认 worker 单实例运行
worker 在启动早期就竞争单实例锁,逻辑见 worker.py 与 cache.py 的acquire_lock:
Redis 锁:配置了 Redis 时,worker 尝试获取键
worker:singleton的锁(非阻塞,blocking=False)。锁超时时间由AI_TRADER_WORKER_LOCK_TIMEOUT_SECONDS控制,代码中下限为 30 秒、默认 120 秒,worker 运行期间会周期性reacquire续期;若续期失败(锁被抢走),worker 记录Lost worker singleton Redis lock: ...后直接退出(os._exit(1)),避免双实例并发。文件锁:Redis 不可用时回退到
fcntl.flock对锁文件加非阻塞排他锁,默认路径/tmp/ai-trader-worker.lock,可用AI_TRADER_WORKER_LOCK_FILE覆盖。拿到锁后 worker 会把自己的 PID 写入该文件,因此锁文件同时是一个单实例证明:cat /tmp/ai-trader-worker.lock # 输出应为正在运行的 worker 的 PID
判定方法:再开一个终端重复执行python service/server/worker.py。第二个进程不应继续运行,而会打印下面之一后退出:
Another AI-Trader worker is already running; Redis singleton lock is held.或
Another AI-Trader worker is already running; lock_file=/tmp/ai-trader-worker.lock看到这条 WARNING 且进程退出,即单实例机制生效;如果第二个进程继续打出Starting background task:日志,说明两个进程没共享同一把锁(典型原因:不同机器/容器之间没有共享 Redis 也没有共享文件系统,此时文件锁无法跨主机生效,需要配置 Redis 锁)。
相关环境变量汇总(均有默认值,只列影响拆分部署的项):
| 变量 | 默认值 | 作用 |
|---|---|---|
AI_TRADER_API_BACKGROUND_TASKS | false | 是否让 API 进程内跑后台任务;拆分部署保持默认 |
AI_TRADER_BACKGROUND_TASKS | 全部 14 个任务 | worker 启用的任务清单,逗号分隔 |
AI_TRADER_WORKER_LOCK_FILE | /tmp/ai-trader-worker.lock | 文件锁路径 |
AI_TRADER_WORKER_LOCK_TIMEOUT_SECONDS | 120(下限 30) | Redis 锁超时/续期周期基准 |
AI_TRADER_WORKER_NICE | 10 | worker 启动时对自己的进程设置 nice 值,降低其调度优先级 |
PROFIT_HISTORY_PRUNE_ON_WORKER_START | false | true 时 worker 启动后额外调度一次利润历史 prune |
注意fcntl是 Unix/Linux 机制,文件锁路径适用于 Linux 类系统;跨主机部署应以 Redis 锁为准。
确认后台任务确实在执行
锁只证明“只有一个 worker”,任务是否真在跑看周期性日志。以下均为 tasks.py 代码中的输出格式,具体数值随行情和数据库内容变化,仅作格式参照:
[Price Update] candidates=... skipped_unsupported=... ... [Price Update] summary requested=... updated=... failed=... [Profit History] Recorded profit for N agents in X.XXs [Polymarket Settler] positions=... settled=... skipped=... [Market Intel] Refreshed market news snapshots: inserted_categories=... errors=...例如prices任务每轮结束会打印下一轮间隔([Price Update] Next update in {refresh_interval} seconds,间隔由POSITION_REFRESH_INTERVAL控制,默认 900 秒,.env.example中示例值为 300 秒)。若某任务持续打印[... Error]行而没有正常 summary 行,说明该任务在报错空转,对照行首的[Task Name]前缀定位是哪个循环。
限制与下一步
- 文件锁只在本机有效;多台机器/容器部署 worker 时必须配置
REDIS_ENABLED=true和REDIS_URL,由 Redis 锁保证全局单实例。 AI_TRADER_API_BACKGROUND_TASKS与 worker.py 是两条互斥路径:默认走 worker.py;若把它设为 true 让 API 进程内跑任务,就不要再起 worker,避免同一任务双跑。- 仓库的本地生产运维文档 docs/local-ops/production-branch.md 提到生产 API 服务以 systemd 单元
ai-trader-api管理(变更分支后用systemctl restart ai-trader-api重启)。如果你的环境同样用 systemd 托管,worker 进程可以用同样的方式做成常驻单元,并配合AI_TRADER_WORKER_LOCK_FILE/ Redis 锁避免重启竞态下出现双实例。 - worker 收到
SIGINT/SIGTERM时会取消所有任务、释放锁后干净退出(worker.py),重启流程不会遗留锁状态(文件锁随进程退出自动释放,Redis 锁在 finally 中主动 release)。
【免费下载链接】AI-Trader"AI-Trader: 100% Fully-Automated Agent-Native Trading"项目地址: https://gitcode.com/GitHub_Trending/aitrad/AI-Trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考