最新公告
  • 欢迎您光临欧资源网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们
  • 模式存储Job框架的基础概念与基础(一)

    1. 简介

    公司前期切换到quartz进行任务调度,日调度量超过200万次。随着调度量的增加,重复作业调度的情况突然开始出现,没有规律可循。网上没有明确的解决办法,于是开始调试Quartz源码,终于找到了问题所在。

    如果没有耐心看源码分析,可以直接拉到文末,有直接简单的解决方法。注意:本文使用的quartz版本为2.3.0,使用JDBC方式存储jobs。

    2. 准备

    首先,由于本文是代码级分析文章,需要提前了解Quartz的用途和用法。网上还是有很多不错的文章,大家可以提前了解。

    其次,除了用法,我们还需要了解Quartz框架的一些基本概念:

    1)Quartz 调用触发作业触发。TRIGGER_STATE 为当前触发状态,PREV_FIRE_TIME 为上一次触发时间,NEXT_FIRE_TIME 为下一次触发时间,misfire 是指作业会在某个时间触发,但由于某种原因没有触发的情况。

    2)Quartz在运行的时候会启动两种线程(两种以上),一种是用于调度作业的调度线程(单线程),另一种是用于执行具体任务的工作池工作的业务。

    3)Quartz自带的表格中,本文主要涉及以下三个表格:

    触发器表。触发器表记录了触发器的 PREV_FIRE_TIME(上次触发时间)、NEXT_FIRE_TIME(下次触发时间)和 TRIGGER_STATE(当前状态)。尽管并不详尽,但这些是本文中唯一使用的。

    锁表。Quartz 支持分布式,即会有多个线程同时抢占同一个资源,Quartz 就是靠这张表来处理这种情况的。至于怎么做,见3.1。

    fired_triggers 表记录了被触发的触发器。

    4)TRIGGER_STATE,即触发器的状态,主要包括以下几类:

    触发器的初始状态是WAITING,处于WAITING状态的触发器正在等待被触发。调度线程会不断的扫描triggers表,根据NEXT_FIRE_TIME提前拉取要触发的触发器。如果触发器被调度线程拉动,它的状态将变为 ACQUIRED。

    因为触发器是提前拉的,还没有达到触发器的真正触发时间,所以调度线程会一直等到真正的触发时间,然后将触发状态从ACQUIRED变为EXECUTING。

    如果触发器不再执行,则将状态更改为 COMPLETE,否则为 WAITING,并开始新的循环。如果循环的任何部分抛出异常,触发器的状态将变为 ERROR。如果手动暂停触发器,状态将变为 PAUSED。

    3. 开始调查

    3.1 分布式状态下的数据访问

    如前所述,触发器的状态存储在数据库中,而 Quartz 支持分布式,所以如果启动了多个 Quartz 服务,就会有多个调度线程争先恐后地触发同一个触发器。MySQL默认执行select语句,不加锁。如果多个调度线程同时抓取同一个触发器,会不会导致触发器被重复调度?让我们看看 Quartz 是如何解决这个问题的。

    首先,我们来看看类的方法:

    图3-1 executeInNonManagedTXLock方法的具体实现

    官方对该方法的介绍:

    也就是说,传入的回调方法携带指定的锁,并在执行过程中打开事务。该注释还提到 lockName 是指定锁的名称。如果 lockName 为空,则执行回调方法。不受锁保护,但仍在事务中。

    这意味着,通过使用该方法,我们不仅可以保证事务,还可以选择性地保证回调方法的线程安全。

    接下来我们看一下 中的方法,也就是抢锁的过程。该方法在接口中定义。该接口通过锁定线程或资源来保护资源不被其他线程修改。由于我们的调度信息存在于数据库中,现在查看该方法的具体实现方式:

    图3-2 gainLock方法的具体实现

    我们通过调试和这两个变量来查看:

    图 3-3 扩展 SQL 和扩展插入 SQL 的详细信息

    如图 3-3 所示,该方法通过 locks 表中的行锁(由 lockName 确定)来保证回调方法的事务和线程安全。拿到锁后,方法会写。当然,它会在它发生时被删除。

    总而言之,该方法保证了在分布式情况下,只有一个线程可以同时执行该方法。

    3.2quartz调度过程

    图 3-4 Quartz 调度时序图

    sql自动插入时间_sql 一张表插入 另一张也插入_sql 触发器 在插入前 改变值

    是调度线程的具体实现。图 3-4 是这个线程方法的主要内容。图中只提到了正常情况,也就是流程没有异常时的处理流程。从图中可以看出,调度过程主要分为以下三个步骤:

    1)拉动要触发的触发器:

    调度线程会一次拉取一定时间窗口内、一定距离内即将触发的触发信息。那么,如何确定时间窗口和数量信息,我们来看看以下参数:

    :默认30s,可以通过配置属性设置。

    :获取可用(空闲)工作线程的数量,总是大于1,因为这个方法会阻塞,直到有工作线程空闲。

    : 一次触发的最大触发数,默认为1,可改写

    :时间窗调整参数,默认为0,可改写

    :在此时间之后未触发的触发器被认为是未触发。默认为60s,可设置。

    调度线程将一次触发 NEXT_FIRE_TIME 小于 ( ) 和大于 ( ) 的触发器。默认情况下,它会扣动一个在接下来的 30 秒和过去 60 秒内未触发的触发器。然后将这些触发器的状态从 WAITING 更改为 ACQUIRED 并将它们插入到fired_triggers 表中。

    2)触发器触发器:

    首先,我们将检查每个触发器的状态是否为 ACQUIRED。如果是,将状态更改为EXECUTING,然后更新触发器的NEXT_FIRE_TIME。如果触发器的NEXT_FIRE_TIME为空,即以后不会触发,则改变其状态。完成。如果触发器不允许并发执行(即标记了Job的实现类),则将状态更改为BLOCKED,否则将状态更改为WAITING。

    3)包触发并扔到工作线程池:

    遍历触发器。如果其中一个触发器在第二步出现错误,即返回值有异常或为null,则会修改triggers表和fired_triggers表的内容,跳过这个触发器,继续检查下一个。否则根据触发信息实例化(实现Thread接口),根据实例化,然后我们把实例扔到工作行中。

    在方法中,Quartz 会在执行前后通知绑定的监听器。如果在执行过程中抛出异常,则执行结果会保存异常信息,否则,如果没有抛出异常sql 触发器 在插入前 改变值,则为null。然后根据不同得到不同的执行指令。

    将触发器信息、作业信息和执行指令传递给方法,完成最终的数据表更新操作。例如,如果在作业执行过程中抛出异常,则将触发器状态更改为ERROR,如果是BLOCKED状态,则将其更改为WAITING等,最后从fired_triggers表中删除已经执行的触发器。请注意,这些是在工作线程池中异步完成的。

    3.3 故障排除

    在上一篇文章中,我们可以看到 Quartz 的调度过程中有 3 种(可选的)锁定行为。为什么叫可选?因为这三个步骤都在方法的保护下,所以方法可以通过设置传入参数lockName为空来取消锁。查看代码时,我们看到第一步是拉动要触发的触发器:

    加锁前对lockName进行判断,不像其他加锁方式,默认传入的是LOCK_TRIGGER_ACCESS:

    通过调试查到的值是,从而导致传入的lockName为空。我在代码中添加了日志以更清楚地查看该过程。

    图 3-5 调度日志

    从图 3-5 可以清楚地看出,当拉动要触发的扳机时,默认是不锁定的。如果这个默认配置有问题,会不会出现频繁的重复调度问题?事实上,事实并非如此。原因是 Quartz 默认采用乐观锁,允许多个线程同时拉同一个触发器。让我们看看 Quartz 在调度过程的第二步触发 fire trigger 时做了什么。注意此时是锁定的:

    如果调度线程发现当前触发器状态不是ACQUIRED,即触发器被其他线程触发,则返回null。在3.2中,我们提到在第三步调度过程中,如果发现一个触发器第二步的返回值为null,则跳过第三步,取消fire。一般情况下,乐观锁可以保证不会出现重复调度,但是ABA问题难免会出现。我们看一下重复调度发生时的日志:

    图3-5 重复调度日志

    在第一步中,Quartz 在拉动符合条件的触发器并将其状态从 WAITING 更改为 ACQUIRED 之间暂停了超过 9 毫秒,而另一台服务器正在利用这 9 毫秒的空闲时间。该文件完成了WAITING–>ACQUIRED–>EXECUTING–>WAITING的整个过程(即一个完整的状态变化周期)。图表见图 3-6。

    图3-6 重复调度原因示意图

    3.4 解决方案

    如何解决这个问题呢?将其添加到配置文件中,使得调度过程的第一步,即拉取待触发的触发器时处于锁定状态,即不会出现多个线程同时拉取同一个触发器的情况同时。也避免了重复调度的危险。

    3.5 经验

    调查过程并非一帆风顺。在经历了一些坑之后,也有一些非技术相关的经验:

    1)学习是一种需要不断打磨和修正的能力。就我个人而言,为了学习Quartz,刚开始翻看一个2.4MB大小的源码,一头雾水,效率低下,于是立即换方向,先了解下运行模式这个框架和它在做什么。,有哪些模块,怎么做,然后找到主线,阅读相关源码。用了一遍又一遍,遇到问题再翻一遍之前没看过的源码,就变得越来越流畅了。

    之前也听过其他同事的学习方法,但感觉并不完全适合我。可能每个人的状态和经历不同,学习方法也略有不同。在正常的学习中,你需要感受自己的学习效率,参考建议,尝试,感受效果,提高,你就会越来越清楚什么是适合自己的。非常感谢这里的师父用一句话帮我理顺了调度过程,让我看源码不会那么难了。

    2)质疑“经验”和“想当然”,习惯性的思维会让你蒙蔽双眼。在大规模代码中很容易被习惯所迷惑。刚开始看到加锁的方法时,觉得这个加锁技术很棒。这种方法是为了解决并发问题,并且“应该”被锁定。,如果是加锁的,就不会有并发问题。怎么可能和数据库的交互被锁了好几次,突然有一段时间没锁了?直到看到触发方法被触发,才觉得不对劲sql 触发器 在插入前 改变值,把日志写了下来,才发现居然解锁了。

    3)日志很重要。虽然我们可以调试,但是没有日志,我们无法发现并证明程序有ABA问题。

    4)最重要的是,不要害怕问题。即使使用像 Quartz 这样的大型框架,解决问题也不一定需要阅读 2.4MB 源代码。只要有时间,问题就可以解决,但好的技能可以缩短这个时间,我们需要在实战中磨练自己的技能。

    最近采访了BAT,整理了一份采访资料《Java采访BATJ通关手册》,内容涵盖Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等。

    PS:因为公众号平台改变了推送规则,如果不想错过内容,看完记得点“看”,加个“星”,这样每一篇新文章推送都会出现您的订阅列表第一次。里面。

    站内大部分资源收集于网络,若侵犯了您的合法权益,请联系我们删除!
    欧资源网 » 模式存储Job框架的基础概念与基础(一)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    欧资源网
    一个高级程序员模板开发平台

    发表评论