当我们的业务服务需要定时的执行业务接口时,我们就需要考虑引入定时任务,那么问题又来了,我们是选择“分布式定时任务”呢,还是“本地Job”呢。
好吧,这里就从技术和架构的角度带着大家一起看一下这个问题。
线程和线程池
熟悉线程和线程池的技术人都应该知道,使用它们是可以实现定时功能的。
RocketMQ中比较常见的定时任务的写法,就是利用线程来实现的定时任务,也就是Thread类,说的简单一点就是线程等待。
如下代码的语义就是按照固定周期waitInterval去执行负载均衡。
@Override
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
this.waitForRunning(waitInterval);
this.mqClientFactory.doRebalance();
}
log.info(this.getServiceName() + " service end");
}
RocketMQ中还有一种比较常见的定时任务的写法就是用线程池来实现定时任务的,也就是ScheduledExecutorService类。
如下代码的语义就是周期性的注册Broker Server。
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
BrokerController.this.registerBrokerAll(true, false,
brokerConfig.isForceRegister());
} catch (Throwable e) {
log.error("registerBrokerAll Exception", e);
}
}
}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.
getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
好吧,既然RocketMQ可以利用线程和线程池来完成定时任务,那么我们的业务服务也是可以的。
JDK自带的定时器Timer和TimerTask
定时器Timer和TimerTask本质上也是线程实现的定时功能,所以说RocketMQ就直接使用线程和线程池来实现个性化的定时任务,人家那样做也不是没有道理。比如在RocketMQ的延迟消息就是使用TimerTask来实现的,当然使用定时器Timer和TimerTask会存在很多缺陷,比如没有分布式调度的功能,不能确保分布式环境下执行Job的数据一致性等,但是人家RocketMQ不需要考虑这样的场景,它的分布式问题利用其他的替代方式解决了,比如集群选举等。
Spring Framework自带的定时器
熟悉Spring Framework框架的技术人应该知道,可以使用注解@EnableScheduling和@Scheduled就可以开启一个定时任务,我们可以这样写一个定时器。
@Component
@EnableScheduling
public class SimulatingMultithreadedOrder {
@Scheduled(fixedRate = 2000)
public void producerMessage() throws MQClientException {
//定时执行的逻辑
}
}
使用Spring Framework框架自带的本地定时任务非常方便,如果你的基础框架采用Spring Boot或者更加高级一点Spring Cloud Alibaba,你不需要添加额外的Jar包依赖,因为@EnableScheduling和@Scheduled是Spring Context提供给我们的定时任务注解,只要你是Spring Framework生态的业务服务,你就可以使用它,非常的简单。
Quartz
Quartz可以说是非常古老的定时任务框架,目前最新的版本为2.3.2,项目最近一次更新是2019年,也就是说处于停更状态,不过这个不影响它的关注度,毕竟它是制定规范的。
看过Quartz底层源码的技术人应该都知道,Quartz中大量使用了线程来实现定时任务的功能,比如执行任务的线程池和调度任务的线程池等。
xxl-job
笔者在很多年之前就已经接触过xxl-job了,也是看着它成长起来的,该框架的作者也是非常的厉害,并且这个框架是个人维护到现在,如果我没记错的话应该有快10年了。
在Github上搜索xxl-job,目前Fork数为8.9k,Star 20.9k,这个成绩已经超过了很多Apache顶级项目的数据了,目前它的最新版本为v2.3.0,社区活跃度还是非常活跃的。
xxl-job给自己的定位是分布式任务调度平台,也就是说它不仅是要做定时任务,而是要做调度,这点就和云原生的理念不谋而合了。
关于xxl-job的原理大家可以参考官方https://github.com/xuxueli/xxl-job,分布式Job的原理其实不难,多实践一下,看看源码大致能知道它是如何完成调度的。
笔者也下载了它的源码大致看了一下,其实它本质上也是用线程来实现的定时Job,只不过加了一些动态调度的规则而已,并且能够做到非常优雅的和动态的杀死运行Job的线程,并完成线程Job的调度。
笔者当初关注过xxl-job,主要原因是它在那个版本把ZooKeeper去掉了,自己写了一套分布式一致性的框架,具体是在哪个版本改进的,这里我大概忘记了。
Elastic-job
Elastic-job是Apache的顶级项目,目前已经改名为shardingsphere-elasticjob,具体数据如下。
shardingsphere-elasticjob和xxl-job一个最大的不同点在于,前者还是重度依赖ZooKeeper的,毕竟ZooKeeper也是Apache的项目,哈哈,肯定不会去掉的。
还有一个比较大的不同点就是shardingsphere-elasticjob的定时任务规范是依赖Quartz的二次开发的产品,而xxl-job是完全自己写的。
目前shardingsphere-elasticjob最新的版本为3.0.1,社区活跃度是非常高的,如果大家对源码感兴趣可以下载相关源码,https://github.com/apache/shardingsphere-elasticjob。
shardingsphere-elasticjob包含两个部分:
- E lastic-Job-Lite 定位 为轻量级无中心化解决方案,使用 jar 包的形式提供分布式任务 的协调服务;
- Ela stic-Job-Cloud 使用 Mesos + Docker(TBD) 的解决方案,额外提供资源治理、应用分发以及进程隔离等服务。
Saturn
Saturn是唯品会在github开源的一款分布式任务调度产品。它是基于当当elastic-job 1.0版本来开发的,其上完善了一些功能和添加了一些新的feature。
Saturn目前最新的版本为v3.5.1,社区活跃度不是很高。
antares
Antares是一个完全自研的个人维护的定时任务框架。
Antares目前最新的版本为1.4.0,最近一次更新是2017年,几乎处于停止维护状态。
Spring Cloud Alibaba Cloud SchedulerX
SchedulerX(分布式任务调度) 是隶属于阿里云EDAS产品的组件, Spring Cloud AliCloud SchedulerX 提供了在Spring Cloud的配置规范下,分布式任务调度的功能支持。SchedulerX可提供秒级、精准、高可靠、高可用的定时任务调度服务,并支持多种类型的任务调度,如简单单机任务、简单多机任务、脚本任务以及网格任务。
阿里巴巴并没有开放Spring Cloud Alibaba Cloud SchedulerX的源码,这个是一个硬伤,咱们只能使用阿里云的商业版本。
总之,不论是哪一种分布式定时Job,都会有它适用的业务场景,并没有谁是绝对的好和绝对的不好,就像是RPC框架一样,合适才是最重要的。优秀的框架的架构和设计思想是非常有用的,我们能够把它学过来,并为自己所用也是一种能力的提升,毕竟咱们要先学会模仿,才能够学会创新。