如何在 FastAPI 中玩转 APScheduler,让任务定时自动执行?

1. APScheduler

1.1 什么是 APScheduler?

APScheduler 是一个轻量级但功能强大的 Python 定时任务调度库,支持固定时间间隔、特定时间点或 Cron 式任务调度。它能与 FastAPI 无缝集成,用于后台执行周期性任务(如数据清理、邮件发送、报表生成等),而不会阻塞主线程。

1.2 在 FastAPI 中集成 APScheduler

  • 后台任务自动化:避免手动触发重复性操作。
  • 资源解耦:将耗时任务从 API 请求中分离,提升响应速度。
  • 高可靠性:支持任务持久化和故障恢复机制。
  • 灵活调度:通过 Cron 表达式实现复杂时间规则。
graph TD A[FastAPI 启动] --> B[初始化 Scheduler] B --> C[添加定时任务] C --> D[任务执行] D --> E[任务结果处理] E --> F[关闭 Scheduler]

2. 基础集成步骤

2.1 安装依赖库

bash 复制代码
pip install fastapi==0.109.0
pip install uvicorn==0.27.0
pip install apscheduler==3.10.4
pip install pydantic==2.5.1

2.2 核心代码实现

python 复制代码
from fastapi import FastAPI
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.jobstores.memory import MemoryJobStore
from pydantic import BaseModel

# 定义 Pydantic 数据模型验证输入
class TaskRequest(BaseModel):
    task_name: str
    interval: int  # 单位:秒

app = FastAPI()
scheduler = AsyncIOScheduler(jobstores={"default": MemoryJobStore()})

@app.on_event("startup")
async def init_scheduler():
    """应用启动时初始化调度器"""
    scheduler.start()
    print("Scheduler started")

@app.on_event("shutdown")
async def shutdown_scheduler():
    """应用关闭时安全停止调度器"""
    scheduler.shutdown()
    print("Scheduler stopped")

@app.post("/schedule")
async def add_task(request: TaskRequest):
    """添加定时任务接口"""
    # 使用 Pydantic 自动校验 request 数据
    job = scheduler.add_job(
        execute_background_task,
        "interval",
        seconds=request.interval,
        args=[request.task_name],
        id=request.task_name
    )
    return {"status": "success", "job_id": job.id}

async def execute_background_task(task_name: str):
    """模拟后台任务执行"""
    print(f"Executing task: {task_name} at {datetime.utcnow()}")

代码说明:

  1. AsyncIOScheduler:基于 asyncio 的调度器,避免阻塞主线程。
  2. MemoryJobStore:默认内存存储(生产环境建议替换为 Redis 或 SQLAlchemy)。
  3. @app.on_event:生命周期钩子管理调度器启停。
  4. Pydantic 模型:自动校验 API 输入参数。

3. 高级应用场景

3.1 Cron 表达式调度

python 复制代码
# 添加每日凌晨执行的任务
scheduler.add_job(
    daily_report,
    "cron",
    hour=0,
    minute=5,
    id="daily_report"
)

3.2 异常处理与重试

python 复制代码
from apscheduler.events import EVENT_JOB_ERROR

def error_listener(event):
    """全局任务异常监听"""
    if event.exception:
        print(f"Job crashed: {event.job_id}, error: {event.exception}")

scheduler.add_listener(error_listener, EVENT_JOB_ERROR)

3.3 持久化存储(使用 SQLite)

python 复制代码
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

jobstores = {
    "default": SQLAlchemyJobStore(url="sqlite:///jobs.sqlite")
}
scheduler = AsyncIOScheduler(jobstores=jobstores)

4. 课后 Quiz

  1. 问题 :如何在 APScheduler 中实现每周一上午 9 点执行任务?
    答案

    python 复制代码
    scheduler.add_job(task, "cron", day_of_week="mon", hour=9)
  2. 问题 :任务意外中止后如何自动恢复?
    答案

    使用持久化存储(如 SQLAlchemyJobStore)并在调度器初始化时添加 misfire_grace_time=300 参数。


5. 常见报错解决方案

报错 1:JobLookupError: No job by the id of ...

  • 原因:操作了不存在的任务 ID。

  • 解决方案

    python 复制代码
    # 操作前先检查任务是否存在
    if scheduler.get_job(job_id):
        scheduler.remove_job(job_id)

报错 2:MaxIterationsReached

  • 原因:Cron 任务迭代次数超出限制(通常因时间表达式错误导致)。
  • 预防措施
    • 使用在线工具校验 Cron 表达式(如 crontab.guru)。
    • 避免在循环中添加重复任务。

报错 3:RuntimeError: Scheduler not running

  • 原因:未正确初始化调度器生命周期。

  • 修复方法

    python 复制代码
    # 确保在 startup 事件中启动
    @app.on_event("startup")
    async def init():
        scheduler.start()
相关推荐
FreeBuf_20 分钟前
Salesloft Drift网络攻击事件溯源:GitHub账户失陷与OAuth令牌窃取
安全·github
第七种黄昏24 分钟前
GitHub 项目提交完整流程(含常见问题与解决办法)
github
考虑考虑39 分钟前
Postgerssql格式化时间
数据库·后端·postgresql
Chan161 小时前
【智能协同云图库】基于统一接口架构构建多维度分析功能、结合 ECharts 可视化与权限校验实现用户 / 管理员图库统计、通过 SQL 优化与流式处理提升数据
java·spring boot·后端·sql·spring·intellij-idea·echarts
库库林_沙琪马1 小时前
REST接口幂等设计深度解析
spring boot·后端
IT_陈寒1 小时前
Redis性能提升50%的7个关键优化策略,90%开发者都不知道第5点!
前端·人工智能·后端
智商偏低1 小时前
ASP.NET Core 身份验证概述
后端·asp.net
冷冷的菜哥1 小时前
ASP.NET Core使用MailKit发送邮件
后端·c#·asp.net·发送邮件·mailkit
canonical_entropy2 小时前
XDef:一种面向演化的元模型及其构造哲学
后端
确定过眼神!2 小时前
GitHub提交到公共项目流程
github·changeset