记一次Quartz状态ERROR的排错(服务多实例下一定要保证最新代码)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jinzhencs/article/details/75058920

问题

服务:jobmgr(任务调度模块)
目前有三种job

  1. HttpPing拨测任务,定时get一下url,监控服务是否存活(多节点则域名解析成ip)
  2. 周期性聚合产生报告/告警(每1小时查询数据看是达到阈值触发告警)
  3. 告警恢复任务

现象:
告警恢复任务是昨晚上新加的job, 同时影响周期告警(原逻辑add:周期告警没出错后产生一个恢复job),
完了之后部署到开发环境,报错。代码有些BUG,修改之后,本地调试(开发环境停止),发现拨测任务正常运行, 周期告警全部无动静,并且没有任何异常抛出。


找问题

查看运行中的job:

 @Override
    public List<JobView> listJobs() throws JobSchedueException { // TODO: 记录数太多?
        List<JobView> jobViews = new ArrayList<>();
        try {
            for (String groupName : sched.getJobGroupNames()) {
                for (JobKey jobKey : sched.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
                    String jobName = jobKey.getName();
                    String jobGroup = jobKey.getGroup();

                    // name and group
                    JobDetail jobDetail = new JobDetail();
                    jobDetail.setName(jobName);
                    jobDetail.setGroup(jobGroup);
                    JobView jobView = new JobView();
                    jobView.setJobDetail(jobDetail);

                    // params
                    org.quartz.JobDetail qJobDetail = sched.getJobDetail(jobKey);
                    if (null != qJobDetail.getJobDataMap()) {
                        Map<String, Object> params = new HashMap<>();
                        params.putAll(qJobDetail.getJobDataMap());
                        jobDetail.setParams(params);
                    }
                    List<Trigger> triggers = (List<Trigger>) sched.getTriggersOfJob(jobKey);
                    for (Trigger trigger : triggers) { // 应该只有一个
                        trigger.getNextFireTime();
                        if (trigger instanceof CronTrigger) {
                            CronTrigger cronTrigger = (CronTrigger) trigger;
                            String cronExpr = cronTrigger.getCronExpression();
                            jobDetail.setCronExpression(cronExpr);
                        }
                        jobView.setNextFireTime(trigger.getNextFireTime());
                        Trigger.TriggerState state = sched.getTriggerState(trigger.getKey());
                        jobView.setStatus(state.name());
                    }
                    jobViews.add(jobView);
                }
            }
        } catch (Exception e) {
            throw new JobSchedueException(e);
        }
        return jobViews;
    }

public class JobView {
    private JobDetail jobDetail;
    private String status;
    private Date nextFireTime;
}

public class JobDetail {
    @NotBlank(message="任务名称不能为空")
    private String name;
    @NotBlank(message="任务组不能为空")
    private String group;
    @NotBlank(message="任务执行表达式不能为空")
    private String cronExpression;
    // 如果由于暂停服务,上次执行时间过长等原因造成未触发,是否触发一次,
    // 对于告警策略不需要,对于归档,日报等任务可以设置为true
    private boolean misFireTriggerOnce;
    @NotBlank(message="任务代码uuid不能为空")
    private String jobTemplateUuid;
    private Map<String, Object> params;
}

Get http://localhost:8080/internal-api/v2/job/list

response:

