flask-apscheduler 是一款 flask 的定时任务框架,其本质上是和 apscheduler 一样的,具体的使用操作和其他的 flask 组件一样。在开发环境上定时任务跑起来很顺利,但是到了生产环境用 Gunicorn 部署的时候出现了各种问题。

踩坑一:TimeZone offset does not match system offset

部署生产环境时,一上去就给我丢了一个大大的异常:“TimeZone offset does not match system offset”,大致意思是说我的运行的时区和系统时区不匹配。

读了下 flask-apscheduler 的源码发现,他会读取 SCHEDULER_TIMEZONE 这个值,作为当前运行的时区。

timezone = self.app.config.get('SCHEDULER_TIMEZONE')
if timezone:
options['timezone'] = timezone

心想这简单,我在配置文件里把这个时区配置上应该就完事了。

# config.py
SCHEDULER_TIMEZONE = "Asia/Shanghai"

配置了时区为“Asia/Shanghai”后,发现依然报这个错,既然运行环境的时区正确了,那会不会我生产环境的时区也有问题?

生产环境是 docker 容器,进入容器用 date 查看了他的当前时间,发现时间是和系统时间一致的,再查看 cat /etc/timezone 的时区,发现这里出了问题,显示的是 Etc/UTC,解决的思路是修改 Dockerfile,配置正确的时区,在 Dockerfile 中加入此行。

RUN echo "Asia/Shanghai" > /etc/timezone

修改之后重新运行,已经没有出现上述报错了,这个坑算是到这就排完了。

踩坑二:Flask-Apscheduler 多进程环境重复运行

排完第一个坑的时候项目可以启动运行了,这时当然要测试一下定时任务的分发是否正常,此时出现了下面的情况。

有三条任务是一模一样的,Flask-Apscheduler 的 add_job() 方法需要传一个 id 值,这个值代表了每一任务,且是不能重复的,不然会抛出

ConflictingIdError 异常,那为什么测试的时候会发出几条一模一样的任务?

其实这主要和生产环境使用 Gunicorn 部署有关,Gunicorn 可以指定一个 worker 参数,指的是开启的进程数,而每次开一个 worker,都会启动一个 scheduler,这就导致了这些定时任务是由不同的进程创建的。

这个问题网上的解决方法五花八门,不如利用阻塞某个 socket 端口,来实现进程的创建时只创建单一一个,或者给 Gunicorn 加 --preload 参数等等,这时还是看官方的 issue 保险,发现还真有,按其中的解决方式我尝试了几种。

配置环境变量以控制开启的 scheduler 数量

通过读取环境变量的方式,判断 SCHEDULER_LOCK 的值是否为 False,此时开启 scheduler,并且修改环境变量值。

if os.environ.get("SCHEDULER_LOCK") == "False":
scheduler.start()
os.environ["SCHEDULER_LOCK"] = "True"

我尝试了这种方法,显然并不行,通过环境变量共享多个进程的方式,是不太可取的。

通过全局锁,控制 scheduler 只运行一次

首次创建进程时,会创建一个 scheduler.lock 文件,并加上非阻塞互斥锁,此时 scheduler 可以成功开启,如果文件加锁失败抛出异常,则表示当前 scheduler 已经开启了,最后再注册一个退出事件,此时 flask 退出的话,就释放文件锁。

def register_scheduler():
"""
注册定时任务
"""
f = open("scheduler.lock", "wb")
# noinspection PyBroadException
try:
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
scheduler.start()
except:
pass def unlock():
fcntl.flock(f, fcntl.LOCK_UN)
f.close() atexit.register(unlock)

这个方法看似完美,其在运行开始时确实没有出现问题,但这还没完,且看下个坑。

踩坑三:集群环境下,Flask-Apscheduler 多进程环境重复运行

第二个问题确实可以通过文件锁的方式解决,但是我的生产环境是集群环境,也就是有多个应用实例,每个应用实例部署于 docker 容器之中,这样他们是互相隔离的,并通过 Nginx 做负载均衡。

