当前位置: 首页 > news >正文

FastAPI + APScheduler + Uvicorn 多进程下避免重复加载任务的解决方案

目录

1. 问题背景

2. 原因分析

3. 常见解决方案

3.1 方案一:单 worker 模式(简单粗暴)

3.2 方案二:文件锁(单机可用,跨平台推荐)

示例代码(使用 filelock,兼容 Linux 和 Windows)

3.3 方案三:使用 Redis/数据库 JobStore(推荐)

示例代码(Redis JobStore)

3.4 方案四:独立 Scheduler 进程(解耦推荐)

示例

4. 方案对比

5. 总结与推荐


1. 问题背景

在 FastAPI 中集成 APScheduler,可以方便地执行定时任务。但当我们用 Uvicorn 启动应用时,如果指定了 --workers > 1,就会出现 每个 worker 进程都会初始化一份 APScheduler 调度器 的情况。

例如:

uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

结果是同一个任务被 重复执行 4 次(每个 worker 都跑一遍),这在生产环境是无法接受的。


2. 原因分析

  • Uvicorn 的多进程模式通过 multiprocessing fork 出多个完全独立的 Python 进程。

  • 每个进程都会加载一份 FastAPI 应用实例,执行 @app.on_event("startup") 钩子。

  • 如果在 startup 里初始化 APScheduler,就会导致 每个进程都启动了自己的调度器

换句话说:Uvicorn 没有 master/worker 角色区分,每个 worker 都是“主进程”。


3. 常见解决方案

3.1 方案一:单 worker 模式(简单粗暴)

只启动一个进程,避免重复加载:

uvicorn main:app --workers 1

适合任务量不大、并发不高的应用。 但缺点是失去了多进程并发能力。


3.2 方案二:文件锁(单机可用,跨平台推荐)

在应用启动时尝试获取文件锁,只有第一个获取锁的进程会启动 APScheduler,其他进程跳过。

示例代码(使用 filelock,兼容 Linux 和 Windows)
import os
import logging
from fastapi import FastAPI
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from filelock import FileLock, Timeout
​
app = FastAPI()
LOCK_FILE = "scheduler.lock"
​
def my_job():print(f"[PID={os.getpid()}] running scheduled task...")
​
@app.on_event("startup")
async def startup_event():lock = FileLock(LOCK_FILE)try:lock.acquire(timeout=0.1)scheduler = AsyncIOScheduler()scheduler.add_job(my_job, "interval", seconds=10, id="unique_job", replace_existing=True)scheduler.start()
​app.state.scheduler = schedulerapp.state.lock = locklogging.info(f"[PID={os.getpid()}] Scheduler started (lock acquired).")
​except Timeout:logging.info(f"[PID={os.getpid()}] Another process holds the scheduler lock. Skipping.")
​
@app.on_event("shutdown")
async def shutdown_event():if hasattr(app.state, "scheduler"):app.state.scheduler.shutdown()if hasattr(app.state, "lock"):app.state.lock.release()logging.info(f"[PID={os.getpid()}] Scheduler stopped and lock released.")

🔎 特点:

  • 适合 单机部署

  • 锁文件路径必须可写;

  • 跨平台可用(Linux/Windows 都支持)。


3.3 方案三:使用 Redis/数据库 JobStore(推荐)

APScheduler 支持 Redis、MongoDB、SQLAlchemy 等作为 JobStore。 即使多个进程启动调度器,它们共享相同的任务存储,不会重复执行。

示例代码(Redis JobStore)
from fastapi import FastAPI
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.jobstores.redis import RedisJobStore
​
app = FastAPI()
​
def my_job():print("Running job...")
​
@app.on_event("startup")
async def startup_event():jobstores = {"default": RedisJobStore(host="localhost", port=6379, db=0)}scheduler = AsyncIOScheduler(jobstores=jobstores)scheduler.add_job(my_job, "interval", seconds=10, id="unique_job", replace_existing=True)scheduler.start()app.state.scheduler = scheduler
​
@app.on_event("shutdown")
async def shutdown_event():if hasattr(app.state, "scheduler"):app.state.scheduler.shutdown()

