zoukankan      html  css  js  c++  java
  • 分布式服务下Quartz任务变为EREOR分析及解决

    一、前言

    在项目中遇到这样的一个问题:

    服务spring-cloud-quartz-one中有一个Quartz任务:MyJob

    服务spring-cloud-quartz-two中有两个Quartz任务:MyJob、MyJob2

    当第一个服务开启MyJob任务,第二个服务开启MyJob2任务。结果是MyJob2任务总是不生效,但是MyJob是生效了的。在spring-cloud-quartz-two中,是没有任何报错信息的,但是在spring-cloud-quartz-one中有个报错:

    2020-12-13 09:56:58.267 ERROR 10432 --- [SchedulerThread] o.s.s.q.LocalDataSourceJobStore          : Error retrieving job, setting trigger state to ERROR.
    
    org.quartz.JobPersistenceException: Couldn't retrieve job because a required class was not found: com.xwj.quartz.job.MyJob2
        at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveJob(JobStoreSupport.java:1393) ~[quartz-2.3.0.jar:?]
        at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTrigger(JobStoreSupport.java:2864) [quartz-2.3.0.jar:?]
        at org.quartz.impl.jdbcjobstore.JobStoreSupport$41.execute(JobStoreSupport.java:2805) [quartz-2.3.0.jar:?]
        at org.quartz.impl.jdbcjobstore.JobStoreSupport$41.execute(JobStoreSupport.java:2803) [quartz-2.3.0.jar:?]
        at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3849) [quartz-2.3.0.jar:?]
        at org.quartz.impl.jdbcjobstore.JobStoreSupport.acquireNextTriggers(JobStoreSupport.java:2802) [quartz-2.3.0.jar:?]
        at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:287) [quartz-2.3.0.jar:?]
    Caused by: java.lang.ClassNotFoundException: com.xwj.quartz.job.MyJob2
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[?:1.8.0_172]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[?:1.8.0_172]
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) ~[?:1.8.0_172]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[?:1.8.0_172]
        at java.lang.Class.forName0(Native Method) ~[?:1.8.0_172]
        at java.lang.Class.forName(Class.java:348) ~[?:1.8.0_172]
        at org.springframework.util.ClassUtils.forName(ClassUtils.java:275) ~[spring-core-5.1.2.RELEASE.jar:5.1.2.RELEASE]
        at org.springframework.scheduling.quartz.ResourceLoaderClassLoadHelper.loadClass(ResourceLoaderClassLoadHelper.java:81) ~[spring-context-support-5.1.2.RELEASE.jar:5.1.2.RELEASE]
        at org.springframework.scheduling.quartz.ResourceLoaderClassLoadHelper.loadClass(ResourceLoaderClassLoadHelper.java:86) ~[spring-context-support-5.1.2.RELEASE.jar:5.1.2.RELEASE]
        at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.selectJobDetail(StdJDBCDelegate.java:852) ~[quartz-2.3.0.jar:?]
        at org.quartz.impl.jdbcjobstore.JobStoreSupport.retrieveJob(JobStoreSupport.java:1390) ~[quartz-2.3.0.jar:?]
        ... 6 more

    二、问题分析:

    在上面的错误日志中,有两点很重要的信息:

    Error retrieving job, setting trigger state to ERROR.

    意思就是说,找不到这个job,将触发器状态改为ERROR。查看 qrtz_triggers 表,发现MyJob2对应的任务状态确实变为ERROR了(也就是任务执行失败了)

    查阅资料会发现Trigger的state有这样几种值:

    WAITING:等待

    PAUSED:暂停

    ACQUIRED:正常执行

    BLOCKED:阻塞

    ERROR:错误

    Couldn't retrieve job because a required class was not found: com.xwj.quartz.job.MyJob2

    意思就是说,没有找到MyJob2对应的类。这就很奇怪了,spring-cloud-quartz-one服务中不应该会执行MyJob2任务啊。可以猜到,当两个服务共用一个Quartz库时,应该是需要有一种隔离机制的,将不同的服务的任务隔离开。那如何隔离呢?

    分别将两个服务的日志级别调低一点:

    logging:
      level:
        jdbc: off
        jdbc.sqltiming: error #记录sql执行的时间
        com.xwj: debug

    重新启动下服务,然后分别开启MyJob和MyJob2任务。在spring-cloud-quartz-one服务中可以看到Quartz日志:

    2020-12-13 10:47:37.867  INFO 9900 --- [_ClusterManager] j.sqltiming  : SELECT * FROM QRTZ_SCHEDULER_STATE WHERE SCHED_NAME = 'instance_one' 
     {executed in 1 msec}
    2020-12-13 10:47:37.868  INFO 9900 --- [_ClusterManager] j.sqltiming  : UPDATE QRTZ_SCHEDULER_STATE SET LAST_CHECKIN_TIME = 1607827657867 WHERE SCHED_NAME = 'instance_one' 
    AND INSTANCE_NAME = 'instance_id_one' 
     {executed in 0 msec}
    2020-12-13 10:47:39.237  INFO 9900 --- [SchedulerThread] j.sqltiming  : SELECT TRIGGER_NAME, TRIGGER_GROUP, NEXT_FIRE_TIME, PRIORITY FROM QRTZ_TRIGGERS WHERE SCHED_NAME 
    = 'instance_one' AND TRIGGER_STATE = 'WAITING' AND NEXT_FIRE_TIME <= 1607827664235 AND (MISFIRE_INSTR = -1 OR (MISFIRE_INSTR != -1 AND NEXT_FIRE_TIME >= 1607827599236)) ORDER BY NEXT_FIRE_TIME 
    ASC, PRIORITY DESC 
     {executed in 1 msec}
    2020-12-13 10:47:39.239  INFO 9900 --- [SchedulerThread] j.sqltiming  : SELECT * FROM QRTZ_TRIGGERS WHERE SCHED_NAME = 'instance_one' AND TRIGGER_NAME = '456' AND 
    TRIGGER_GROUP = 'MyJob2' 
     {executed in 1 msec}
    2020-12-13 10:47:39.241  INFO 9900 --- [SchedulerThread] j.sqltiming  : SELECT * FROM QRTZ_CRON_TRIGGERS WHERE SCHED_NAME = 'instance_one' AND TRIGGER_NAME = '456' 
    AND TRIGGER_GROUP = 'MyJob2' 
     {executed in 1 msec}
    2020-12-13 10:47:39.243  INFO 9900 --- [SchedulerThread] j.sqltiming  : SELECT * FROM QRTZ_JOB_DETAILS WHERE SCHED_NAME = 'instance_one' AND JOB_NAME = 'JOB_456' AND 
    JOB_GROUP = 'MyJob2' 
     {executed in 1 msec}
    2020-12-13 10:47:39.245 ERROR 9900 --- [SchedulerThread] o.s.s.q.LocalDataSourceJobStore : Error retrieving job, setting trigger state to ERROR.

    通过上面的日志可以看到,可以看到Quartz任务的执行步骤为:

    1、通过 SCHED_NAME 查询 QRTZ_SCHEDULER_STATE ,并更新 LAST_CHECKIN_TIME 字段

    2、通过 SCHED_NAME 查询 QRTZ_TRIGGERS 表中当前可以执行的所有任务(主要是返回TRIGGER_NAME, TRIGGER_GROUP)

    3、通过上一步返回的信息,然后加上 SCHED_NAME 查询出具体的触发器详细信息

    4、使用和第3步中一样的查询条件,查询 QRTZ_CRON_TRIGGERS 表,获取定时任务CRON_EXPRESSION

    5、使用和第3步中一样的查询条件,查询 QRTZ_JOB_DETAILS 表,获取任务详细信息(包括JOB_CLASS_NAME)

    当执行到第5步时,结果返回了MyJob2,然而spring-cloud-quartz-one服务中并没有类MyJob2,所以MyJob2任务一定会执行失败,将状态更新为ERROR了。

    问题的原因终于找到了,但是如何解决呢?

    三、解决问题:

    其实不难发现,在Quartz所在服务启动时,会往 qrtz_scheduler_state 表插入一条数据,如下:

    其中 SCHED_NAMEINSTANCE_NAME 分别是quartz.properties配置文件中的 instanceNameinstanceId。并且Quartz在寻找当前可执行任务时,全部都会带上SCHED_NAME,所以只要将不同服务的SCHED_NAME设置为不一样,也就是将配置文件中 instanceName 设置为与其他服务不一样就行(instanceId最好也设置为不一样,设置为AUTO自动生成一个id也行)。

    修改配置如下:

    org.quartz.scheduler.instanceName = instance_two
    org.quartz.scheduler.instanceId = instance_id_two

    再次查看 qrtz_scheduler_state 表,会发现表中多了一条数据:

    此时,不同的服务开启任务后,就会通过 SCHED_NAME 隔离开了。

    四、遇到的坑

    如果是使用SpringBoot1.5 + org.quartz-scheduler,在配置文件中,设置 instanceName 是不生效的(也不知道为啥),默认是使用 schedulerFactoryBean 作为SCHED_NAME。如果想自己配置 SCHED_NAME,可以在创建SchedulerFactoryBean时,对 schedulerName 手动赋值,如下:

        @Bean
        public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            // 设置SCHED_NAME
            schedulerFactoryBean.setSchedulerName("instance_two");
            // 将spring管理job自定义工厂交由调度器维护
            schedulerFactoryBean.setJobFactory(jobFactory);
            // 设置覆盖已存在的任务
            schedulerFactoryBean.setOverwriteExistingJobs(true);
            // 项目启动完成后,等待2秒后开始执行调度器初始化
            schedulerFactoryBean.setStartupDelay(2);
            // 设置调度器自动运行
            schedulerFactoryBean.setAutoStartup(true);
            // 设置数据源,使用与项目统一数据源
            schedulerFactoryBean.setDataSource(dataSource);
            // 设置上下文spring bean name
            schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
            // 设置配置文件位置
            schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
            return schedulerFactoryBean;
        }
  • 相关阅读:
    nullnullConnecting with WiFi Direct 与WiFi直接连接
    nullnullUsing WiFi Direct for Service Discovery 直接使用WiFi服务发现
    nullnullSetting Up the Loader 设置装载机
    nullnullDefining and Launching the Query 定义和启动查询
    nullnullHandling the Results 处理结果
    装置输出喷泉装置(贪心问题)
    数据状态什么是事务?
    停止方法iOS CGD 任务开始与结束
    盘文件云存储——金山快盘
    函数标识符解决jQuery与其他库冲突的方法
  • 原文地址:https://www.cnblogs.com/xuwenjin/p/14121077.html
Copyright © 2011-2022 走看看