xxl-job(1) 执行器注册流程

911 阅读4分钟

前言

部门内一直在使用xxl-job的定时任务缓存数据,用起来很方便!但是本人一直没有对xxl-job框架的原理、流程进行学习,本篇文章对执行器的注册流程进行探索,欢迎指正!

自动注册

保存执行器信息

发送/xxl-job-admin/jobgroup/save请求,将AppName、名称、注册方式保存到MySQL中,保存信息如下: image.png address_type为执行器地址类型:0=自动注册、1=手动录入

执行器启动后注册到调度中心

1)启动执行器项目时,会启动一个netty服务器,把该netty服务器的信息注册到调度中心,该netty服务器会与调度中心进行通信
EmbedServer.java

public void start(final String address, final int port, final String appname, final String accessToken) {
    executorBiz = new ExecutorBizImpl();
    thread = new Thread(new Runnable() {
        @Override
        public void run() {
            // param
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            // 1.bizThreadPool用于执行调度中心发来的任务
            ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
                    0,
                    200,
                    60L,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>(2000),
                    new ThreadFactory() {
                        @Override
                        public Thread newThread(Runnable r) {
                            return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode());
                        }
                    },
                    // 拒绝策略:直接丢弃
                    new RejectedExecutionHandler() {
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
                        }
                    });

            try {
                // start server
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel channel) throws Exception {
                                channel.pipeline()
                                        .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle 超时关闭通道
                                        .addLast(new HttpServerCodec())
                                        .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL
                                        .addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
                            }
                        })
                        .childOption(ChannelOption.SO_KEEPALIVE, true);

                // bind
                ChannelFuture future = bootstrap.bind(port).sync();

                logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);

                // start registry
                // 把自己注册到调度中心
                startRegistry(appname, address);

                // wait util stop
                future.channel().closeFuture().sync();

            } catch (InterruptedException e) {
                if (e instanceof InterruptedException) {
                    logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
                } else {
                    logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
                }
            } finally {
                // stop
                try {
                    workerGroup.shutdownGracefully();
                    bossGroup.shutdownGracefully();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    });
    thread.setDaemon(true);    // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
    thread.start();
}

2)再往startRegistry里面走,可以看到

public void start(final String appname, final String address){

    // valid
    if (appname==null || appname.trim().length()==0) {
        logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, appname is null.");
        return;
    }
    if (XxlJobExecutor.getAdminBizList() == null) {
        logger.warn(">>>>>>>>>>> xxl-job, executor registry config fail, adminAddresses is null.");
        return;
    }

    registryThread = new Thread(new Runnable() {
        @Override
        public void run() {

            // registry
            while (!toStop) {
                try {
                    RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
                    for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {// 这里可以看到,调度中心可以部署多个
                        try {
                            ReturnT<String> registryResult = adminBiz.registry(registryParam);//把自己注册到当前的adminBiz调度中心中
                            if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                                registryResult = ReturnT.SUCCESS;
                                logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                                break;
                            } else {
                                logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                            }
                        } catch (Exception e) {
                            logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);
                        }

                    }
                } catch (Exception e) {
                    if (!toStop) {
                        logger.error(e.getMessage(), e);
                    }

                }

                try {
                    if (!toStop) {
                        TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                    }
                } catch (InterruptedException e) {
                    if (!toStop) {
                        logger.warn(">>>>>>>>>>> xxl-job, executor registry thread interrupted, error msg:{}", e.getMessage());
                    }
                }
            }

            // registry remove
            try {
                RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
                for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
                    try {
                        ReturnT<String> registryResult = adminBiz.registryRemove(registryParam);
                        if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
                            registryResult = ReturnT.SUCCESS;
                            logger.info(">>>>>>>>>>> xxl-job registry-remove success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                            break;
                        } else {
                            logger.info(">>>>>>>>>>> xxl-job registry-remove fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});
                        }
                    } catch (Exception e) {
                        if (!toStop) {
                            logger.info(">>>>>>>>>>> xxl-job registry-remove error, registryParam:{}", registryParam, e);
                        }

                    }

                }
            } catch (Exception e) {
                if (!toStop) {
                    logger.error(e.getMessage(), e);
                }
            }
            logger.info(">>>>>>>>>>> xxl-job, executor registry thread destroy.");

        }
    });
    registryThread.setDaemon(true);
    registryThread.setName("xxl-job, executor ExecutorRegistryThread");
    registryThread.start();
}

