1 引言
我们在创建或者编辑执行器的时候,选择了自动注册,就会自动显示IP和端口。
效果:
这里面的IP和端口是怎么获取到呢?
2 任务的发现
保存的时候,调用的接口是:/jobgroup/update
关键代码块:
com.xxl.job.admin.controller.JobGroupController#findRegistryByAppName
private List<String> findRegistryByAppName(String appnameParam){
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
// 从xxl_job_registry库表中直接查询
List<XxlJobRegistry> list = xxlJobRegistryDao.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>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);
}
}
}
return appAddressMap.get(appnameParam);
}
直接从数据查询显然不是我们关注的流程,我们需要探索IP和地址如何获取并保存到数据库的。
3 任务的注册
查询方式可以通过反向查找的方式(上一期已经讲过)获取入口,这里我们直接从入口分析。注册的入口其实就是项目启动的时候,内置服务的启动,就会获取IP和端口进行注册。
入口:com.xxl.job.core.server.EmbedServer#start
调用链路
链路说明
项目启动初始化com.xxl.job.core.server.EmbedServer,就会通过NetUtil.findAvailablePort()获取端口,IpUtil.getIp()获取IP。
然后,通过com.xxl.job.admin.dao.XxlJobRegistryDao#registrySaveOrUpdate保存到数据库,完成任务的注册。
但是值得一提的是,在注册(com.xxl.job.core.thread.ExecutorRegistryThread#start)的时候,并不是一次性直接保存入库的,而是采用了带开关的线程死循环调用的。
这样避免一次注册失败,导致无法调度任务的问题。
4 结语
小小的一个注册发现,里面的设计思想却不简单。里面基本都采用了异步线程的方式,不影响其他业务模块。通过开关控制死循环,增加了模块的健壮性。
对于线程的死循环的时候,有很多工具都用到了,必须CountDownLatch、锁机制的重试等等。死循环用不好,可能会导致CPU的浪费、甚至内存溢出等问题,所以使用的时候就要小心。
死循环的开关设计尤为重要,尤其在多线程的的情况下保证共享变量的可见性,volatile 关键字有其特别的功能。
private volatile boolean toStop = false;
END
喜欢就点赞收藏,也可以关注我的微信公众号:编程朝花夕拾