FastAPI-utils 重复任务指南:使用@repeat_every实现周期性任务
引言
在FastAPI应用开发中,我们经常需要执行一些周期性任务,比如定时清理缓存、定期更新数据库记录等。传统做法是在启动事件中创建循环,但这会带来几个技术挑战:
- 启动事件需要快速完成,否则会阻塞服务器启动
- 同步IO操作会阻塞事件循环
- 异常处理机制不完善
fastapi-utils项目提供的@repeat_every
装饰器优雅地解决了这些问题,让开发者能够轻松实现可靠的周期性任务。
基本用法
@repeat_every
装饰器的基本使用非常简单。下面是一个典型的使用场景:
from fastapi import FastAPI
from fastapi_utils.tasks import repeat_every
from fastapi_utils.session import FastAPISessionMaker
app = FastAPI()
sessionmaker = FastAPISessionMaker("sqlite:///./test.db")
@app.on_event("startup")
@repeat_every(seconds=60 * 60) # 每小时执行一次
async def remove_expired_tokens() -> None:
async with sessionmaker.context_session() as session:
# 执行清理过期token的逻辑
pass
关键点说明:
- 同时使用
@app.on_event("startup")
和@repeat_every
确保任务在启动时立即执行并周期性重复 seconds
参数指定执行间隔(秒)- 函数可以是异步(async def)或同步(def)形式
高级配置选项
@repeat_every
提供了多个参数来满足不同场景需求:
首次执行时机控制
@repeat_every(seconds=60, wait_first=True) # 首次执行等待60秒
def task():
pass
wait_first=False
(默认):立即执行第一次,然后周期性执行wait_first=True
:先等待一个周期,再执行第一次
异常处理机制
import logging
logger = logging.getLogger(__name__)
@repeat_every(seconds=60, logger=logger, raise_exceptions=False)
def risky_task():
# 可能抛出异常的操作
pass
logger
:指定日志记录器,异常会被记录raise_exceptions
:False
(默认):捕获异常不中断任务True
:抛出异常并停止任务
执行次数限制
@repeat_every(seconds=60, max_repetitions=10) # 只执行10次
def limited_task():
pass
max_repetitions=None
(默认):无限次执行- 设置整数值:限制最大执行次数
技术实现原理
理解@repeat_every
的内部机制有助于更好地使用它:
- 非阻塞设计:对于同步函数,会自动使用线程池执行,避免阻塞事件循环
- 异常隔离:默认捕获任务异常,防止影响主程序
- 生命周期管理:与FastAPI生命周期事件无缝集成
- 资源清理:服务器关闭时会自动取消周期性任务
最佳实践建议
- 日志记录:始终为关键任务配置logger参数,避免静默失败
- 超时处理:确保任务执行时间远小于间隔时间
- 资源管理:使用上下文管理器管理数据库连接等资源
- 测试策略:在测试环境中减小间隔时间,验证任务可靠性
- 监控集成:考虑将任务执行情况集成到应用监控系统
常见问题解答
Q:任务执行时间超过间隔时间会怎样?
A:@repeat_every
会等待当前任务完成后再开始计算下一个间隔,不会并发执行。
Q:如何在开发环境快速测试? A:可以临时设置较小的seconds值(如5秒),但生产环境要恢复合理值。
Q:任务抛异常后如何恢复? A:默认配置下任务会继续执行,如需停止需设置raise_exceptions=True。
Q:能否动态修改间隔时间? A:目前不支持运行时修改,需要重启应用。
通过fastapi-utils的@repeat_every
装饰器,开发者可以轻松实现各种周期性任务,同时保证了代码的简洁性和可靠性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考