定时任务系统原理与应用
1. 定时任务的核心概念
定时任务系统是Web应用中用于在特定时间自动执行任务的机制,它在各种场景中都有重要应用:
- 数据清理:每日凌晨清理过期缓存数据
- 报表生成:每周一早晨生成业务数据周报
- 状态刷新:每5分钟同步外部系统状态
- 批量处理:月末计算用户账单
在FastAPI中,主要存在两种实现方式:
- 后台任务队列:适用于一次性延时任务
- 定时调度系统:适用于周期重复任务
graph LR
A[触发方式] --> B[客户端请求触发]
A --> C[定时器自动触发]
B --> D[BackgroundTasks]
C --> E[APScheduler]
2. APScheduler 集成方案
APScheduler 是Python中最成熟的定时任务调度库,支持多种调度器和存储方案:
调度器类型比较:
调度器类型 | 适用场景 | 特点 |
---|---|---|
BlockingScheduler | 独立脚本 | 阻塞主线程 |
BackgroundScheduler | Web应用 | 后台独立线程 |
AsyncIOScheduler | 异步应用 | 兼容asyncio |
集成示例:
python
# requirements.txt
fastapi==0.109.2
apscheduler==3.10.1
pydantic==2.6.4
uvicorn==0.27.1
python
from fastapi import FastAPI
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from pydantic import BaseModel
import logging
app = FastAPI()
scheduler = BackgroundScheduler()
class TaskConfig(BaseModel):
"""定时任务配置模型"""
task_id: str
schedule: str # Cron表达式
payload: dict = {}
@app.on_event("startup")
async def init_scheduler():
"""应用启动时初始化调度器"""
scheduler.add_job(clean_temp_files, "cron", hour=3) # 每天3点执行
scheduler.add_job(send_daily_report, CronTrigger.from_crontab("0 9 * * 1-5")) # 工作日9点
scheduler.start()
logging.info("Scheduler started")
@app.on_event("shutdown")
def shutdown_scheduler():
"""应用关闭时安全终止调度器"""
scheduler.shutdown()
logging.info("Scheduler stopped")
def clean_temp_files():
"""清理临时文件任务"""
logging.info("Cleaning temp files...")
# 实际清理逻辑...
def send_daily_report():
"""发送日报任务"""
logging.info("Sending daily report...")
# 实际邮件发送逻辑...
3. 案例:电商订单超时处理
业务需求:
- 用户下单后15分钟内未支付自动取消订单
- 每天凌晨统计前日取消订单数量
Pydantic 订单模型:
python
class Order(BaseModel):
order_id: str
user_id: int
items: list[str]
amount: float
created_at: datetime = Field(default_factory=datetime.now)
status: Literal["pending", "paid", "canceled"] = "pending"
定时任务实现:
python
from datetime import timedelta
@app.post("/orders")
async def create_order(order: Order):
"""创建订单接口"""
# 订单入库...
scheduler.add_job(
cancel_unpaid_order,
"date",
run_date=datetime.now() + timedelta(minutes=15),
args=[order.order_id]
)
return {"order_id": order.order_id}
def cancel_unpaid_order(order_id: str):
"""取消未支付订单"""
order = db.get_order(order_id)
if order and order.status == "pending":
order.status = "canceled"
db.update_order(order)
logging.info(f"Order {order_id} auto-canceled")
4. 课后 Quiz
-
问题 :如何在分布式环境中避免重复执行定时任务?
答案:结合分布式锁(如Redis Lock)或使用支持集群的作业存储器(如APScheduler的SQLAlchemyJobStore) -
问题 :当任务执行时间超过调度间隔会怎样?
答案:默认产生任务叠加(misfire)。可通过设置:pythonscheduler.add_job(misfire_grace_time=300) # 允许5分钟延迟
5. 常见错误及解决方法
-
错误 :
JobLookupError: No job by the id of...
原因 :任务已移除或从未添加
解决:添加前检查任务是否存在:pythonif not scheduler.get_job(job_id): scheduler.add_job(...)
-
错误 :任务未按预期时间执行
原因 :时区配置错误
解决:明确设置时区:pythonscheduler = BackgroundScheduler(timezone="Asia/Shanghai")
-
错误 :
RuntimeError: Scheduler is not running
原因 :在调度器未启动时添加任务
解决 :确保在startup
事件中添加任务或检查状态:pythonif scheduler.state == STATE_RUNNING: scheduler.add_job(...)