接入 MSE XXL-JOB 任务调度实现优雅下线

本文涉及的产品
性能测试 PTS,5000VUM额度
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 阿里云 MSE XXL-JOB 产品最新版本特别新增了分布式定时任务优雅下线功能,能有效保障高频业务处理在发布与重启过程中的平滑持续,避免数据丢失和调度失败。

作者:千习


XXL-JOB 为什么需要优雅下线


对于接入定时任务进行业务处理的应用,往往都存在高频率持续性的定时业务处理,当相应应用集群进行发布重启时会影响到对应任务业务处理执行,最终出现业务数据受损。主要存在以下情况:


  • 任务执行中断:任务正在运行中,应用进程停机业务处理中断,可能会导致业务数据不完整。
  • 任务调度下跌:在发布重启过程中,调度器将任务分发给停机节点,导致调度失败影响整体处理效率。


因此,在使用定时任务调度的场景下,需要定时任务执行的优雅下线来实现滚动发布和重启过程中的业务平滑运行。


开源 XXL-JOB 执行原理及优雅下线问题


目前开源 XXL-JOB 在任务调度持续执行过程中,任务执行侧还无法有效地实现优雅下线功能。因此,想通过开源版本实现优雅下线还需进行一番自定义改造。在改造开源 XXL-JOB 优雅下线之前,先可以对 XXL-JOB 任务分发和任务执行的整体链路进行一个剖析。整个链路涉及 xxl-job admin 和 xxl-job executor 两个模块,参考如下图示例:

image.png

针对上图 XXL-JOB 主要的交互执行链路进行解读说明,并标注出对优雅下线的影响逻辑点,以便后续作为自定义改造的参考。


主要存在以下问题:


问题一:下线节点摘流延迟

描述说明:执行器在下线时未能及时完成调度测执行机器列表更新,会出现定时调度至下线节点导致调度失败。


逻辑处理-1:执行器注册

  • 业务应用在依赖 xxl-job sdk 启动后,会初始化 ExecutorRegistryThread 线程持续向调度中心汇报心跳。
  • 调度中心在接收到会通过 JobRegistryHelper,将注册上来的执行器信息写入数据库表 xxl_job_registry。
  • JobRegistryHelper 中存在一个线程,定期查询更新 xxl_job_group 表的 address_list【实际使用的列表】。

image.png

image.png

逻辑处理-2:选择在线执行机器

  • 任务在通过调度线程触发后,会交给 XxlJobTrigger 完成触发执行动作。
  • 在触发执行前,会从 xxl_job_group 表 address_list 读取可用的执行器列表。
  • 通过 ExecutorRouter 在上述机器列表中,按对应的路由策略选择一个机器。
  • 完成机器选择后,会通过 rpc 请求将任务分发至对应 IP 节点运行;此时如选择一个已下线节点任务触发会失败。

image.png

总结说明:由于上述注册执行器逻辑和触发任务执行获取列表数据不实时同步,是通过异步定时更新,因此会产生可用在线机器列表刷新延迟。


问题二:任务执行强制中断

描述说明:目前 xxl-job executor 在退出时会直接触发任务运行线程中断按失败处理,且对列中等待执行的任务执行请求会全部丢弃按失败处理。


涉及以下逻辑处理问题:

逻辑处理-3:将任务分发至对应机器执行

  • 业务应用执行器接收到对应任务后,会根据任务 ID 为每个任务创建一个 JobThread 线程用于任务执行。
  • 该任务本次触发执行请求,会被添加到当前任务线程待执行队列中;任务不同阻塞策略会有不同处理。
  • JobThread 该线程会持续循环读取,队列中的触发记录并执行对应的 JobHandler 完成业务逻辑处理。
  • 任务执行结束后,会将本次执行结果信息提交给 TriggerCallbackThread 的执行应答队列,继续下一次执行。
  • 执行器在停止时会执行 XxlJobExecutor.destroy 方法,该方法处理会中断运行中线程及清理掉队列中等待执行的调度请求。

逻辑处理-4:任务执行结果反馈

  • TriggerCallbackThread 会持续运行加载当前执行结果队列,批量分发执行结果给调度中心。
  • 如向调度中心发送应答结果失败,则会写本地文件落盘,会按排重试。
  • 调度中心在接收到执行结果后会进行执行记录的更新写库。

image.png

总结说明:在上述下线处理过程中,removeJobThread 底层会直接中断运行中的任务线程,且线程对列中等待执行的任务会直接忽略执行按失败处理。