{
    "code": 200,
    "message": "OK",
    "data": [
        {
            "jobDetail": {
                "name": "interface_access1499307485790",
                "group": "alert.period",
                "cronExpression": "* 0/10 * * * ?",
                "misFireTriggerOnce": false,
                "jobTemplateUuid": null,
                "params": {
                    "uuid": "891c9511-9fc0-40e7-9a13-0cecbc50f4ad"
                }
            },
            "status": "ERROR",
            "nextFireTime": 1499856601000
        },
        {
            "jobDetail": {
                "name": "mcc_api_perf_test1499490623431",
                "group": "alert.period",
                "cronExpression": "0 0/3 * * * ?",
                "misFireTriggerOnce": false,
                "jobTemplateUuid": null,
                "params": {
                    "uuid": "fc41e5df-fca9-4e1e-b570-5acae82d051e"
                }
            },
            "status": "ERROR",
            "nextFireTime": 1499856840000
        },
        {
            "jobDetail": {
                "name": "mcc_test_11499690825160",
                "group": "alert.period",
                "cronExpression": "0 0/5 * * * ?",
                "misFireTriggerOnce": false,
                "jobTemplateUuid": null,
                "params": {
                    "uuid": "da22f81d-39ec-4e9d-ab71-7258f92944c9"
                }
            },
            "status": "ERROR",
            "nextFireTime": 1499856900000
        },
        {
            "jobDetail": {
                "name": "alert1499692566563",
                "group": "alert.period",
                "cronExpression": "0 0/5 * * * ?",
                "misFireTriggerOnce": false,
                "jobTemplateUuid": null,
                "params": {
                    "uuid": "7eb35760-5844-438f-9b2a-014f91376904"
                }
            },
            "status": "ERROR",
            "nextFireTime": 1499856900000
        },
        {
            "jobDetail": {
                "name": "mcc_api_perf_test1499490623314",
                "group": "alert.period",
                "cronExpression": "0 0/3 * * * ?",
                "misFireTriggerOnce": false,
                "jobTemplateUuid": null,
                "params": {
                    "uuid": "5e3d90a9-5a3d-4a10-9d08-f3458d499256"
                }
            },
            "status": "ERROR",
            "nextFireTime": 1499856840000
        }
    ]
}

发现问题,很多job(周期告警的job)状态为ERROR,显然是因为昨晚上BUG代码导致ERROR的,于是网上查了下,QRTZ_TRIGGERS这张表存放了任务的具体执行状态,可以看到,里面的周期告警任务全是ERROR
这里写图片描述

原因清楚了,但是我现在代码正确了,还是不能正常运行,说明ERROR状态不会自动恢复,并且Quartz应该是直接忽略掉了ERROR的job。


解决

http://blog.csdn.net/taizhenba/article/details/51719776
很简单,resume恢复任务即可。我们jobmgr模块pause、resume的接口都是有的,调用即搞定!

trigger各状态说明:

  • None:Trigger已经完成,且不会在执行,或者找不到该触发器,或者Trigger已经被删除
  • NORMAL:正常状态
  • PAUSED:暂停状态
  • COMPLETE:触发器完成,但是任务可能还正在执行中
  • BLOCKED:线程阻塞状态

总结

http://blog.csdn.net/u014419512/article/details/24364117
找问题过程中看到这哥们的博客,顺便提一下,一定要注意,在服务多实例的情况下,务必一定要保证代码都是最新的!!!

这个服务多实例不仅仅体现在连接quartz的服务多节点,即时是其他的服务如果共用一个中间件,代码不同步也会出问题的!道理非常简单,老的代码拿到新的数据肯定会执行出错!

我之前遇到的坑就是服务2个实例,其中一个是老代码,并且这个服务消费kafka,group相同(本来这样设计就是为了随意扩展N个节点),多实例的情况下,(n)一半数据被老代码拿走但是运行错误导致结果错误,最终表现为部分数据错误,部分数据正常。。。

其实不是我自己坑自己,是QA偷跑了我的老的docker image,完事之后一直连着我的DB,在我不知情的情况下,完了之后我代码都升级好几个版本了,他们也没关,估计都忘了。。。然后我就坑爹的发现老是不对劲,一度认为代码出了BUG。。。因为PAAS平台上面只有一个我的服务(QA换了个名字),但是之后关掉那个服务发现数据状态还在变,于是就知道有东西在偷跑,最终是在mysql上show process进而跑到机器上去查,最终终于找到了那个偷跑的container!!!天坑!!

现在开窍了,遇到这种问题第一时间猜测是有老的服务没有停、或者PAAS出问题导致container没删除成功。排除了这个坑爹原因。

Quartz一样,自动支持了多实例,但是一定注意连接quartz表的服务代码一定要是最新的!

QRTZ_SCHEDULER_STATE这个表存放了当前连接quartz库的实例名称。几个节点就有几条数据,如果只有你一个在跑,里面却有多条数据,那么就是有老的实例还没关掉!

展开阅读全文

没有更多推荐了,返回首页