FastApi使用装饰器实现定时任务重复执行的问题

装饰器实现定时任务的方法可查看:FastApi定时任务发送钉钉消息
发现部署到服务器运行时同一时间会执行多次

通过日志可以看到同一时间执行了5次

这是因为使用gunicorn启动服务时设置的 workers 进程数是5个

from multiprocessing import cpu_count
​
reload_engine = 'inotify'
# //绑定与Nginx通信的端
bind = '0.0.0.0:8000'
daemon = True  # 守护进程
​
workers = cpu_count() * 2 + 1  # 进程数
​
worker_class = 'gevent'  # 默认为阻塞模式,最好选择gevent模式,默认的是sync模式
# 维持TCP链接
keepalive = 6
timeout = 65
graceful_timeout = 10
worker_connections = 65535
# 日志级别
loglevel = 'info'
# 访问日志路径
accesslog = '/www/wwwlogs/gunicorn_access.log'
# 错误日志路径
errorlog = '/www/wwwlogs/gunicorn_error.log'
# 设置gunicorn访问日志格式,错误日志无法设置
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'

解决办法

#!/usr/bin/env python
# _*_ coding: utf-8 _*_
# 创 建 人: 李先生
# 文 件 名: tasks.py
# 创建时间: 2022/9/29 0029 20:32
# @Version:V 0.1
# @desc :
import asyncio
from functools import wraps
from asyncio import ensure_future
from starlette.concurrency import run_in_threadpool
from typing import Any, Callable, Coroutine, Optional, Union
from aioredis import Redis, create_redis_pool
​
from public.log import logger
​
NoArgsNoReturnFuncT = Callable[[], None]
NoArgsNoReturnAsyncFuncT = Callable[[], Coroutine[Any, Any, None]]
NoArgsNoReturnDecorator = Callable[
    [Union[NoArgsNoReturnFuncT, NoArgsNoReturnAsyncFuncT]],
    NoArgsNoReturnAsyncFuncT
]
​
redis = None # 这是redis全局变量
def repeat_task(
        *,
        seconds: float,
        wait_first: bool = False,
        raise_exceptions: bool = False,
        max_repetitions: Optional[int] = None,
) -> NoArgsNoReturnDecorator:
​
    def decorator(func: Union[NoArgsNoReturnAsyncFuncT, NoArgsNoReturnFuncT]) -> NoArgsNoReturnAsyncFuncT:
        is_coroutine = asyncio.iscoroutinefunction(func)
​
        @wraps(func)
        async def wrapped() -> None:
            repetitions = 0  # 限制定时任务执行次数
            global redis
            if not redis: # redis为None就重新连接
                redis = await create_redis_pool(f"redis://:@127.0.0.1:6379/0", password="123456", encoding="utf-8")
​
            async def loop() -> None:
                nonlocal repetitions
                if wait_first:
                    await asyncio.sleep(seconds)
                while max_repetitions is None or repetitions < max_repetitions:
                    try:
                        lock = await redis.get(key="LOCK") # 查看是否有lock标记
                        if not lock: # 没有就执行定时任务
                            await redis.setex(key="LOCK", value="lock", seconds=seconds) # 任务开始前先设置lock标记
                            if is_coroutine:
                                # 以协程方式执行
                                await func()  # type: ignore
                            else:
                                # 以线程方式执行
                                await run_in_threadpool(func)
                            repetitions += 1
                        else:
                            logger.info(f"多个进程同一时间多次执行定时任务的限制==>{lock}") # 未执行定时任务log打印
                    except Exception as exc:
                        logger.error(f'执行重复任务异常: {exc}')
                        if raise_exceptions:
                            raise exc
                    await asyncio.sleep(seconds)
​
            ensure_future(loop())
​
        return wrapped
​
    return decorator

然后再次执行定时任务

看样子是解决了~

posted @ 2022-10-24 22:16  backlightズ  阅读(762)  评论(0编辑  收藏  举报