分布式任务调度平台XXL-JOB--源码解析三:xxl-job-admin调度中心源码解析之初始化两个Thread工作线程

 xxl-job-admin初始化工作

1.1 启动时, 加载applicationcontext-xxl-job-admin.xml, 初始化spring容器


	
		
		
		
	

Spring容器在初始化xxlJobDynamicScheduler时, 会调用inti-method指定的init()方法.

1.2 xxlJobDynamicScheduler类init()分析

 public void init() throws Exception {
         // admin调度中心专门开启一个自动注册线程registryThread, 获取类型为自动注册的执行器信息,完成executor执行器的自动注册与发现
        // 1 用来监控自动注册上来的机器,达到自动注册的目的
        // 2 admin调度中心的自动注册就是, 将executor端每30秒心跳发送过来的address写入registry表,
        // 然后就是开启一个自动registry线程, 每隔30s, 将registry表数据写入到group表.
        // admin registry monitor run
        JobRegistryMonitorHelper.getInstance().start();

        // 启动失败日志监控线程
        // 2 监控任务的执行状态, 如若失败,则发送邮件预警
        // admin monitor run
        JobFailMonitorHelper.getInstance().start();

        NetComServerFactory.putService(AdminBiz.class, XxlJobDynamicScheduler.adminBiz);
        NetComServerFactory.setAccessToken(accessToken);

        // valid
        Assert.notNull(scheduler, "quartz scheduler is null");
        logger.info(">>>>>>>>> init xxl-job admin success.");
    }

上述代码片段中, 主要是开启了两个线程:

JobRegistryMonitorHelper.getInstance().start(); // registryThread线程: 用于发现注册上来的executor执行器

JobFailMonitorHelper.getInstance().start(); // monitorThread线程: 查询数据库log表, 根据trigger_code字段值(200成功, 500失败), 用于监控executor执行器本次job执行是否成功.

NetComServerFactory.putService(AdminBiz.class, XxlJobDynamicScheduler.adminBiz); // 将Spring容器初始化的adminBizImpl对象放入本地缓存
NetComServerFactory.setAccessToken(accessToken);// 设置访问AccessToken

1.3 具体分析admin调度中心是如何实现对executor执行器暴露服务的自动发现 -- registry线程

public void start() {
        registryThread = new Thread(new Runnable() {

            @Override
            public void run() {
                while (!toStop) {
                    try {

                        // 查找自动注册的,address_type字段类型为0的即为自动注册的
                        // auto registry group
                        List groupList = XxlJobDynamicScheduler.xxlJobGroupDao.findByAddressType(0);
                        // groupList = [{"addressType":0,"appName":"xiongxianze-test1","id":2,"order":2,"title":"新分组,
                        // 注册方式:自动"}]>
                        logger.info("==>groupList : {}", JSON.toJSONString(groupList));
                        if (CollectionUtils.isNotEmpty(groupList)) {

                            // 删除 90秒之内没有更新信息的注册机器, 90秒没有心跳信息返回,代表机器已经出现问题,故移除
                            // remove dead address (admin/executor)
                            XxlJobDynamicScheduler.xxlJobRegistryDao.removeDead(RegistryConfig.DEAD_TIMEOUT);

                            // fresh online address (admin/executor)
                            HashMap> appAddressMap = new HashMap>();
                            // 正常心跳的executor端
                            List list = XxlJobDynamicScheduler.xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT);
                            // list =
                            // [{"id":7,"registryGroup":"EXECUTOR","registryKey":"local-execute","registryValue":"10.0.254.5:9999","updateTime":1539711248000}]>
                            logger.info("==>fresh online address list:{}", JSON.toJSONString(list));

                            if (list != null) {
                                for (XxlJobRegistry item : list) {
                                    // 判断该机器注册信息RegistryGroup ,RegistType 是否是EXECUTOR 
                                   
                                    if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
                                        // appName = local-execute
                                        String appName = item.getRegistryKey();
                                        // 获取注册的执行器 KEY (也就是执行器)
                                        List registryList = appAddressMap.get(appName);
                                        // registryList1 = null
                                        logger.info("==>registryList1 :{}", JSON.toJSONString(registryList));
                                        if (registryList == null) {
                                            registryList = new ArrayList();
                                        }

                                        if (!registryList.contains(item.getRegistryValue())) {
                                            registryList.add(item.getRegistryValue());
                                        }
                                        // registryList2 = ["10.0.254.5:9999"]
                                        logger.info("==>registryList2 :{}", JSON.toJSONString(registryList));
                                        // local-execute : ["10.0.254.5:9999"]
                                        appAddressMap.put(appName, registryList);
                                    }
                                }
                            }

                            // fresh group address
                            for (XxlJobGroup group : groupList) {
                                // xiongxianze-test1
                                // 从这里就可以看出来, xxl.job.executor.appname properties文件中该项配置,
                                // 必须与admin端管理执行器时AppName一致
                                List registryList = appAddressMap.get(group.getAppName());
                                String addressListStr = null;
                                // 判断是否已经有了address, 有了的话, 使用"," 拼接addressList
                                if (CollectionUtils.isNotEmpty(registryList)) {
                                    Collections.sort(registryList);
                                    addressListStr = StringUtils.join(registryList, ",");
                                }
                                // 所以addressListStr = null
                                group.setAddressList(addressListStr);
                                // group = {"addressType":0,"appName":"xiongxianze-test1","id":2,"order":2,"title":"新分组,
                                // 注册方式:自动"}>
                                logger.info("==> group : {}", JSON.toJSONString(group));
                                XxlJobDynamicScheduler.xxlJobGroupDao.update(group);
                            }
                        }
                    } catch (Exception e) {
                        logger.error("job registry instance error:{}", e);
                    }
                    try {
                        TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                    } catch (InterruptedException e) {
                        logger.error("job registry instance error:{}", e);
                    }
                }
            }
        });
        registryThread.setDaemon(true);
        registryThread.start();
    }

