Python 记录一次使用 flask_apsheduler 插件遇到的坑

一枚老男孩 · 2022年11月01日 · 最后由 王稀饭 回复于 2022年11月03日 · 8241 次阅读

目前团队使用 flask+flask_apsheduler 作为异步任务来跑自动化,前几天本地调试的时候,发现 sheduler 服务一直启动不成功,部分代码如下:

本地启动 flask 之后:

启动代码:

但是之前一直都是这么用的没有问题
继续查看 flask_apscheduler 源码,发现新增了下面一段判断逻辑:

def start(self, paused=False):
"""
Start the scheduler.
:param bool paused: if True, don't start job processing until resume is called.
"""

# Flask in debug mode spawns a child process so that it can restart the process each time your code changes,
# the new child process initializes and starts a new APScheduler causing the jobs to run twice.
if flask.helpers.get_debug_flag() and not werkzeug.serving.is_running_from_reloader():
    return

if self.host_name not in self.allowed_hosts and '*' not in self.allowed_hosts:
    LOGGER.debug('Host name %s is not allowed to start the APScheduler. Servers allowed: %s' %
                 (self.host_name, ','.join(self.allowed_hosts)))
    return

self._scheduler.start(paused=paused)
def get_debug_flag() -> bool:
    """Get whether debug mode should be enabled for the app, indicated
    by the :envvar:`FLASK_DEBUG` environment variable. The default is
    ``True`` if :func:`.get_env` returns ``'development'``, or ``False``
    otherwise.
    """
    val = os.environ.get("FLASK_DEBUG")

    if not val:
        return get_env() == "development"

    return val.lower() not in ("0", "false", "no")

大致的意思就是判断当前 flask 如果配置了环境变量 FLASK_ENV 为 development 同时启动 flask 是 use_reload 为 False,就直接 return 不会继续执行后续的启动 scheduler 动作
看了下我本地的配置,刚好配置了环境变量 FLASK_ENV 为 development,并且启动 app 时 use_reload 为 False

解决方式就是 app 在启动的时候把 use_reload 改为 True 或者把环境变量 FLASK_ENV 改成除了 development 之外的其他值
但是之前为什么没有问题呢?
让同事本地跑了一下也确实没问题,scheduler 可以正常启动,怀疑的 flask_apscheduler 版本的问题,因为之前我本地升级过一波第三方库,对比了一下,同事的版本是 1.12.0,而我的是 1.12.4,1.12.0 版本的 start 方法里面确实没有上面那一段判断逻辑:

这个问题引发的思考:

没事插件不要乱升级!!!

共收到 9 条回复 时间 点赞

好像直接用 apscheduler 这个库还方便点,我也有在用,本身库封装得很好,拿过来就直接用了。

不过我遇到另一个问题,是多服务实例下的会出现多套定时任务,apscheduler 好像没有提供分布式下的解决方案,当时我自己解决就用了个分布式锁去做,但感觉不够保险。

王稀饭 回复

出现多套定时任务是指一个定时任务被执行多次吗

  • 现象:服务上线,出现多个 apscheduler object,每个 object 都会执行它的定时任务 —— 简单来说就是一个定时任务被执行多次,本质原因是存在多个 apscheduler 库的 object。
  • 原因:我们服务有多机房实例,而且上线时会有小流量灰度发布的流程,先在单机房部署一个服务实例,此时该实例产生一套定时任务;后面在其他机房部署服务实例,就又产生一套定时任务了。
  • 解法(不严谨不保险):redis 做个简单的分布式锁,生成 apscheduler object 时判断一下是否其他机房已经生成过,从而保证全局只有一套定时任务。但是存在分布式锁超时失效的问题,这样还是会存在多套定时任务。

多实例问题, 我的做法是: 定时任务不随 web 服务启动, 单独一个实例不启动 web 只启动定时任务. 代码可以放到同一个仓库, 只是不同角色启动不同的服务; 定时任务复用 web 的逻辑可以引用 flask 上下文环境

王稀饭 回复

那我理解还是因为存在多个 apscheduler 实例对象导致可能出现同时取到相同的定时任务,进而出现重复执行的问题对吧

Andy 回复

没听太明白,大佬可以放部分示例代码吗

yes,apscheduler 没提供有分布式多实例环境下的解法(至少我完整看了官网文档,也翻过源码,确实没发现),现在我们还是选择逐渐迁移到 celery 上去了

王稀饭 回复

celery 是我之前的方案,但是网上说 bug 比较多,自己实际在使用过程中也确实遇到过奇奇怪怪的问题,就放弃了

一开始选择 apscheduler 就是想轻量快速,搞着搞着我们发现不适合有坑,就只能回去大家都用的 celery 上,目前还没出现问题😅

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册