此时再调用定时任务时,显然还会有问题,当定时任务调用接口打在同一个实例时,是没有问题的,当第二次调用接口时就可以抛出异常并捕获,但对于不同的实例,当第三次,接口请求打在了另外的实例上,此时按正常的业务逻辑,任务下发应该需要失败,但它依然成功了,也就是说上面通过文件全局锁的方式,并不适用于集群环境。

此时只能再次阅读其他 issue,确实也有人遇到了此类问题,但我看到作者的建议其实是拆分业务。

拆分业务确实给了我不少提示,但我的定时任务的功能不算是一个微服务,如果独立成一个 flask 实例也需要一点时间,此时我想到了这个问题的本质是由于多进程导致 Flask-Apscheduler 开启多个 scheduler 而造成的,那么我可以构建一个单进程的实例,并让特定的定时任务接口流量从这个集群的实例过就可以了。

修改 Gunicorn 的配置文件,通过获取 WORKERS_COUNT 这个环境变量来判断需要开启几个进程,并把进程数赋值给 workers。

if os.environ.get("WORKERS_COUNT"):
workers = int(os.environ.get("WORKERS_COUNT"))
else:
workers = multiprocessing.cpu_count() * 2 + 1

在 docker-compose.yml 配置文件中指定其中一个实例的进程数为单进程。

environment:
- WORKERS_COUNT=1

此时再通过 Nginx 的正则规则,将定时任务的接口路由到该实例就可以了,Nginx 的匹配标识符可以参考下表。

标识符 描述
= 精确匹配:用于标准uri前,要求请求字符串和uri严格匹配。如果匹配成功就停止匹配,立即执行该location里面的请求。
~ 正则匹配:用于正则uri前,表示uri里面包含正则,并且区分大小写。
~* 正则匹配:用于正则uri前,表示uri里面包含正则,不区分大小写。
^~ 非正则匹配;用于标准uri前,nginx服务器匹配到前缀最多的uri后就结束,该模式匹配成功后,不会使用正则匹配。
普通匹配(最长字符匹配);与location顺序无关,是按照匹配的长短来取匹配结果。若完全匹配,就停止匹配。

转自链接:https://ld246.com/article/1582878621206