3)最终发送请求给调度中心

return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryParam, String.class);

4)调度中心收到请求
JobApiController.java

// 其他代码省略
return adminBiz.registry(registryParam);

5)调度中心保存注册信息到MySQL

public ReturnT<String> registry(RegistryParam registryParam) {

   // valid
   if (!StringUtils.hasText(registryParam.getRegistryGroup())
         || !StringUtils.hasText(registryParam.getRegistryKey())
         || !StringUtils.hasText(registryParam.getRegistryValue())) {
      return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
   }

   // async execute
   registryOrRemoveThreadPool.execute(new Runnable() {
      @Override
      public void run() {
      // 可能存在原有注册记录,先尝试更新(多个调度中心使用的是同一台MySQL,所以有可能出现已存在的记录)
         int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
         if (ret < 1) {
         // 如果没有原有记录,则插入新记录
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());

            // fresh
            freshGroupRegistryInfo(registryParam);
         }
      }
   });

   return ReturnT.SUCCESS;
}

注意: 注册信息刚插入MySQL表之后,查看执行器管理界面时,仍不会显示online机器地址
原因: 注册信息插入到xxl_job_registry表,执行器管理界面查询的是xxl_job_group表的信息,需要registryMonitorThread线程将online机器地址信息更新到xxl_job_group表中,具体代码如下: JobRegistryHelper.java

registryMonitorThread = new Thread(new Runnable() {
   @Override
   public void run() {
      while (!toStop) {
         try {
            // auto registry group
            List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
            if (groupList!=null && !groupList.isEmpty()) {

               // remove dead address (admin/executor)
               List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
               if (ids!=null && ids.size()>0) {
                  XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
               }

               // fresh online address (admin/executor)
               HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
               List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
               if (list != null) {
                  for (XxlJobRegistry item: list) {
                     if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
                        String appname = item.getRegistryKey();
                        List<String> registryList = appAddressMap.get(appname);
                        if (registryList == null) {
                           registryList = new ArrayList<String>();
                        }
                        // registryValue就是执行器的ip:port
                        if (!registryList.contains(item.getRegistryValue())) {
                           registryList.add(item.getRegistryValue());
                        }
                        appAddressMap.put(appname, registryList);
                     }
                  }
               }

               // fresh group address
               for (XxlJobGroup group: groupList) {
                  List<String> registryList = appAddressMap.get(group.getAppname());
                  String addressListStr = null;
                  if (registryList!=null && !registryList.isEmpty()) {
                     Collections.sort(registryList);
                     StringBuilder addressListSB = new StringBuilder();
                     for (String item:registryList) {
                        addressListSB.append(item).append(",");
                     }
                     addressListStr = addressListSB.toString();
                     addressListStr = addressListStr.substring(0, addressListStr.length()-1);
                  }
                  group.setAddressList(addressListStr);
                  group.setUpdateTime(new Date());
                  //此处会将读取到的机器地址,保存到xxl_job_group表中,保存之后前端界面刷新才能看到online机器地址
                  XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
               }
            }
         } catch (Exception e) {
            if (!toStop) {
               logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
            }
         }
         try {
            //修改该值,可以缩短监控时间间隔,默认30s
            TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
         } catch (InterruptedException e) {
            if (!toStop) {
               logger.error(">>>>>>>>>>> xxl-job, job registry monitor thread error:{}", e);
            }
         }
      }
      logger.info(">>>>>>>>>>> xxl-job, job registry monitor thread stop");
   }
});
总结

即使是一个任务调度框架,也是很复杂的,有很多细节值得学习,学习时不能像无头苍蝇一样乱撞,这样只会在看代码的过程中迷失方向,自我内耗。

我的思路是:使用功能,给自己提出问题,然后对疑点进行调试,最终对问题进行自我解答,以始为终。