🔎 特点:

  • 适合 多进程、多机集群 部署;

  • 保证任务全局唯一;

  • 需要 Redis 或数据库支持。


3.4 方案四:独立 Scheduler 进程(解耦推荐)

将 APScheduler 独立成单独的进程或服务,只负责任务调度;FastAPI 应用只处理 API 请求。

示例
# scheduler.py
import asyncio
from apscheduler.schedulers.asyncio import AsyncIOScheduler
​
def my_job():print("Task executed")
​
async def main():scheduler = AsyncIOScheduler()scheduler.add_job(my_job, "interval", seconds=10)scheduler.start()await asyncio.Event().wait()
​
if __name__ == "__main__":asyncio.run(main())

运行:

python scheduler.py
uvicorn main:app --workers 4

🔎 特点:

  • 彻底避免重复加载;

  • 解耦,适合中大型项目。


4. 方案对比

方案优点缺点
单 worker简单稳定无法利用多进程并发
文件锁跨平台,代码简单,单机有效多机部署无效
Redis/DB JobStore多进程/多机可用,全局唯一依赖额外存储
独立 scheduler 进程彻底解耦,灵活扩展需要额外部署进程

5. 总结与推荐

  • 开发环境 / 单机部署:可以用 文件锁 简单解决。

  • 生产环境 / 多机部署:推荐用 Redis/DB JobStore 或者 独立 scheduler 进程

  • 任务量大/耗时任务:建议用 APScheduler + Celery/RQ,调度和执行分离。


最佳实践:

FastAPI(Uvicorn 多 worker)只处理请求,定时任务交给单独的 scheduler 服务,或者 Redis JobStore 管理。 这样既能保证任务不会重复执行,又能发挥多进程并发的优势。


http://www.dtcms.com/a/393560.html

相关文章:

  • 数据库造神计划第十八天---事务(1)
  • Docker在Linux中离线部署
  • 面阵vs线阵工业相机的触发方式有什么不同?
  • 【Hadoop】HBase:构建于HDFS之上的分布式列式NoSQL数据库
  • 拉取GitHub源码方式
  • 【国二】【C语言】改错题中考察switch的用法、do while执行条件的用法
  • 23种设计模式之【命令模式模式】-核心原理与 Java 实践
  • APP持续盈利:简单可行实行方案
  • qt 操作pdf文档小工具
  • Web3 开发者周刊 68 | EF 将成立一个新的 AI 团队
  • [OpenGL]相机系统
  • 软件体系结构——负载均衡
  • Unity 游戏引擎中 HDRP(高清渲染管线) 的材质着色器选择列表
  • 系统架构设计师(现代计算机系统架构和软件开发)错题集
  • 七、Linux创建自己的proc文件
  • 理解CSS中的100%和100vh
  • [特殊字符] Chrome浏览器证书导入指南
  • 15-用户登录案例
  • Kurt-Blender零基础教程:第3章:材质篇——第1节:材质基础~原理化BSDF,添加有纹理材质与用蒙版做纹理叠加
  • 南京大学 - 复杂结构数据挖掘(一)
  • 嵌入式系统、手机与电脑:一场技术演化的“三角关系”
  • Go语言常用的第三方开发包教程合集
  • 鸿蒙Next ArkTS卡片进程模型解析:安全高效的UI组件隔离之道
  • ubuntu linux 控制wifi功能 dbus控制
  • `TensorBoard`、`PyTorchViz` 和 `HiddenLayer` 深度学习中三个重要的可视化工具
  • 本地设备ipv6默认网关和路由器ipv6默认网关的区别
  • 云原生docker在线yum安装
  • LeetCode 384 打乱数组 Swift 题解:从洗牌算法到实际应用
  • 计算机网络-因特网
  • HDFS和MapReduce——Hadoop的两大核心技