剧照 | 《爱尔兰人》
简介
APScheduler是一个定时任务框架,其主要功能就是方便控制不同类型的定时任务,本身并没有考虑分布式多实例情况下的一些问题,本篇文章就来简单谈谈APScheduler在简单分布式场景下的使用。
分布式带来的问题
比如有个服务A,服务A中有使用APScheduler添加任务的逻辑,每次添加的任务会在随后固定的某个时间点被APScheduler调用执行。
在单节点情况下,这并没有什么问题,但随着业务加大,你可能要开启多个服务A来做负载时,此时APScheduler就会出现重复执行任务的问题。
为了方便说明,这里使用MongoDB作为APScheduler的jobstore,使用线程池作为它的执行器。(如果你不明白我在说啥,建议看看此前APScheduler的文章)
如果开启了多个服务A,服务A中都使用了相同配置的scheduler,此时就会出现任务重复执行的问题。
为何会有这个问题?一起来阅读一下相关源码,一探究竟。
因为使用了BlockingScheduler作为调度器,所以直接看到该类的代码
方法会构成主循环,其具体的执行逻辑在 方法中, 方法部分代码如下。
方法通过 获取jobstore中的任务对象,通过前面的配置可知,mongodb是这里的jobstore。
看到mongodb对应jobstore的代码。
getduejobs方法主要调用 方法去获取任务对象,要关注的重点是,它使用了lte以及时间戳作为参数,简单用过mongodb的朋友都知道lte其实就是小于等于的意思,简单而言,只要小于或等于timestamp这个时间戳的任务都会被获取。
都看到这了,顺便看一下 方法的代码吧。
到这里就很清楚APScheduler会出现重复执行任务问题的原因。
启动多个服务A,相当于运行同一份代码多次,此时APSCheduler的配置都是相同的,即多个APScheduler实例连接同一个mongodb,此时mongodb中存在一个任务就有可能被APScheduler消费多次。
使用分布式锁
要解决APScheduler多实例重复执行任务的问题,最常见的解决方案就是使用分布式锁,而分布式锁中最常见的就是基于Redis构建的字段锁。
Redis字段锁很容易理解,就是通过set命令在redis中设置一个字段,如果字段存在,则是加锁状态,而字段不存在,则是解锁状态。
设计Redis锁时,需要考虑操作原子性,避免同时去获取Redis字段的情况出现,还需要考虑字段超时,避免因逻辑错误出现的长时间死锁,所以设计Redis字段锁还是需要一些tick的,这里分享一种写法,如下。
通过上面方法设置的锁与常用的锁不同。
如果程序没有获得常用的锁,则会阻塞等待锁,而这里涉及的锁并不会等待,它的作用只是保证被锁方法在特定时间段内只执行一次。
此外还要考虑的是加锁位置,因为APScheduler会获取小于某个时间戳下的所有任务,那为了避免任务被重复执行,最直观的做法就是在任务函数中加上锁,例子如下。
结尾
至此分布式场景下使用APScheduler的方法就介绍完了,核心思想就是确保多个APScheduler实例对同一任务只会执行一次,感谢你的阅读。
如果文章对你有所帮助,点击「在看」支持二两,叩谢豪恩。
感谢创作者的好文
领取专属 10元无门槛券
私享最新 技术干货