开源 XXL-JOB 如何实现优雅下线


通过上述 XXL-JOB 主要的运行过程分析,接下来根据上述过程基于开源代码实现优雅下线功能。应用优雅下线的核心三步骤:先摘流,再等待执行中业务完成,最后停机下线。

image.png

xxl-job core 模块的 com.xxl.job.core.executor.XxlJobExecutor#destroy,在 springboot 模式下会在应用进程退出时自动进行回调处理,其中包含了应用执行器的一些下线回收动作,但目前此处的相关逻辑处理并不能完全实现优雅下线功能。因此,需结合上述的剖析进行以下步骤改造处理:


步骤一:应用节点摘流

  • 首先 XxlJobExecutor#destroy 方法中,存在 stopEmbedServer() 方法会停止心跳注册并下调度中心发送 registryRemove 请求移除当前执行节点。
  • 调度服务端在接收请求后会将数据库 xxl_job_registry 表中当前节点移除,但参考原理剖析说明实际使用的是 xxl_job_group 表 address_list(并未被同步更新),此时并未真正完成摘流动作。
  • 需对调度服务端进行相应改造来实现摘流,改造点选择(选其一):
  • 对 JobRegistryHelper.registryRemove 方法中添加后续处理,直接刷新 xxl_job_group 表 address_list,也可在 freshGroupRegistryInfo 实现刷新逻辑。
  • 改造 XxlJobTrigger#trigger() 方法,调整 group 中 addressList 的读取方式,对于自动注册直接从 xxl_job_registry 表读取地址列表。
  • 完成上述改造后,即可完成第一步摘流动作。


步骤二:等待执行中业务完成

  • 改造 XxlJobExecutor#destroy 方法中的下一步,需要等待所有待执行任务处理完成,参考如下代码:


public void destroy(){

    // destroy executor-server
    stopEmbedServer();

    // destroy jobThreadRepository
    if (jobThreadRepository.size() > 0) {
        List keyList = new ArrayList(jobThreadRepository.keySet());
        for (int i=0; i < keyList.size(); i++) {
            JobThread jobThread = jobThreadRepository.get(keyList.get(i));
            // 等待所有任务队列执行完成
            while (jobThread != null && jobThread.isRunningOrHasQueue()) {
                try {
                    TimeUnit.SECONDS.sleep(1L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    jobHandlerRepository.clear();

    // destroy JobLogFileCleanThread
    JobLogFileCleanThread.getInstance().toStop();

    // destroy TriggerCallbackThread
    TriggerCallbackThread.getInstance().toStop();

}
  • 等待答应结果队列反馈所有执行结果,目前开源实现的 TriggerCallbackThread.getInstance().toStop() 方法在中断应答结果线程后会最终同步一次处理结果,因此可不做额外处理。
  • 至此,等待运行中任务处理即可完成;另外,可自行发挥对不同任务类型做个性化的下线处理。


步骤三:应用进程停机

  • 应用停止建议在发布部署脚本中通过 kill -15 来触发上述 JVM 的 Hook 回调,可根据业务需要再控制超时强停。
  • 可以通过 pring boot actuator 功能将任务调度优雅下线集成,再通过 /actuator/shutdown 接口下线应用。


阿里云 MSE XXL-JOB 零改造实现优雅下线


在阿里云上 MSE 产品的分布式任务调度功能中,提供了完全兼容开源的 XXL-JOB 实例型产品[1]。用户可以通过创建一个云上实例集群来替代自建的 XXL-JOB Admin 服务,可实现零改造调度服务端切换选择。在选择 MSE XXL-JOB 实例服务后,业务方可以直接获得现成可用的优雅下线能力。

image.png

在采用 springboot 部署应用中,用户在选择接入阿里云上版本的 XXL-JOB 实例后,仅需以下几个步骤即可实现轻松集成优雅下线功能。


步骤一:购买阿里云上 MSE XXL-JOB[2]实例服务,完成业务应用接入使用[3]

步骤二:扩展能力集成,以下方式(二选一):

  • 开通接入 MSE 服务治理[4],可自动集成相关扩展功能(推荐)注:待最新版 Agent 探针发布。
  • 业务应用添加 POM 依赖扩展插件。


通过引入该插件包可实现不同 XXL-JOB 客户端版本功能增强,除优雅下线外还具备其他商业化产品能力增强,无需担心客户端版本映射关系。


<dependency>
  <groupId>com.aliyun.schedulerx</groupId>
  <artifactId>schedulerx3-plugin-xxljob</artifactId>
  <version>最新版本</version>
</dependency>


应用配置参数添加“下线模式”,支持不同的优雅下线方案。


xxl.job.executor.shutdownMode=WAIT_ALL


下线模式 描述
等待全部(WAIT_ALL) (推荐)该模式下,待所有已接收的任务和子任务执行完成后,应用才退出。
等待运行中(WAIT_RUNNING) 该模式下,应用在退出时,将等待已分配线程并在处理中的执行记录完成,队列中的任务将被放弃执行。


image.png


阿里云 MSE XXL-JOB 任务优雅下线演示


以下将通过 1 分钟短视频,演示下业务应用在容器部署场景下,业务应用集群在各种日常运维操作时 MSE XXL-JOB 优雅下线功能效果。在该演示案例中,我们已先提前按上一章节完成了相关的应用接入步骤。

在 K8s 容器部署环境中,POD 销毁时会自动给容器中的主业务进程发送 SIGTERM 信号,应用进程在接收到停止信号后会按提供的 JVM Hook 执行下线处理逻辑并阻塞等待预期逻辑执行完毕。在该案例中需要注意 K8s 中 POD 的 terminationGracePeriodSeconds 参数,该参数控制最长终止等待时间(默认:30s),实际使用中可根据业务任务运行耗时特征进行配置。


结束语


欢迎社区用户对上述内容交流反馈(加钉群:34754806,欢迎评论回复大家都为企业级应用做了哪些本地化改造。同时,当前云产品提供了新用户免费试用体验计划,欢迎大家试用阿里云上商业化版本的 XXL-JOB 实例服务。


参考文档:

10 分钟快速体验云上 MSE XXL-JOB

https://help.aliyun.com/zh/mse/getting-started/get-started-with-xxljob-in-10-minutes

XXL-JOB GET STARTED

https://www.xuxueli.com/xxl-job/

如何启用 MSE XXL-JOB 优雅下线

https://help.aliyun.com/zh/mse/use-cases/how-to-enable-graceful-shutdown

免费试用体验计划

https://free.aliyun.com/?product=1371


相关链接:

[1] XXL-JOB 实例型产品

https://help.aliyun.com/zh/mse/getting-started/get-started-with-xxljob-in-10-minutes

[2] MSE XXL-JOB

https://mse.console.aliyun.com/?spm=a2c4g.11186623.0.0.4ff6b9598kAyz7#/auth

[3] 业务应用接入使用

https://help.aliyun.com/zh/mse/getting-started/get-started-with-xxljob-in-10-minutes

[4] 开通接入 MSE 服务治理

https://help.aliyun.com/zh/mse/user-guide/application-access-3/

相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
Java Nacos
在MSE微服务引擎中,可以使用Java代码进行Nacos下线操作
在MSE微服务引擎中,可以使用Java代码进行Nacos下线操作
273 3
|
Java API Nacos
在MSE微服务引擎中,如果你使用的是Java,你可以通过以下步骤使服务下线
在MSE微服务引擎中,如果你使用的是Java,你可以通过以下步骤使服务下线
80 1
|
7月前
|
安全 应用服务中间件 API
微服务分布式系统架构之zookeeper与dubbo-2
微服务分布式系统架构之zookeeper与dubbo-2
|
7月前
|
负载均衡 Java 应用服务中间件
微服务分布式系统架构之zookeeper与dubbor-1
微服务分布式系统架构之zookeeper与dubbor-1
|
7月前
|
存储 负载均衡 Dubbo
分布式-Zookeeper(一)
分布式-Zookeeper(一)
|
4月前
|
存储 SpringCloudAlibaba Java
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论。
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
|
5月前
|
存储 运维 NoSQL
分布式读写锁的奥义:上古世代 ZooKeeper 的进击
本文作者将介绍女娲对社区 ZooKeeper 在分布式读写锁实践细节上的思考,希望帮助大家理解分布式读写锁背后的原理。
154 11
|
9月前
|
监控 NoSQL Java
分布式锁实现原理问题之ZooKeeper的观察器(Watcher)特点问题如何解决
分布式锁实现原理问题之ZooKeeper的观察器(Watcher)特点问题如何解决
|
6月前
|
分布式计算 NoSQL Java
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
105 2
|
6月前
|
分布式计算 Hadoop
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
125 1

相关产品

  • 微服务引擎
  • 下一篇
    oss创建bucket