Gunicorn 部署 Flask-Apscheduler 重复执行问题的更多相关文章

  1. 测试平台系列(82) 解决APScheduler重复执行的问题

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上一节我们编写了在线执行R ...

  2. gunicorn部署Flask服务

    作为一个Python选手,工作中需要的一些服务接口一般会用Flask来开发. Flask非常容易上手,它自带的app.run(host="0.0.0.0", port=7001)用 ...

  3. 使用gunicorn部署Flask项目

    [*] 本文出处:http://b1u3buf4.xyz/ [*] 本文作者:B1u3Buf4 [*] 本文授权:禁止转载 从自己的博客移动过来. gunicorn是一个python Wsgi的WEB ...

  4. nginx gunicorn 部署flask,带参数链接不可用的现象(笔记)

    微信小程序后台,开启 gunicorn之后屏幕会输出打印结果,一旦关闭shell 带参数链接不可用,只有开启shell才能使用, 一针见血 : 注释掉所有print语句,关闭shell 带参数的链接  ...

  5. 腾讯云Unubtu 16.04 (gunicorn+supervisor+ngnix+mongodb)部署Flask应用

    1.申请腾讯云服务 我申请了免费使用的云服务器 ,选择安装的Linux版本是ubuntu16.04.1 LTSx86_64.我个人PC安装使用的也是这个版本,比较熟悉些. 详细参考帮助文档. 2.登录 ...

  6. flask +gevent+nginx+Gunicorn+supervisor部署flask应用

    上篇   可以完美部署flask ,但是视乎在结合gevent+apscheduler 实现异步非阻塞后台和定时任务的时候视乎不是那么完美.请教了前辈,决定使用flask+gevent+nginx+g ...

  7. CentOS7部署Flask+Gunicorn+Nginx+Supervisor

    1. Git客户端 Win10安装git for windows 1.1 设置Git全局参数 打开Git Bash $ git config --global user.name "Alic ...

  8. Gunicorn+Flask中重复启动后台线程问题

    假设程序如下: if __name__ == '__main__': t = Thread(target=test) t.start() app.run(host='0.0.0.0',port=808 ...

  9. CentOS 下部署Nginx+Gunicorn+Supervisor部署Flask项目

    原本之前有一部分东西是在Windows Server,但是由于Gunicorn不支持Windows部署起来颇为麻烦.最近转战CentOS,折腾一段时间,终于简单部署成功.CentOS新手,作为一个总结 ...

  10. 如何使用Nginx和uWSGI或Gunicorn在Ubuntu上部署Flask Web应用

    你好!欢迎阅读我的博文,你可以跳转到我的个人博客网站,会有更好的排版效果和功能. 此外,本篇博文为本人Pushy原创,如需转载请注明出处:https://pushy.site/posts/151981 ...

随机推荐

  1. Java Web专题攻关

    servlet概念 servlet其实就是运行在服务器的一个小程序 如何去理解呢?我们访问服务器的资源包括静态资源和动态资源,其中静态资源是我们放置的模板,CSS.JS等文件,是不变的.而我们访问的动 ...

  2. 网络安全公开数据集Maple-IDS,恶意流量检测数据集开放使用!

    Maple Dataset 枫叶数据集Maple Dataset枫叶数据集由东北林业大学网络安全实验室(https://maple.nefu.edu.cn/lab/)公开发布,是用于入侵检测评估的数据 ...

  3. java-GUI编程之布局类型介绍

    java使用AWT和Swing相关的类可以完成图形化界面编程,其中AWT的全称是抽象窗口工具集(Abstract Window Toolkit),它是sun公司最早提供的GUI库,这个GUI库提供了一 ...

  4. 2024 秋季PAT认证甲级(题解A1-A4)

    2024 秋季PAT认证甲级(题解A-D) 写在前面 这一次PAT甲级应该是最近几次最简单的一次了,3个小时的比赛差不多30分钟就ak了(也是拿下了整场比赛的rk1),下面是题解报告,每个题目差不多都 ...

  5. python脚本之requests库上传文件

    一.前言 在学习的时候,发现有一个AWD的文件上传执行漏洞,突然想着批量对不同靶机进行操作并get_flag.思路简单,但是没构造 过文件上传的requests 的post请求的payload.便记录 ...

  6. 系统编程-文件IO-dup和dup2系统调用

    在linux下,一切皆文件. 文件描述符用于操作文件. 从shell中运行一个进程,默认会有3个文件描述符存在(0.1.2):)0表示标准输入,1表示标准输出,2表示标准错误. 一个进程当前有哪些打开 ...

  7. [TK] 理想的正方形

    题目描述 有一个整数组成的矩阵,现请你从中找出一个指定边长的正方形区域,使得该区域所有数中的最大值和最小值的差最小. 题目分析 其实这道题和滑动窗口很像,而滑动窗口使用优先队列解决. 我们都知道优先队 ...

  8. [Tkey] OSU!

    更新的题解可看 此处 你说得对但是 恐怖日本病毒会自动向你的电脑中下载 OSU! 题意简述 一个 01 串,每个位置有 \(p_{i}\) 的概率为 \(1\),连续的 \(x\) 个 \(1\) 贡 ...

  9. find_sys_call_table和kallsysms_lookup_name的区别

    find_sys_call_table 和 kallsyms_lookup_name 都可以用于查找内核符号,但它们的具体作用和使用场景有所不同.以下是两者的详细对比: 1. find_sys_cal ...

  10. CF1659 Codeforces Round #782 (Div. 2) 题解

    之前说过的题解,E应该不会补了(大概) A Red Versus Blue 题意非常简单,构造题.给定\(r\)个红色气球和\(b\)个蓝色气球,将它们排成一排,要求使得连续出现的最多的同色气球最少, ...