上述代码片段主要的流程就是:

①XxlJobDynamicScheduler.xxlJobGroupDao.findByAddressType(0);//从数据库中查询address_type=0(即自动注册)的全部分组信息

②XxlJobDynamicScheduler.xxlJobRegistryDao.removeDead(RegistryConfig.DEAD_TIMEOUT);//删除数据库中该xxl_job_qrtz_trigger_registry表中已经过期, 没有进行心跳注册的executor执行器

③XxlJobDynamicScheduler.xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT);//查找全部正常心跳注册上来的executor执行器

④if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) //判断是否是executor执行器

appAddressMap.put(appName, registryList);//这里的appname指的是executor执行器在xxl-job-executor.properties文件中配置的xxl.job.executor.appname=xxl-job-executor-sample, 而registryList即使每个executor执行器的ip和executor启动jetty server端口号组成, 端口号由配置文件中的xxl.job.executor.port=9999决定.

⑤ 接着的这段代码就是, 循环判断admin调度中心group分组中是否有与executor执行器appname相同的名称.     

// fresh group address
                            for (XxlJobGroup group : groupList) {
                                // xiongxianze-test1
                                // 从这里就可以看出来, xxl.job.executor.appname properties文件中该项配置,
                                // 必须与admin端管理执行器时AppName一致, 因为这里是根据从数据库的group表的appname作为key,
                                // 去取executor执行器端的放入缓存中appname
                                List registryList = appAddressMap.get(group.getAppName());
                                String addressListStr = null;
                                // 判断是否已经有了address, 有了的话, 使用"," 拼接addressList
                                if (CollectionUtils.isNotEmpty(registryList)) {
                                    Collections.sort(registryList);
                                    addressListStr = StringUtils.join(registryList, ",");
                                }
                                // 所以addressListStr = null
                                group.setAddressList(addressListStr);
                                // group = {"addressType":0,"appName":"xiongxianze-test1","id":2,"order":2,"title":"新分组,
                                // 注册方式:自动"}>
                                logger.info("==> group : {}", JSON.toJSONString(group));
                                XxlJobDynamicScheduler.xxlJobGroupDao.update(group);
                            }

这里的关键代码就是, XxlJobDynamicScheduler.xxlJobGroupDao.update(group); 将executor执行器端注册上来相同的xxl.job.executor.appname值, 放入了同一个组, 这样admin调度中心, 就能从这个group组中选择不同的调度策略调度本次执行的job. 同时也说明了这几个executor执行器, 对应的是调度同一个job, 这样admin调度中心就能选择相应的调度策略调度job, 比如:first第一个executor执行本次job, last最后一个executor执行器执行本次调度, random随机选择一个executor执行进行本次调度, 或者是LFU最不经常, LRU最近最久未使用的执行器执行本次调度.

