一、xxl-job 执行器自动注册 ip 错误
先说一下问题发生的场景,这个问题来自我们测试 xxl-job 后管新增一个执行器时,新增执行器时选择的是自动注册机制,也就是由 xxl-job 自动填写我们的执行器 ip 地址。
相信使用过 xxl-job 的众多开发同学里,有很多人新增执行器时都是跟我们一样选择自动注册。这个操作在容器化部署来临之前没什么问题,但是随着后来 docker 容器的流行,线上服务大多以容器方式放行。
容器的 ip 都是虚拟 ip,不代表物理机真实通信地址的 ip,这里就有一个问题,如果 xxl-job 后管跟执行器不在同一个服务器部署,并且执行器自动注册提交的是容器 ip,那么就会产生定时任务执行失败的问题,因为 xxl-job 后管调用不通执行器。
这里给大家画一个 xxl-job 调用执行器的示意图,方便大家理解,
调用流程
给大家讲一下示意图:
1.xxl-job 执行器在启动时会向 xxl-job 后管提交当前服务器 ip,获取逻辑在 com.xxl.job.core.util.IpUtil#getIp 方法中,这个方法在最新版本里默认优先使用 JDK 提供的 InetAddress.getLocalHost() 方法获取本机 ip。获取不到的话再使用 JDK 提供的 NetworkInterface.getNetworkInterfaces() 方法遍历本机网卡获取 ip。
InetAddress.getLocalHost() 的一些坑:
- 在 Linux 环境通过获取 /etc/hosts 和 /etc/resolv.conf 文件内容,如果在 /etc/hosts 文件内容中没有匹配到对应的 ip 地址,则通过 /etc/resolv.conf 中配置的 DNS 地址,向 DNS 服务器发出域名解析请求;
- 如果访问 DNS 服务存在性能问题;
- InetAddress.getLocalHost() 实现中还加了 synchronized 锁,并发环境中会影响性能。
2.调用 xxl-job 后管提供的 api/registry 接口,传递 xxl-job 执行器名称以及通信地址。
3.xxl-job 后管触发定时任务时,会调用 xxl-job 执行器提供的 /run 接口来执行对应任务。
在上面流程中,因为第二步提交的 xxl-job 执行器通信地址错误,因而导致第三步执行失败。那么怎么解决这个问题嘞,其实很简单,xxl-job 后管使用手动注册填写可以联通的执行器地址即可。
二、xxl-job执行器自动注册流程
xxl-job 执行器的自动注册流程其实很简单,这里给大家梳理一下,
自动注册、移除流程
三、执行器注册
客户端在引入 xxl-job 执行器启动时,xxl-job 执行器会启动 ExecutorRegistryThread 线程,
图片
这个线程是专门来做执行器注册的,它会每隔 30 秒调用 xxl-job 后管的 /api/register 接口提交执行器地址和名称。
xxl-job 后管收到执行器的注册信息后,会通过 registryOrRemoveThreadPool 线程池异步保存执行器信息并发送成功响应。
图片
registryOrRemoveThreadPool 线程池会判断是否已经存在当前执行器注册记录,不存在就新建,存在就更新执行器最新注册时间。
所以当我们在 xxl-job 后管新增执行器,选择自动注册的时候,执行器的地址就是这么来的。
四、执行器移除
移除有两个方式,一种是正常结束 xxl-job 执行器,比如停止应用程序,xxl-job 执行器就会调用 /api/registryRemove 接口用来移除当前执行器注册信息。还有一种 xxl-job 后管启动的 registryMonitorThread 线程自动检测注册时间已经超期的执行器注册信息。
图片
registryMonitorThread 线程会查询所有自动注册类型的执行器,然后查询它们对应的注册记录最新时间是否超期,在xxl-job中,这个超期时间是90秒。超过 90 秒后就会移除执行器。
移除时会同时删除 xxl_job_group、xxl_job_registry 表的执行器信息。
四、最后关于IpUtil.getIp() 的一点思考
其实查看 xxl-job 执行器获取执行器 ip 的 com.xxl.job.core.util.IpUtil 文件的提交历史,我们可以发现,早在 18 年就有人发现了这个问题,并向官方提交了 issue 和 pr,并且这个 pr 也被官方认可合并了。
图片
然而官方在 2020 的提交记录中又修改了 com.xxl.job.core.util.IpUtil.getIp 方法,
图片
把本来放到方法末尾的 InetAddress.getLocalHost() 方法又提到了方法开头。。。
看了提交历史和上面关于InetAddress.getLocalHost() 一些坑的朋友,此处应该一脸疑问。。。
这里就暂且当作者基于某种考虑这么做,至于为什么,我已经去 github 提交了 issue,帮大家问了。。。
图片