简易个人网站模板,网站备案查询不出来,福州互联网公司排行榜,线上免费推广平台都有哪些在日常的项目开发中#xff0c;多多少少都会涉及到一些定时任务的需求。例如每分钟扫描超时支付的订单#xff0c;每小时清理一次数据库历史数据#xff0c;每天统计前一天的数据并生成报表#xff0c;定时去扫描某个表的异常信息#xff08;最终一致性的方案也可能涉及多多少少都会涉及到一些定时任务的需求。例如每分钟扫描超时支付的订单每小时清理一次数据库历史数据每天统计前一天的数据并生成报表定时去扫描某个表的异常信息最终一致性的方案也可能涉及定时启用某个业务开关等等。下面对一些Java中常用的定时任务做一些简单的介绍。
1.Java自带解决方式
Java 中自带的解决方案Timer。
1.Timer
使用 Timer创建 java.util.TimerTask 任务在 run 方法中实现业务逻辑。通过 java.util.Timer 进行调度支持按照固定频率执行。所有的 TimerTask 是在同一个线程中串行执行相互影响。也就是说对于同一个 Timer 里的多个 TimerTask 任务如果一个 TimerTask 任务在执行中其它 TimerTask 即使到达执行的时间也只能排队等待。如果有异常产生线程将退出整个定时任务就失败。在编码的时候因为是单线程阻塞式的行为编写代码的时候阿里巴巴插件会提示采用ScheduledExecutorService来代替Timer。
注在分布式锁的redission的实现中用到watchdog就是基于这种方式
import java.util.Timer;
import java.util.TimerTask;
public class TestTimerTask { public static void main(String[] args) {TimerTask timerTask new TimerTask() {Overridepublic void run() {System.out.println(hell world);}};Timer timer new Timer();timer.schedule(timerTask, 10, 3000);}
}
2.ScheduledExecutorService
基于线程池设计的定时任务解决方案每个调度任务都会分配到线程池中的一个线程去执行解决 Timer 定时器无法并发执行的问题支持 fixedRate 和 fixedDelay。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TestTimerTask {public static void main(String[] args) {ScheduledExecutorService ses Executors.newScheduledThreadPool(5);//按照固定频率执行每隔5秒跑一次ses.scheduleAtFixedRate(new Runnable() {Overridepublic void run() {System.out.println(hello fixedRate);}}, 0, 5, TimeUnit.SECONDS);//按照固定延时执行上次执行完后隔3秒再跑ses.scheduleWithFixedDelay(new Runnable() {Overridepublic void run() {System.out.println(hello fixedDelay);}}, 0, 3, TimeUnit.SECONDS);}
} 2.Spring 中自带的解决方案
这个应该就是大多数项目会采用的方式了也是比较主流的方式。Springboot 中提供了一套轻量级的定时任务工具 Spring Task通过注解可以很方便的配置支持 cron 表达式、fixedRate、fixedDelay。
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
Component
EnableScheduling
public class MyTask {/*** 每分钟的第30秒跑一次*/Scheduled(cron 30 * * * * ?)public void task1() throws InterruptedException {System.out.println(hello cron);}/*** 每隔5秒跑一次*/Scheduled(fixedRate 5000)public void task2() throws InterruptedException {System.out.println(hello fixedRate);}/*** 上次跑完隔3秒再跑*/Scheduled(fixedDelay 3000)public void task3() throws InterruptedException {System.out.println(hello fixedDelay);}
}
Spring Task 相对于上面提到的两种解决方案最大的优势就是支持 cron 表达式可以处理按照标准时间固定周期执行的业务比如每天几点几分执行。这个cron表达式也不用刻意的去记住只需要大概理解然后找一个cron生成的网站就行。
同样这个Scheduled的方式默认也是单线程的如果想采用多线程可以自定义线程池来结合使用也可以用Async的方式指定线程池来使用。这里就不多做介绍了
3.业务幂等解决方案
现在的应用基本都是分布式部署所有机器的代码都是一样的前面介绍的 Java 和 Spring 自带的解决方案都是进程级别的每台机器在同一时间点都会执行定时任务。这样会导致需要业务幂等的定时任务业务有问题比如每月定时给用户推送消息就会推送多次。
于是很多应用很自然的就想到了使用分布式锁的解决方案。即每次定时任务执行之前先去抢锁抢到锁的执行任务抢不到锁的不执行。怎么抢锁又是五花八门比如使用 DB、zookeeper、redis。
1.使用 DB 或者 Zookeeper 抢锁
使用 DB 或者 Zookeeper 抢锁的架构差不多原理如下 定时时间到了在回调方法里先去抢锁。抢到锁则继续执行方法没抢到锁直接返回。执行完方法后释放锁
示例代码如下 import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
Component
EnableScheduling
public class MyTask {/*** 每分钟的第30秒跑一次*/Scheduled(cron 30 * * * * ?)public void task1() throws Exception {String lockName task1;if (tryLock(lockName)) {System.out.println(hello cron);releaseLock(lockName);} else {return;}}private boolean tryLock(String lockName) {//TODOreturn true;}private void releaseLock(String lockName) {//TODO}
}
当前的这个设计仔细一点的同学可以发现其实还是有可能导致任务重复执行的。比如任务执行的非常快A 这台机器抢到锁执行完任务后很快就释放锁了。B 这台机器后抢锁还是会抢到锁再执行一遍任务。因为数据库的io也是需要时间在极限或者并发高的情况下就会出现等等情况。所以由此衍生出来Redis
2.使用 redis 抢锁
使用 redis 抢锁其实架构上和 DB/zookeeper 差不多不过 redis 抢锁支持过期时间不用主动去释放锁并且可以充分利用这个过期时间解决任务执行过快释放锁导致任务重复执行的问题架构如下 示例代码如下
Component
EnableScheduling
public class MyTask {/*** 每分钟的第30秒跑一次*/Scheduled(cron 30 * * * * ?)public void task1() throws InterruptedException {String lockName task1;if (tryLock(lockName, 30)) {System.out.println(hello cron);releaseLock(lockName);} else {return;}}private boolean tryLock(String lockName, long expiredTime) {//TODOreturn true;}private void releaseLock(String lockName) {//TODO}
} 同样如果基于这个的话可以直接用开源的redission。提供了包括分布式锁限流自动续约时间自动删除锁避免死锁等。 4.使用 Quartz
Quartz 是一套轻量级的任务调度框架只需要定义了 Job任务Trigger触发器和 Scheduler调度器即可实现一个定时调度能力。支持基于数据库的集群模式可以做到任务幂等执行。其实关联的东西很多而且对数据库要新建很多业务要求的表如果业务不是很复杂用spring的就足够了
Quartz 支持任务幂等执行其实理论上还是抢 DB 锁我们看下 quartz 的表结构 其中QRTZ_LOCKS 就是 Quartz 集群实现同步机制的行锁表其表结构如下
--QRTZ_LOCKS表结构CREATE TABLE QRTZ_LOCKS (LOCK_NAME varchar(40) NOT NULL,PRIMARY KEY (LOCK_NAME)) ENGINEInnoDB DEFAULT CHARSETutf8;--QRTZ_LOCKS记录----------------- | LOCK_NAME |----------------- | CALENDAR_ACCESS || JOB_ACCESS || MISFIRE_ACCESS || STATE_ACCESS || TRIGGER_ACCESS |-----------------
可以看出 QRTZ_LOCKS 中有 5 条记录代表 5 把锁分别用于实现多个 Quartz Node 对 Job、Trigger、Calendar 访问的同步控制。
5.开源任务调度中间件 上面提到的解决方案在架构上都有一个问题那就是每次调度都需要抢锁特别是使用 DB 和 Zookeeper 抢锁性能会比较差一旦任务量增加到一定的量就会有比较明显的调度延时。还有一个痛点就是业务想要修改调度配置或者增加一个任务得修改代码重新发布应用。
于是开源社区涌现了一堆任务调度中间件通过任务调度系统进行任务的创建、修改和调度这其中国内最火的就是 XXL-JOB 和 ElasticJob。
1.ElasticJob
ElasticJob 是一款基于 Quartz 开发依赖 Zookeeper 作为注册中心、轻量级、无中心化的分布式任务调度框架目前已经通过 Apache 开源。
ElasticJob 相对于 Quartz 来说从功能上最大的区别就是支持分片可以将一个任务分片参数分发给不同的机器执行。架构上最大的区别就是使用 Zookeeper 作为注册中心不同的任务分配给不同的节点调度不需要抢锁触发性能上比 Quartz 上强大很多架构图如下 开发上也比较简单和 springboot 结合比较好可以在配置文件定义任务如下
elasticjob:regCenter:serverLists: localhost:2181namespace: elasticjob-lite-springbootjobs:simpleJob:elasticJobClass: org.apache.shardingsphere.elasticjob.lite.example.job.SpringBootSimpleJobcron: 0/5 * * * * ?timeZone: GMT08:00shardingTotalCount: 3shardingItemParameters: 0Beijing,1Shanghai,2GuangzhouscriptJob:elasticJobType: SCRIPTcron: 0/10 * * * * ?shardingTotalCount: 3props:script.command.line: echo SCRIPT Job: manualScriptJob:elasticJobType: SCRIPTjobBootstrapBeanName: manualScriptJobBeanshardingTotalCount: 9props:script.command.line: echo Manual SCRIPT Job:
实现任务接口如下 Component
public class SpringBootShardingJob implements SimpleJob {Overridepublic void execute(ShardingContext context) {System.out.println(分片总数context.getShardingTotalCount() , 分片号context.getShardingItem() , 分片参数context.getShardingParameter());}
} 同时ElasticJob 还提供了一个简单的 UI可以查看任务的列表同时支持修改、触发、停止、生效、失效操作。 ElasticJob 暂不支持动态创建任务。
2.XXL-JOB
XXL-JOB 是一个开箱即用的轻量级分布式任务调度系统其核心设计目标是开发迅速、学习简单、轻量级、易扩展在开源社区广泛流行。
XXL-JOB 是 Master-Slave 架构Master 负责任务的调度Slave 负责任务的执行架构图如下 XXL-JOB 接入也很方便不同于 ElasticJob 定义任务实现类是通过XxlJob 注解定义 JobHandler。安装和集成springboot可自行百度需要SQL数据库来存储一些相关的表通杀也提供了动态的数据UI展示
实例代码
Component
public class SampleXxlJob {private static Logger logger LoggerFactory.getLogger(SampleXxlJob.class);/*** 1、简单任务示例Bean模式*/XxlJob(demoJobHandler)public ReturnTString demoJobHandler(String param) throws Exception {XxlJobLogger.log(XXL-JOB, Hello World.);for (int i 0; i 5; i) {XxlJobLogger.log(beat at: i);TimeUnit.SECONDS.sleep(2);}return ReturnT.SUCCESS;}/*** 2、分片广播任务*/XxlJob(shardingJobHandler)public ReturnTString shardingJobHandler(String param) throws Exception {// 分片参数ShardingUtil.ShardingVO shardingVO ShardingUtil.getShardingVo();XxlJobLogger.log(分片参数当前分片序号 {}, 总分片数 {}, shardingVO.getIndex(), shardingVO.getTotal());// 业务逻辑for (int i 0; i shardingVO.getTotal(); i) {if (i shardingVO.getIndex()) {XxlJobLogger.log(第 {} 片, 命中分片开始处理, i);} else {XxlJobLogger.log(第 {} 片, 忽略, i);}}return ReturnT.SUCCESS;}
}XXL-JOB 相较于 ElasticJob最大的特点就是功能比较丰富可运维能力比较强不但支持控制台动态创建任务还有调度日志、运行报表等功能。强力推荐 还有一些企业级别的组件例如阿里云任务调度 SchedulerX。这个如果有需要就请自行了解了。
感谢