baby sword‘s blog baby sword‘s blog
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)

zhengjian

不敢承担失去的风险,是不可能抓住梦想的
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)
  • 其他

    • 定时任务
      • 1. 什么时候会用到定时任务?
      • 2.单机定时任务选型
        • 自写代码
        • Timer
        • 实现案例
        • SechduledExecutorService
        • spring中的@Scheduled
        • 时间轮
      • 3. 分布式定时任务
        • XXL-JOB
    • protobuf工具
    • 浅入XXL-JOB
  • Spring

  • springboot

  • RPC

  • netty

  • mybatis

  • maven

  • 单元测试

  • 常见框架
  • 其他
xugaoyi
2022-12-23
目录

定时任务

# 1. 什么时候会用到定时任务?

  1. 某系统凌晨要进行数据备份。
  2. 某电商平台,用户下单半个小时未支付的情况下需要自动取消订单。
  3. 某媒体聚合平台,每 10 分钟动态抓取某某网站的数据为自己所用。
  4. 某博客平台,支持定时发送文章。
  5. 某基金平台,每晚定时计算用户当日收益情况并推送给用户最新的数据。
  6. ......

这些场景往往都要求我们在某个特定的时间去做某个事情

# 2.单机定时任务选型

# 自写代码

使用while+sleep

可以让具体执行相关逻辑的线程进行休眠指定的时间,就可以完成在指定时间后完成相应的任务

缺点:

  • 自己实现,难免会出现bug
  • 无法使用cron表达时,无法指定周期执行,只能执行一次时间滞后任务
  • 浪费线程资源

# Timer

Timer是jdk自带的以中国定时任务实现方式

原理:Timer 内部使用一个叫做 TaskQueue 的类存放定时任务,它是一个基于最小堆实现的优先级队列。TaskQueue 会按照任务距离下一次执行时间的大小将任务排序,保证在堆顶的任务最先执行。这样在需要执行任务时,每次只需要取出堆顶的任务运行即可!

# 实现案例

// 示例代码:
TimerTask task = new TimerTask() {
    public void run() {
        System.out.println("当前时间: " + new Date() + "n" +
                "线程名称: " + Thread.currentThread().getName());
    }
};
System.out.println("当前时间: " + new Date() + "n" +
        "线程名称: " + Thread.currentThread().getName());
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);


//输出:
当前时间: Fri May 28 15:18:47 CST 2021n线程名称: main
当前时间: Fri May 28 15:18:48 CST 2021n线程名称: Timer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

缺点:

  • 不支持cron表达式
  • 一个 Timer 一个线程,这就导致 Timer 的任务的执行只能串行执行,一个任务执行时间过长的话会影响其他任务(性能非常差),再比如发生异常时任务直接停止(Timer 只捕获了 InterruptedException )。

# SechduledExecutorService

image-20221223225955891

ScheduledExecutorService 是一个接口,有多个实现类,比较常用的是 ScheduledThreadPoolExecutor 。

ScheduledThreadPoolExecutor 本身就是一个线程池,支持任务并发执行。并且,其内部使用 DelayQueue 作为任务队列。

// 示例代码:
TimerTask repeatedTask = new TimerTask() {
    @SneakyThrows
    public void run() {
        System.out.println("当前时间: " + new Date() + "n" +
                "线程名称: " + Thread.currentThread().getName());
    }
};
System.out.println("当前时间: " + new Date() + "n" +
        "线程名称: " + Thread.currentThread().getName());
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
long delay  = 1000L;
long period = 1000L;
executor.scheduleAtFixedRate(repeatedTask, delay, period, TimeUnit.MILLISECONDS);
Thread.sleep(delay + period * 5);
executor.shutdown();
//输出:
当前时间: Fri May 28 15:40:46 CST 2021n线程名称: main
当前时间: Fri May 28 15:40:47 CST 2021n线程名称: pool-1-thread-1
当前时间: Fri May 28 15:40:48 CST 2021n线程名称: pool-1-thread-1
当前时间: Fri May 28 15:40:49 CST 2021n线程名称: pool-1-thread-2
当前时间: Fri May 28 15:40:50 CST 2021n线程名称: pool-1-thread-2
当前时间: Fri May 28 15:40:51 CST 2021n线程名称: pool-1-thread-2
当前时间: Fri May 28 15:40:52 CST 2021n线程名称: pool-1-thread-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

优缺点:

  • 解决了timer中线程问题
  • 无法使用cron表达式

# 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()));
}

1
2
3
4
5
6
7
8

注意:

  • 需要在启动类上加上@EnableScheduled注解
  • 底层使用的是SechduledExecutorService,但是默认只有一个线程,也即是所有的定时任务都由同一个线程执行,如果一个任务占用的时间过久,会影响下一个任务的执行时间。

那么怎么自定义线程池,让执行定时任务的线程有自己的名字和线程数量呢?

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
    private final int POOL_SIZE = 10;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

        threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
        threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
        threadPoolTaskScheduler.initialize();

        scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

如上面所示,我们需要创建一个配置类,并是实现SchedulingConfigurer 重写对应的方法就可以了。

优缺点:

  • 可以使用cron了,是一大优点

  • 但是功能比较单一

# 时间轮

Kafka、Dubbo、ZooKeeper、Netty 、Caffeine 、Akka 中都有对时间轮的实现。

https://javaguide.cn/system-design/schedule-task.html#spring-task

# 3. 分布式定时任务

分布式环境下单机定时任务的问题:

①如何保证集群部署得机器上定时任务不被重复执行

②如何动态调整定时任务的执行时间(不重启服务)

③部署定时任务的机器发生故障时,如何进行故障转移

④如何对集群中哪些进行了定时任务进行监控

⑤单机性能出现瓶颈,如何进行扩展

# XXL-JOB

一款优秀的轻量级分布式任务调度框架,支持任务可视化管理、弹性扩容缩容、任务失败重试和告警、任务分片等功能

image-20221223232231169

解决了很多quartz的不足之处

image-20221223232251337

架构设计:

image-20221223232303848

从上图可以看出,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;
    }
}

1
2
3
4
5
6
7
8
9
10
11

还可以使用注解的方式:

@XxlJob("myAnnotationJobHandler")
public ReturnT<String> myAnnotationJobHandler(String param) throws Exception {
  //......
  return ReturnT.SUCCESS;
}

1
2
3
4
5
6

官网 (opens new window)

优缺点总结:

  • 优点:开箱即用(学习成本比较低)、与 Spring 集成、支持分布式、支持集群、内置了 UI 管理控制台。
  • 缺点:不支持动态添加任务(如果一定想要动态创建任务也是支持的,参见:xxl-job issue277open in new window (opens new window))
编辑 (opens new window)
上次更新: 2024/02/22, 14:03:19
protobuf工具

protobuf工具→

最近更新
01
spark基础
02-22
02
mysql读写分离和分库分表
02-22
03
数据库迁移
02-22
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式