我们采用 xxl-job 来作为我们的调度任务中心,而 xxl-job 采用的是 quartz 作为它的调度内核。
我们在部署的时候,采用了2台服务器集群的方式,有一天在分析一个工单的时候发现这2台服务器同时执行了某个时间段的调度2次,于是开始了这个问题查找分析过程。
首先我们找到了这篇文章:多个xxl-job-admin节点,任务重复执行问题,这篇文章描述了为什么会发生这个问题,以及该如何解决,但是这篇文章写得比较简短,还是不明白。
接着我们找到了这篇文章:记一次Quartz重复调度(任务重复执行)的问题排查,这篇文章的作者花了很多心思写这篇文章,写得很透彻。我们通过一边参照他的文章,一边看源代码,一边再进行不同阶段跟踪测试,证实了这篇文章所写得完全正确,也证实了他的解决办法是正确的。
下面就这个问题的出现原因再啰嗦几句,希望能更进一步的解惑:
下面这张图非常重要,我这边摘抄过来(链接地址没法用,所以我摘抄过来,希望作者理解一下这里用来说明问题):
-
上面图中出现的那几个Lock,都是基于数据库的分布式锁,这个分布式锁采用了数据库的for update行级锁的机制来实现。这个机制保证了在一开始的时候只有1个调度服务才能获取到调度任务。
-
上图中的第一个Lock,非常关键,这个Lock是出现重复调度的关键所在,但是 quartz 默认这个 Lock 是空的!!!这就会出现在上面文中所说的问题,2个调度服务同时发出请求,第1个非常快速的完成,会导致第2个也能在瞬间拿到。具体可以参见这个过程:
-
上图中的作者,有部分语言描述有丁点漏洞,上图中的“线程A”,“线程B”,会让读者认为只是在一个进程中会出现重复调度的问题,其实不然,应该改成“进程A”,”进程B”