随着公司业务的不断发展,我们的项目也在不断壮大。为了更好地管理和调度任务,我们决定集成Flask-APScheduler框架,此框架是基于APScheduler封装了一层,使得我们更容易的集成在Flask 框架上。不得不说这是一个强大的任务调度库,可以帮助我们更高效地完成各种定时任务和周期性任务。
公司业务需求驱动
公司业务的增长往往伴随着更多的任务和计划,例如定时生成报表、数据清理、邮件发送等。为了应对这些需求,我们需要一个可靠、灵活且易于集成的任务调度框架,于是我们选择了Flask-APScheduler。
Flask-APScheduler框架介绍
Flask-APScheduler是一个基于Python的任务调度库,支持多种触发器类型,如定时触发、间隔触发、日期触发等。它提供了简单而强大的API,使得任务的创建和管理变得十分便捷。同时,Flask-APScheduler还支持分布式任务调度,使其在大规模应用中表现出色。
如何使用Flask-APScheduler
安装Flask-APScheduler
首先,我们需要通过pip安装Flask-APScheduler:
p install Flask-APScheduler
持久化配置
在使用之前,如何我们需要将任务持久化存储,那就需要我们提前进行配置了
以下是最常用的配置,要特别注意下,如何不指定tablename的话,sqlalchemy会默认使用表名'scheduler_jobs'
SCHEDULER_TIMEZONE = 'Asia/Shanghai' # 配置时区
SCHEDULER_API_ENABLED = True # 调度器开关
# 配置mysql job存储位置
SCHEDULER_JOBSTORES = {
'default':SQLAlchemyJobStore(url=url, tablename='xxx_jobs')
}
SCHEDULER_JOB_DEFAULTS = {'coalesce': False, 'max_instances': 1}
创建任务
接下来,我们可以通过简单的代码示例来创建一个定时任务,在这里为了简单介绍如何使用,采用装饰器的方式创建任务,当然也可以采用其他更加灵活的方式;
以下代码就实现了一个简单的flask集成apschedule功能,分flask启动之后,配置的3个定时任务也会被持久化到mysql中去,然后达到规定时间就可以老老实实的执行了,然而理想很美好但现实却很残酷... ....
from flask import Flask
from flask_apscheduler import APScheduler
# 创建一个flask应用
app = Flask(__name__)
# 创建定时任务,将flask注册进apscheduler
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
# 创建定时任务
@scheduler.task(trigger='cron', day='*', hour='00', minute='00')
def job1():
print('每天晚上0点开始跑')
@scheduler.task(trigger='cron', day='*', hour='10', minute='00')
def job2():
print('每天早上10点开始跑')
@scheduler.task(trigger='cron', day='*', hour='20', minute='00')
def job3():
print('每天晚上8点开始跑')
app.run()
问题初现
1、job1在晚上12点没毛病,确实是正确的执行了
2、但job2在早上10点的时候,却没有如期执行,很奇怪,但mysql中这条任务记录却更新了下次执行时间,好了开始查看日志吧...
好好的为什么会报 pymysql.err.OperationalError / MySQL server has gone away (BrokenPipeError(32, 'Broken pipe')) 这种错误呢,这明显是和mysql的连接断开了啊。
问题很严重,如何不尽快解决的话,到时业务方投诉过来,微薄的工资又要被扣光了 ... ....
Error getting due jobs from job store 'default': (pymysql.err.OperationalError) (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")
[SQL: SELECT scheduler_jobs.id, scheduler_jobs.job_state
FROM scheduler_jobs
WHERE scheduler_jobs.next_run_time <= %(next_run_time_1)s ORDER BY scheduler_jobs.next_run_time]
[parameters: {'next_run_time_1': 1705484100.000265}]
(Background on this error at: https://sqlalche.me/e/14/e3q8)
Run time of job "job2 (trigger: cron[day='*', hour='10', minute='00', second='00'], next run at: 2024-01-18 10:00:00 CST)" was missed by 0:00:10.005574
问题排查
以下是gpt的回答,虽然不能帮你实际解决问题,但能给人提供思路,个人感觉还是非常nice的,以下标红处就是本人从gpt的回答中找到的解决方案
这个错误提示表明 Flask-APScheduler 在尝试从 MySQL 数据库中获取即将到期的任务时,遇到了数据库连接问题。错误信息 `"MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))"` 通常意味着与数据库的连接在等待操作时被关闭或中断了。这种情况可能由几个原因引起:
1. **MySQL服务器超时:** 如果一个连接在一定时间内没有活动,MySQL服务器可能会关闭该连接。这个超时时间由 `wait_timeout` 和 `interactive_timeout` 设置控制。
2. **网络问题:** 网络不稳定或配置不当也可能导致连接断开。
3. **MySQL服务器重启:** 如果MySQL服务在操作期间重启,现有的连接将会丢失。
4. **资源限制:** 服务器资源限制(如内存不足)也可能导致MySQL服务意外中断。
要解决这个问题,你可以尝试以下几个步骤:
### 增加MySQL服务器的超时时间
增加 `wait_timeout` 和 `interactive_timeout` 的值可以允许连接在不活动时保持更长时间的打开状态。你可以通过以下SQL命令来查看当前的设置,并适当增加它们的值:```sql
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';SET GLOBAL wait_timeout=28800;
SET GLOBAL interactive_timeout=28800;
```### 检查网络配置
确保服务器之间的网络连接是稳定的。如果你在云环境中运行,检查相关的网络策略和安全组设置。### 使用数据库连接池
使用连接池可以帮助管理数据库连接,防止连接超时。确保 Flask-APScheduler 使用的数据库连接池(通常是 SQLAlchemy)正确配置了连接回收时间。### 捕获并重试
在代码中捕获 `OperationalError` 异常,并尝试重新建立数据库连接或重试操作。这种方法可以提高应用的鲁棒性。### 服务器资源监控
监控MySQL服务器的资源使用情况,如内存和CPU使用率,确保服务器不会因资源不足而崩溃。### 日志记录
增加日志记录,以便更好地理解错误发生的背景和上下文。最后,针对 "Run time of job was missed" 的问题,这通常是因为当APScheduler尝试运行任务时,任务的执行时间已经过去。这可能是由于数据库连接问题导致调度程序无法及时从数据库中检索任务。解决数据库连接问题后,这个问题通常也会随之解决。同时,检查确保服务器时间同步和APScheduler的配置是否正确,以确保任务能够按预定计划执行。
问题找到了,就好解决了,由于我司项目采用的是将定时任务集成到mysql中,自然从这里入手了,查看sqlalchemy的配置,并没有设置连接回收时间
from sqlalchemy import create_engine
engine = create_engine(url)
解决方案
engine = create_engine(url,
# 连接池容量
pool_size=15,
# 上溢:超过连接池容量外最多创建的连接数,之后就不能创建新的连接了,只能等待。上溢建立的连接用完后会直接被连接池销毁
max_overflow=10,
# 放弃从池中获取连接之前等待的秒数(默认值为30秒), 池中没有可用连接时不立即抛异常,而是阻塞等待指定秒数(此处设为10秒),如果超过指定秒数仍然没有可用连接,才抛出异常
pool_timeout=10,
# 多少秒之后对池中的连接进行一次回收重置(默认值为-1,表示不回收重置),这对MySQL是必要的。对于mysql建立的连接,在8小时内都没有访问请求的话,mysql server将主动断开这条连接,后续在该连接上进行的查询操作都将抛出异常,为避免这种情况,需设置pool_recycle小于8小时(例如设为3600秒,即1小时)
pool_recycle=3600
)