一、单机定时任务
1、Timer
java.util.Timer
是 JDK 1.3 开始就已经支持的一种定时任务的实现方式。
Timer
内部使用一个叫做 TaskQueue
的类存放定时任务,它是一个基于最小堆实现的优先级队列。TaskQueue
会按照任务距离下一次执行时间的大小将任务排序,保证在堆顶的任务最先执行。这样在需要执行任务时,每次只需要取出堆顶的任务运行即可!
Timer
使用起来比较简单,通过下面的方式我们就能创建一个 1s 之后执行的定时任务。
// 示例代码:
import java.util.Timer;
import java.util.TimerTask;
public class TimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("Task executed at: " + new java.util.Date());
}
};
// 安排任务在 1000 毫秒后执行一次
timer.schedule(task, 1000);
// 如果需要定期执行,可以使用以下代码(例如每秒执行一次)
// timer.scheduleAtFixedRate(task, 1000, 1000);
}
}
不过其缺陷较多,比如一个 Timer
一个线程,这就导致 Timer
的任务的执行只能串行执行,一个任务执行时间过长的话会影响其他任务(性能非常差),再比如发生异常时任务直接停止(Timer
只捕获了 InterruptedException
)。
2、ScheduledExecutorService
-
ScheduledExecutorService
是 Java 5 引入的,提供了更强大和灵活的定时任务调度功能。它基于线程池实现,能够更好地处理并发任务。 -
ScheduledThreadPoolExecutor
支持多线程执行定时任务并且功能更强大,是Timer
的替代品 -
ScheduledExecutorService
是一个接口,有多个实现类,比较常用的是ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
本身就是一个线程池,支持任务并发执行。并且,其内部使用 DelayedWorkQueue
作为任务队列
// 示例代码:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceExample {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Task executed at: " + new java.util.Date());
// 安排任务在 1 秒后首次执行,之后每隔 2 秒执行一次
executorService.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
// 注意:在实际应用中,通常需要在适当的时候关闭 executorService 以释放资源
// 例如,在应用程序关闭时调用 executorService.shutdown()
}
}
3、时间轮
Kafka、Dubbo、ZooKeeper、Netty、Caffeine、Akka 中都有对时间轮的实现。
时间轮简单来说就是一个环形的队列(底层一般基于数组实现),队列中的每一个元素(时间格)都可以存放一个定时任务列表。
时间轮中的每个时间格代表了时间轮的基本时间跨度或者说时间精度,假如时间一秒走一个时间格的话,那么这个时间轮的最高精度就是 1 秒(也就是说 3 s 和 3.9s 会在同一个时间格中)。
下图是一个有 12 个时间格的时间轮,转完一圈需要 12 s。当我们需要新建一个 3s 后执行的定时任务,只需要将定时任务放在下标为 3 的时间格中即可。当我们需要新建一个 9s 后执行的定时任务,只需要将定时任务放在下标为 9 的时间格中即可。
那当我们需要创建一个 13s 后执行的定时任务怎么办呢?这个时候可以引入一叫做 圈数/轮数 的概念,也就是说这个任务还是放在下标为 1 的时间格中, 不过它的圈数为 2 。
除了增加圈数这种方法之外,还有一种 多层次时间轮 (类似手表),Kafka 采用的就是这种方案。
针对下图的时间轮,我来举一个例子便于大家理解。
上图的时间轮(ms -> s),第 1 层的时间精度为 1 ,第 2 层的时间精度为 20 ,第 3 层的时间精度为 400。假如我们需要添加一个 350s 后执行的任务 A 的话(当前时间是 0s),这个任务会被放在第 2 层(因为第二层的时间跨度为 20*20=400>350)的第 350/20=17 个时间格子。
当第一层转了 17 圈之后,时间过去了 340s ,第 2 层的指针此时来到第 17 个时间格子。此时,第 2 层第 17 个格子的任务会被移动到第 1 层。
任务 A 当前是 10s 之后执行,因此它会被移动到第 1 层的第 10 个时间格子。
这里在层与层之间的移动也叫做时间轮的升降级。参考手表来理解就好!
时间轮比较适合任务数量比较多的定时任务场景,它的任务写入和执行的时间复杂度都是 0(1)
4、DelayQueue
DelayQueue是 JUC 包(java.util.concurrent)为我们提供的延迟队列,用于实现延时任务比如订单下单 15 分钟未支付直接取消。它是 BlockingQueue 的一种,底层是一个基于 PriorityQueue 实现的一个无界队列,是线程安全的
DelayQueue
和 Timer/TimerTask
都可以用于实现定时任务调度,但是它们的实现方式不同。DelayQueue
是基于优先级队列和堆排序算法实现的,可以实现多个任务按照时间先后顺序执行;而 Timer/TimerTask
是基于单线程实现的,只能按照任务的执行顺序依次执行,如果某个任务执行时间过长,会影响其他任务的执行。另外,DelayQueue
还支持动态添加和移除任务,而 Timer/TimerTask
只能在创建时指定任务。
Spring Task5、Spring TaskSpring Task
我们直接通过 Spring 提供的 @Scheduled
注解即可定义定时任务,非常方便!
/**
* cron:使用Cron表达式。 每分钟的1,2秒运行
*/
@Scheduled(cron = "1-2 * * * * ? ")
public void reportCurrentTimeWithCronExpression() {
log.info("Cron Expression: The time is now {}", dateFormat.format(new Date()));
}
我在大学那会做的一个 SSM 的企业级项目,就是用的 Spring Task 来做的定时任务。
并且,Spring Task 还是支持 Cron 表达式 的。Cron 表达式主要用于定时作业(定时任务)系统定义执行时间或执行频率的表达式,非常厉害,你可以通过 Cron 表达式进行设置定时任务每天或者每个月什么时候执行等等操作。咱们要学习定时任务的话,Cron 表达式是一定是要重点关注的
Spring Task 底层是基于 JDK 的 ScheduledThreadPoolExecutor
线程池来实现的
二、分布式定时任务
1、Redis
Redis 是可以用来做延时任务的,基于 Redis 实现延时任务的功能无非就下面两种方案:
-
Redis 过期事件监听
-
Redisson 内置的延时队列
1、依赖添加
如果你使用的是 Maven,请在 pom.xml
文件中添加 Jedis 依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.0.1</version> <!-- 请根据需要选择最新版本 -->
</dependency>
2、代码实现
以下是一个简单的 Java 程序,它使用 Jedis 连接到 Redis,并设置一个定时任务。当任务到期时,它将通过 Redis 的发布/订阅机制通知所有订阅者。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import java.util.UUID;
public class RedisDistributedScheduler {
private static final String CHANNEL = "task:expired";
private static final String TASK_PREFIX = "task:";
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379); // 连接到 Redis 服务器
// 创建一个新的定时任务
String taskId = UUID.randomUUID().toString();
String taskKey = TASK_PREFIX + taskId;
long delayInSeconds = 10; // 任务延迟时间(秒)
// 设置键的过期时间,当键过期时发布消息到指定频道
jedis.setex(taskKey, delayInSeconds, taskId);
jedis.publish(CHANNEL, "Task " + taskId + " will expire after " + delayInSeconds + " seconds");
// 订阅任务过期频道
JedisPubSub subscriber = new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
if (CHANNEL.equals(channel)) {
String expiredTaskId = jedis.get(TASK_PREFIX + message);
if (expiredTaskId != null) {
System.out.println("Task " + expiredTaskId + " has expired!");
// 在这里执行任务的后续操作
}
}
}
};
// 注意:这里的订阅是阻塞的,因此通常会在单独的线程中运行
new Thread(() -> {
Jedis jedisSubscriber = new Jedis("localhost", 6379);
jedisSubscriber.subscribe(subscriber, CHANNEL);
}).start();
// 为了演示,主线程在这里睡眠一段时间,然后退出
try {
Thread.sleep(20000); // 睡眠20秒,以确保能看到任务过期通知
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
jedis.close();
}
}
}
2、MQ
RabbitMQ 本身是一个消息代理(message broker),它主要处理消息的发送、接收、存储和转发。它并不直接提供分布式定时任务的功能,但你可以结合 RabbitMQ 和其他工具或框架来实现这一功能。
一个常见的做法是使用 RabbitMQ 与一个定时任务调度器(如 Quartz Scheduler)或消息队列的延迟队列功能(如果 RabbitMQ 插件或扩展支持)相结合。然而,RabbitMQ 官方并没有内置的延迟队列功能,但你可以通过 TTL(Time-To-Live)和死信交换机(Dead Letter Exchange, DLX)来实现类似的效果。
下面,我将给出一个使用 RabbitMQ 和 Quartz Scheduler 的简单示例,演示如何实现分布式定时任务。请注意,这个示例并不是在生产环境中推荐的最佳实践,但它可以帮助你理解基本概念
优缺点总结:
-
优点:可以与 Spring 集成、支持分布式、支持集群、性能不错
-
缺点:功能性较差、不灵活、需要保障消息可靠性
1、环境准备
-
确保你已经安装了 RabbitMQ 服务器,并且可以通过客户端连接到它。
-
在你的 Java 项目中引入 RabbitMQ 客户端库和 Quartz Scheduler 库。
2. 依赖添加
如果你使用的是 Maven,请在 pom.xml
文件中添加以下依赖:
<dependencies>
<!-- RabbitMQ Java client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.13.0</version> <!-- 请根据需要选择最新版本 -->
</dependency>
<!-- Quartz Scheduler -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version> <!-- 请根据需要选择最新版本 -->
</dependency>
<!-- Quartz Scheduler with Spring integration (if using Spring) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.10</version> <!-- 请根据需要选择最新版本,注意与 Spring 版本兼容 -->
</dependency>
</dependencies>
注意:如果你打算使用 Spring 来管理 Quartz Scheduler,你可能还需要添加 Spring 的相关依赖。
3. 代码实现
以下是一个简单的 Java 程序,它使用 Quartz Scheduler 来调度任务,并在任务执行时发送消息到 RabbitMQ。
// 省略了导入语句,请确保你导入了所需的类
public class RabbitMQQuartzScheduler {
private static final String QUEUE_NAME = "taskQueue";
private static final String EXCHANGE_NAME = "taskExchange";
private static final String ROUTING_KEY = "taskRoutingKey";
public static void main(String[] args) throws SchedulerException, IOException, TimeoutException {
// 连接到 RabbitMQ
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 声明队列、交换机和绑定
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
// 初始化 Quartz Scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
// 定义任务
JobDetail job = JobBuilder.newJob(TaskSenderJob.class)
.withIdentity("taskSenderJob", "group1")
.build();
// 定义触发器,每隔10秒执行一次任务
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("taskSenderTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.build();
// 将任务和触发器调度到 Scheduler
scheduler.scheduleJob(job, trigger);
// 为了演示,让主线程等待一段时间
Thread.sleep(60000); // 等待60秒
// 关闭 Scheduler
scheduler.shutdown();
}
}
// 定义任务类
public static class TaskSenderJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 连接到 RabbitMQ 并发送消息
try (ConnectionFactory factory = new ConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
String message = "Hello, RabbitMQ!";
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes());
System.out.println("Sent: " + message);
} catch (IOException e) {
throw new JobExecutionException("Failed to send message to RabbitMQ", e);
}
}
}
}
注意:
-
在上面的代码中,Quartz Scheduler 被配置为每隔 10 秒执行一次
TaskSenderJob
。这个任务连接到 RabbitMQ 并发送一条消息到指定的队列。 -
你需要确保 RabbitMQ 服务器正在运行,并且你提供的连接参数(如主机名、端口等)是正确的。
-
这个示例中的 RabbitMQ 连接是在每次任务执行时创建的,这不是最佳实践。在实际应用中,你应该使用连接池或重用连接。
-
Quartz Scheduler 的配置和调度逻辑可以根据你的需求进行调整。
-
如果你打算在分布式环境中运行这个示例,你需要确保 Quartz Scheduler 的实例之间不会相互干扰(例如,通过数据库锁或分布式锁来协调任务的执行)。
-
对于更复杂的定时任务需求,你可能需要更高级的调度功能,如 cron 表达式、依赖任务等。Quartz Scheduler 提供了这些功能,但你需要更深入地学习其 API 和配置。
三、分布式任务调度框架
如果我们需要一些高级特性比如支持任务在分布式场景下的分片和高可用的话,我们就需要用到分布式任务调度框架了。
通常情况下,一个分布式定时任务的执行往往涉及到下面这些角色:
-
任务:首先肯定是要执行的任务,这个任务就是具体的业务逻辑比如定时发送文章。
-
调度器:其次是调度中心,调度中心主要负责任务管理,会分配任务给执行器。
-
执行器:最后就是执行器,执行器接收调度器分派的任务并执行。
1、Quartz
-
功能:Quartz是一个功能丰富的开源作业调度库,适用于Java应用程序。它支持复杂的调度规则,可以与任何Java应用程序集成,并提供了基于数据库的作业存储,确保状态的持久化。
-
特点:
-
强大的调度功能。
-
支持集群,保证高可用性。
-
可以通过ZooKeeper等分布式协调工具来管理和协调Quartz集群,实现更灵活的分布式任务调度。
-
优缺点总结:
-
优点:可以与 Spring 集成,并且支持动态添加任务和集群。
-
缺点:分布式支持不友好,不支持任务可视化管理、使用麻烦(相比于其他同类型框架来说)
2、XXL-JOB
XXL-JOB
于 2015 年开源,是一款优秀的轻量级分布式任务调度框架,支持任务可视化管理、弹性扩容缩容、任务失败重试和告警、任务分片等功能
-
定位:XXL-JOB是一个轻量级分布式任务调度平台,主要面向互联网场景。它易于上手和使用,支持集群,可保证高可用。
-
特点:
-
提供了Web界面进行任务管理和监控。
-
支持任务分片,可以处理大量数据。
-
根据 XXL-JOB
官网介绍,其解决了很多 Quartz 的不足。
Quartz 作为开源作业调度中的佼佼者,是作业调度的首选。但是集群环境中 Quartz 采用 API 的方式对任务进行管理,从而可以避免上述问题,但是同样存在以下问题:
问题一:调用 API 的的方式操作任务,不人性化;
问题二:需要持久化业务 QuartzJobBean 到底层数据表中,系统侵入性相当严重。
问题三:调度逻辑和 QuartzJobBean 耦合在同一个项目中,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务;
问题四:quartz 底层以“抢占式”获取 DB 锁并由抢占成功节点负责运行任务,会导致节点负载悬殊非常大;而 XXL-JOB 通过执行器实现“协同分配式”运行任务,充分发挥集群优势,负载各节点均衡。
XXL-JOB 弥补了 quartz 的上述不足之处。
XXL-JOB
的架构设计如下图所示:
从上图可以看出,XXL-JOB
由 调度中心 和 执行器 两大部分组成。调度中心主要负责任务管理、执行器管理以及日志管理。执行器主要是接收调度信号并处理。另外,调度中心进行任务调度时,是通过自研 RPC 来实现的。
不同于 Elastic-Job 的去中心化设计, XXL-JOB
的这种设计也被称为中心化设计(调度中心调度多个执行器执行任务)。
和 Quzrtz
类似 XXL-JOB
也是基于数据库锁调度任务,存在性能瓶颈。不过,一般在任务量不是特别大的情况下,没有什么影响的,可以满足绝大部分公司的要求。
不要被 XXL-JOB
的架构图给吓着了,实际上,我们要用 XXL-JOB
的话,只需要重写 IJobHandler
自定义任务执行逻辑就可以了,非常易用!
@JobHandler(value="myApiJobHandler")
@Component
public class MyApiJobHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
//......
return ReturnT.SUCCESS;
}
}
还可以直接基于注解定义任务。
@XxlJob("myAnnotationJobHandler")
public ReturnT<String> myAnnotationJobHandler(String param) throws Exception {
//......
return ReturnT.SUCCESS;
}
3、ElasticJob
-
背景:ElasticJob是当当网开源的一个分布式调度解决方案,基于Quartz进行扩展。
-
特点:
-
支持分布式任务分片。
-
提供了失效转移和错过任务重新执行的功能。
-
可以与Spring Boot集成。
-
分为Elastic-job-lite和Elastic-job-cloud两个产品线,其中Elastic-job-lite为轻量级无中心化的解决方案,使用jar包的形式提供分布式任务的协调服务。
-
ElasticJob-Lite 和 ElasticJob-Cloud 两者的对比如下:
ElasticJob-Lite | ElasticJob-Cloud | |
---|---|---|
无中心化 | 是 | 否 |
资源分配 | 不支持 | 支持 |
作业模式 | 常驻 | 常驻 + 瞬时 |
部署依赖 | ZooKeeper | ZooKeeper + Mesos |
ElasticJob
支持任务在分布式场景下的分片和高可用、任务可视化管理等功能。
ElasticJob-Lite 的架构设计如下图所示:
从上图可以看出,Elastic-Job 没有调度中心这一概念,而是使用 ZooKeeper 作为注册中心,注册中心负责协调分配任务到不同的节点上。
Elastic-Job 中的定时调度都是由执行器自行触发,这种设计也被称为去中心化设计(调度和处理都是执行器单独完成)。
@Component
@ElasticJobConf(name = "dayJob", cron = "0/10 * * * * ?", shardingTotalCount = 2,
shardingItemParameters = "0=AAAA,1=BBBB", description = "简单任务", failover = true)
public class TestJob implements SimpleJob {
@Override
public void execute(ShardingContext shardingContext) {
log.info("TestJob任务名:【{}】, 片数:【{}】, param=【{}】", shardingContext.getJobName(), shardingContext.getShardingTotalCount(),
shardingContext.getShardingParameter());
}
}
4、LTS(Light Task Scheduler)
-
特点:
-
LTS是一个轻量级分布式任务调度框架,具有三种角色:JobClient、JobTracker、TaskTracker。各个节点都是无状态的,可以部署多个以实现负载均衡和更大的负载量。
-
具有良好的容错能力,采用多种注册中心(如Zookeeper、redis等)进行节点信息暴露和master选举。
-
使用Mongo或Mysql存储任务队列和任务执行日志,使用netty做底层通信。
-
5、SchedulerX
-
背景:SchedulerX是阿里云提供的分布式任务调度平台。
-
特点:
-
云原生,提供企业级的任务调度服务。
-
支持可视化任务调度和管理。
-
具有高可用性和可扩展性。
-
支持任务依赖和事件触发。
-
6、Celery
-
定位:Celery是一个基于Python开发的异步任务队列/作业队列,用于在多个线程或机器上执行任务。
-
特点:
-
支持多种消息传递方式,如RabbitMQ、Redis等。
-
任务可以异步执行,也可以调度执行。
-
支持任务结果持久化和监控。
-
7、Cronsun
-
定位:Cronsun是一个分布式任务系统,旨在解决多台Linux机器上crontab任务管理不方便的问题。
-
特点:
-
单个节点与Linux机器上的crontab近似,但提供了任务高可用的支持。
-
支持界面管理机器上的任务,以及任务失败邮件提醒。
-
使用etcd作为通讯工具。
-
8、Saturn
-
特点:Saturn是阿里巴巴开源的一个分布式任务调度平台,在开源社区及企业内部都有广泛应用。它支持任务的高可用、弹性扩容、分片、任务依赖等特性。
9、TBSchedule
-
背景:TBSchedule是淘宝开源的一个分布式任务调度系统,它解决了大量定时、延时任务的调度问题。
每个分布式任务调度框架都有其独特的特性和适用场景。在选择合适的框架时,需要考虑任务类型、执行频率、数据量、系统架构、易用性、社区支持、可扩展性和高可用性等因素。
10、PowerJob
非常值得关注的一个分布式任务调度框架,分布式任务调度领域的新星。目前,已经有很多公司接入比如 OPPO、京东、中通、思科。
这个框架的诞生也挺有意思的,PowerJob 的作者当时在阿里巴巴实习过,阿里巴巴那会使用的是内部自研的 SchedulerX(阿里云付费产品)。实习期满之后,PowerJob 的作者离开了阿里巴巴。想着说自研一个 SchedulerX,防止哪天 SchedulerX 满足不了需求,于是 PowerJob 就诞生了。
由于 SchedulerX 属于人民币产品,我这里就不过多介绍。PowerJob 官方也对比过其和 QuartZ、XXL-JOB 以及 SchedulerX。
QuartZ | xxl-job | SchedulerX 2.0 | PowerJob | |
定时类型 | CRON | CRON | CRON、固定频率、固定延迟、OpenAPI | CRON、固定频率、固定延迟、OpenAPI |
任务类型 | 内置 Java | 内置 Java、GLUE Java、Shell、Python 等脚本 | 内置 Java、外置 Java(FatJar)、Shell、Python 等脚本 | 内置 Java、外置 Java(容器)、Shell、Python 等脚本 |
分布式计算 | 无 | 静态分片 | MapReduce 动态分片 | MapReduce 动态分片 |
在线任务治理 | 不支持 | 支持 | 支持 | 支持 |
日志白屏化 | 不支持 | 支持 | 不支持 | 支持 |
调度方式及性能 | 基于数据库锁,有性能瓶颈 | 基于数据库锁,有性能瓶颈 | 不详 | 无锁化设计,性能强劲无上限 |
报警监控 | 无 | 邮件 | 短信 | WebHook、邮件、钉钉与自定义扩展 |
系统依赖 | JDBC 支持的关系型数据库(MySQL、Oracle...) | MySQL | 人民币 | 任意 Spring Data Jpa 支持的关系型数据库(MySQL、Oracle...) |
DAG 工作流 | 不支持 | 不支持 | 支持 | 支持 |
四、定时任务方案总结
-
单机定时任务的常见解决方案有
Timer
、ScheduledExecutorService
、DelayQueue
、Spring Task 和时间轮,其中最常用也是比较推荐使用的是时间轮。另外,这几种单机定时任务解决方案同样可以实现延时任务。 -
Redis 和 MQ 虽然可以实现分布式定时任务,但这两者本身不是专门用来做分布式定时任务的,它们并不提供较为完整和强大的分布式定时任务的功能。而且,两者不太适合执行周期性的定时任务,因为它们只能保证消息被消费一次,而不能保证消息被消费多次。因此,它们更适合执行一次性的延时任务,例如订单取消、红包撤回。实际项目中,MQ 延时任务用的更多一些,可以降低业务之间的耦合度。
-
Quartz、Elastic-Job、XXL-JOB 和 PowerJob 这几个是专门用来做分布式调度的框架,提供的分布式定时任务的功能更为完善和强大,更加适合执行周期性的定时任务。除了 Quartz 之外,另外三者都是支持任务可视化管理的。
-
XXL-JOB 2015 年推出,已经经过了很多年的考验。XXL-JOB 轻量级,并且使用起来非常简单。虽然存在性能瓶颈,但是,在绝大多数情况下,对于企业的基本需求来说是没有影响的。PowerJob 属于分布式任务调度领域里的新星,其稳定性还有待继续考察。ElasticJob 由于在架构设计上是基于 Zookeeper ,而 XXL-JOB 是基于数据库,性能方面的话,ElasticJob 略胜一筹。
-
这篇文章并没有介绍到实际使用,但是,并不代表实际使用不重要。我在写这篇文章之前,已经动手写过相应的 Demo。像 Quartz,我在大学那会就用过。不过,当时用的是 Spring 。为了能够更好地体验,我自己又在 Spring Boot 上实际体验了一下。如果你并没有实际使用某个框架,就直接说它并不好用的话,是站不住脚的。