到这里admin调度中心就实现了executor执行器注册上来的服务暴露地址的自动发现.

1.4 具体分析admin调度中心是如何实现监控executor执行器执行结果的 -- monitorThread线程

@Override
            public void run() {

                // monitor
                while (!toStop) {
                    try {
                        // 从缓存队列中获取数据, jogLogId
                        Integer jobLogId = JobFailMonitorHelper.instance.queue.take();
                        if (jobLogId != null && jobLogId > 0) {
                            // 根据joblogid从xxl_job_qrtz_trgger_log表中获取job执行的log
                            XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId);
                            if (log != null) {

                                // log.getHandleCode()默认值为0, 而且返回值200, job正在运行中
                                if (ReturnT.SUCCESS_CODE == log.getTriggerCode() && log.getHandleCode() == 0) {
                                    // job running, wait + again monitor
                                    TimeUnit.SECONDS.sleep(10);

                                    JobFailMonitorHelper.monitor(jobLogId);
                                    logger.info(">>>>>>>>>>> job monitor, job running, JobLogId:{}", jobLogId);
                                }

                                // job成功执行
                                if (ReturnT.SUCCESS_CODE == log.getTriggerCode()
                                    && ReturnT.SUCCESS_CODE == log.getHandleCode()) {
                                    // job success, pass
                                    logger.info(">>>>>>>>>>> job monitor, job success, JobLogId:{}", jobLogId);
                                }

                                // job执行失败
                                if (ReturnT.FAIL_CODE == log.getTriggerCode()
                                    || ReturnT.FAIL_CODE == log.getHandleCode()) {
                                    // job fail,
                                    // 执行失败, 发送告警邮件
                                    sendMonitorEmail(log);
                                    logger.info(">>>>>>>>>>> job monitor, job fail, JobLogId:{}", jobLogId);
                                }
                            }
                        }
                    } catch (Exception e) {
                        logger.error("job monitor error:{}", e);
                    }
                }

                // monitor all clear
                List jobLogIdList = new ArrayList();
                int drainToNum = getInstance().queue.drainTo(jobLogIdList);
                if (jobLogIdList != null && jobLogIdList.size() > 0) {
                    for (Integer jobLogId : jobLogIdList) {
                        XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId);
                        if (ReturnT.FAIL_CODE == log.getTriggerCode() || ReturnT.FAIL_CODE == log.getHandleCode()) {
                            // job fail,
                            sendMonitorEmail(log);
                            logger.info(">>>>>>>>>>> job monitor last, job fail, JobLogId:{}", jobLogId);
                        }
                    }
                }

            }

①JobFailMonitorHelper.instance.queue.take(); // 首先monitor线程会从queue队列中取出已经logJobId, 而这个jobLogId是什么时候放入queue队列中的呢? 是XxlJobTrigger类在执行trigger()进行一次调度时, 写入的, 如下:

分布式任务调度平台XXL-JOB--源码解析三:xxl-job-admin调度中心源码解析之初始化两个Thread工作线程_第1张图片

当执行本次调度时, 其实是走了一遍rpc远程调用, 得到执行结果200或者500状态, 而monitor监控线程就是根据该状态判断本次调度是否成功了.

分布式任务调度平台XXL-JOB--源码解析三:xxl-job-admin调度中心源码解析之初始化两个Thread工作线程_第2张图片

②monitor线程再根据jobLogId查询数据库, 判断本次调度job执行结果, trigger_code=200成功, 则在控制台打印本次调度执行成功.

否则就发送告警邮件.

分布式任务调度平台XXL-JOB--源码解析三:xxl-job-admin调度中心源码解析之初始化两个Thread工作线程_第3张图片

以上分析的只是admin调度中心初始化时, 所做的一些初始化工作, 一是, 开启一个自动发现executor执行器服务的线程, 并写入xxl-job-qrtz-trigger-group表, 二是, 再开启一个monitor监控线程, 用于监控executor执行器执行本次job的执行结果.

下一篇将解析admin调度中心具体的调度过程, 调度策略以及rpc通信的实现.

 

 

你可能感兴趣的:(1====>Java)