定时任务:使用延时队列可以实现定时任务,例如每隔一段时间执行某个操作,或者在特定的时间点执行某个操作。
注:延时队列和定时任务的区别:
定时任务一般是有固定时间周期的,有明确的触发时间。而延时任务一般没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内执行,没有执行周期。
定时任务一般批处理操作多个任务,延时任务一般是单个任务。
使用场景:
-
批量处理数据:批量统计上个月的某个数据。
-
时间驱动的场景:某个时间点发送短信、邮件。
-
固定频率的场景:每隔5分钟需要执行一次
-
...
实现
在Java中,实现定时任务的方式有很多,最简单的在线程中通过JDK自带Timer,Thread.sleep睡眠线程,或者采用SpringBoot中的@Schedule注解,或者采用定时线程池ScheduledExecutorService来实现,又或者采用Spring Boot中集成Quartz框架实现。
cron表达式
通过这个生成器,可以在线生成任务调度。比如Quartz的Cron表达式,对Quartz Cron 表达式的可视化双向解析和生成。
定时任务的所有方法都必须是void,采用SpringBoot中的@Schedule注解方式,如果是在SpringBoot项目中,需要在启动类上添加@EnableScheduling来开启定时任务:
Springboot中cron表达式只支持6位数,这种方式只适合在单机上使用。
由于单机式任务调度只能在一台机器上运行,如果程序或者系统出现异常就会导致功能不可用,还有单机处理极限问题等,于是,分布式的定时任务就出现了。
Quartz的集群方案
Quartz通常有三部分组成:调度器(Scheduler)、任务(JobDetail)、触发器(Trigger)
定时任务的集群方案是使用数据库来实现,使用了数据库锁来保证只有在一台机器上触发。
quartz采用了悲观锁的方式对triggers表进行行加锁,以保证任务同步的正确性。一旦某一个节点上面的线程获取了该锁,那么这个Job就会在这台机器上被执行,同时这个锁就会被这台机器占用。同时另外一台机器也会想要触发这个任务,但是锁已经被占用了,就只能等待,直到这个锁被释放。之后会看trigger状态,如果已经被执行了,则不会执行了。
quartz的分布式调度策略是以数据库为边界资源的一种异步策略。各个调度器都遵守一个基于数据库锁的操作规则从而保证了操作的唯一性,同时多个节点的异步运行保证了服务的可靠。
局限性:集群特性对于高CPU使用率的任务效果很好,但是对于大量的短任务,各个节点都会抢占数据库锁,这样就出现大量的线程等待资源(这种情况随着节点的增加会越来越严重)
另外,quartz的分布式只是解决了高可用的问题,并没有解决任务分片的问题,还是会有单机处理的极限。