# 踩坑指南
这是一篇排错指南,源自博主近期用到APScheduler
较多,特此总结了一些常见的问题,希望对大家有帮助。
# 问题1: Can't Connect to MySQL的问题
如果用的是SQLAlchemy作为job的存储介质
,那如果参数没配置好的话,就会导致这个问题。
问题描述
在涉及到job数据读取/更新的时候,我们会调用
SQLAlchemy
的Session去完成相关操作。但偶尔会弹出Can't Connect to MySQL
或者Lose Connection的错误
。问题分析
MySQL对每个数据库连接是有个有效期的,默认应该是
8小时
,但这个是可以配置的。而SQLAlchemy的连接都是在连接池里面,而且有一个默认的回收时间。假如SQLAlchemy开辟的连接回收时间是2小时,但是MySQL配置的连接失效时间是1小时,那么就会出现一个问题:
SQLAlchemy认为连接仍然有效,但MySQL认为没效了,所以连接到了MySQL那边就
不认了
。就会出现以上错误了。解决方法
在jobstore处配置pool_recycle参数,这个参数要确保小于数据库的连接超时时间。
比如我配置25分钟,那么数据库超时时间就算只有30分钟,那我也能正常工作。
# 连接回收时间为1500秒
job_store = {
'default': SQLAlchemyJobStore(url="数据库jdbc连接地址", engine_options={"pool_recycle": 1500})
}
2
3
4
# 问题2 job莫名其妙被删除的问题
不得不说,这个设定很变态
,问题表象是: 你的服务跑着跑着,你的定时任务全不见了
。这个非常非常蛋疼。
问题描述
我这边的情况是,当job无法从数据库中反序列化到内存的时候,APScheduler会自动删除这样的job。
问题分析
注意看具体的日志,经过我的排查,发现APScheduler用的序列化方式是
pickle
,与JSON相比,是个咱们不太熟悉但又听过的序列化类库。但这个类库有个很严重的问题,比如我的用的python3.7,里面有个
pickle.DEFAULT_PROTOCOL
参数,在3.7的时候是3,而到Python3.9以后,这个值变成了4.APScheduler里面这个参数默认用的pickle.DEFAULT_PROTOCOL参数。
想象一下,你本地是Python3.9, 但服务器是3.7,那你添加job的时候可能是用的DEFAULT_PROTOCOL=4,但是服务器反序列化job的时候,拿到的就是3了,导致无法反序列化,最终删除job。
解决方法
指定序列化类型,最好知道你即将部署的机器的python版本,目前可以指定为2或者3。
job_store = {
'default': SQLAlchemyJobStore(url="数据库连接地址", pickle_protocol=3, engine_options={"pool_recycle": 1500})
2
# 问题3 用gunicorn或者uvicorn重复执行的问题
可以看一下我上一篇文章,用socket/分布式锁都可以解决。
# 问题4 分布式部署的问题
如上所说,不管是分布式部署
,还是多worker模式。APScheduler的支持都不是很友好。
因为它旨在给单个Python项目完成定时任务方案,而没有考虑集群等分布式问题。在单个python服务里面,他可以运行良好。我们来看一个例子:
服务器A 部署了web服务
服务器B 部署了web服务
2个服务一模一样,之所以部署2台机器,是为了
负载均衡
考虑。那么我此时调用新增job的方法,请求打到了服务器A,那会是什么过程呢:
- 服务器A接收数据,与本地的func联动并加载job到内存
- 讲加载后的内存持久化到数据库
但你要说服务器B的jobstore有这个任务吗?那当然是没有的
,因为它没有任何集群的概念。不存在服务器A接受命令,送达到服务器B的机制。
这最直观地导致,你在gunicorn/uvicorn开启N个worker,并调用add接口添加job,不会感知到job重复执行的问题。
而一旦你重启了服务,8个worker都从数据库加载了job到内存中,那你会很快遇到重复执行的任务
。
本文仅限于我对APScheduler的浅薄理解而写,如有不周之处还望指正。