Skip to content

43|定时任务与 APScheduler

后台任务在请求触发时执行,定时任务则按预定时间表运行:每天凌晨清理日志、每小时同步一次监控数据、每 5 分钟检查服务健康状态。APScheduler 是 Python 最常用的定时任务库,支持多种触发器和任务存储后端。

一、安装与基础

bash
uv add apscheduler
python
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger

scheduler = BackgroundScheduler()

二、触发器类型

间隔触发(IntervalTrigger)

python
def check_health():
    print("检查服务健康状态...")

# 每 5 分钟执行一次
scheduler.add_job(
    check_health,
    trigger=IntervalTrigger(minutes=5),
    id="health_check",
    replace_existing=True,
)
参数含义
seconds / minutes / hours / days / weeks间隔单位

定时触发(CronTrigger)

python
def cleanup_logs():
    print("清理过期日志...")

# 每天凌晨 3 点执行
scheduler.add_job(
    cleanup_logs,
    trigger=CronTrigger(hour=3, minute=0),
    id="cleanup_logs",
)

Cron 表达式字段:

字段范围示例
year四位年份2026
month1-121, 6-12
day1-31*/2(每两天)
week1-53
day_of_week0-6 或 mon-sunmon-fri
hour0-239, 14, 18
minute0-590, 30
second0-590
python
# 工作日早上 9 点和下午 6 点
CronTrigger(day_of_week="mon-fri", hour="9,18", minute=0)

# 每小时的第 0 和第 30 分钟
CronTrigger(minute="0,30")

日期触发(DateTrigger)

在指定时间执行一次:

python
from apscheduler.triggers.date import DateTrigger
from datetime import datetime

scheduler.add_job(
    send_reminder,
    trigger=DateTrigger(run_date=datetime(2026, 7, 1, 10, 0)),
)

三、与 FastAPI 集成

python
from fastapi import FastAPI
from contextlib import asynccontextmanager

scheduler = BackgroundScheduler()

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时:添加任务并启动调度器
    scheduler.add_job(check_health, IntervalTrigger(minutes=5), id="health")
    scheduler.start()
    yield
    # 关闭时:停止调度器
    scheduler.shutdown()

app = FastAPI(lifespan=lifespan)

lifespan 在应用启动时执行 yield 之前的代码,关闭时执行 yield 之后的代码。这是管理 APScheduler 生命周期的正确方式。

四、任务管理

移除任务

python
scheduler.remove_job("health_check")

暂停和恢复

python
scheduler.pause_job("health_check")
scheduler.resume_job("health_check")

获取任务列表

python
for job in scheduler.get_jobs():
    print(job.id, job.next_run_time)

五、任务持久化

默认任务只保存在内存中,进程重启后丢失。生产环境需要持久化:

python
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

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

scheduler = BackgroundScheduler(jobstores=jobstores)

任务信息(下次执行时间、执行次数等)存入数据库,进程重启后从数据库恢复。

六、并发执行

同一个任务如果上次还没执行完,下次触发时间又到了,默认会同时运行多个实例。如果任务不能并发,加锁:

python
import fcntl

def exclusive_task():
    with open("/tmp/task.lock", "w") as f:
        try:
            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
            # 执行任务...
        except BlockingIOError:
            print("上次任务还在执行,跳过")
            return
        finally:
            fcntl.flock(f, fcntl.LOCK_UN)

七、常见错误

没有启动调度器

python
scheduler.add_job(check_health, IntervalTrigger(minutes=5))
# 错误:忘记 start()

# 正确
scheduler.start()

任务函数阻塞事件循环

python
# 错误:在异步应用中阻塞
async def task():
    time.sleep(60)   # 阻塞 60 秒,其他任务都卡住

# 正确:用异步 sleep
async def task():
    await asyncio.sleep(60)

时区问题

python
# 错误:没有指定时区,按系统本地时间执行
CronTrigger(hour=3)   # 服务器换时区后执行时间会变

# 正确:明确指定时区
from pytz import timezone
CronTrigger(hour=3, timezone=timezone("Asia/Shanghai"))