何为定时器,说白了就是指定一个延迟时间,到期执行,就像咱们早上定的闹铃同样,天天定点提醒咱们起床;固然在咱们各个系统中也是无处不在,好比定时备份数据,定时拉取文件,定时刷新数据等等;定时器工具也是层出不穷好比Timer,ScheduledExecutorService,Spring Scheduler,HashedWheelTimer(时间轮),Quartz,Xxl-job/Elastic-job等;本文将对这些定时器工具作个简单介绍和对比,都在哪些场景下使用。mysql
Timer能够说是JDK提供最先的一个定时器了,使用简单,功能也相对来讲比较简单;能够指定固定时间后触发,固定时间点触发,固定频率触发;算法
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(System.currentTimeMillis() + " === task1");
}
}, 1000, 1000);
复制代码
时间默认为毫秒,表示延迟一秒后执行任务,而且频率为1秒执行任务;Timer内部使用TaskQueue存听任务,使用TimerThread单线程用来执行任务:spring
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
复制代码
TimerThread内部是一个while(true)循环,不停的从TaskQueue中获取任务执行;固然每次添加到TaskQueue中的任务会进行排序,经过nextExecutionTime来进行排序,这样TimerThread每次均可以获取到最近执行的任务;
Timer有两大缺点:sql
正由于Timer存在的一些缺点,JDK1.5出现了新的定时器ScheduledExecutorService;数据库
JDK1.5提供了线程池的功能,ScheduledExecutorService是一个接口类,具体实现类是ScheduledThreadPoolExecutor继承于ThreadPoolExecutor;bash
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + " === " + System.currentTimeMillis() + " === task1");
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
复制代码
比起Timer能够配置多个线程去执行定时任务,同时异常任务并不会中断ScheduledExecutorService,线程池的几个核心配置:多线程
ScheduledExecutorService中添加的任务会被包装成一个ScheduledFutureTask类,同时将任务放入DelayedWorkQueue队列中是一个BlockingQueue;相似Timer也会根据加入任务触发时间的前后进行排序,而后线程池中的Worker会到Queue中获取任务执行;运维
Spring提供了xml和注解方式来配置调度任务,以下面xml配置:分布式
<!-- 建立一个调度器 -->
<task:scheduler id="scheduler" />
<!-- 配置任务类的bean -->
<bean id="helloTask" class="com.spring.task.HelloTask"></bean>
<task:scheduled-tasks scheduler="scheduler">
<!-- 每2秒执行一次 -->
<task:scheduled ref="helloTask" method="say" cron="0/2 * * * * ?" />
</task:scheduled-tasks>
复制代码
Spring提供了cron表达式的支持,而且能够直接配置执行指定类中的指定方法,对使用者来讲更加方便和简单;可是其内部仍是使用的ScheduledThreadPoolExecutor线程池;ide
Netty提供的一个定时器,用于定时发送心跳,使用的是时间轮算法;HashedWheelTimer是一个环形结构,能够类比成一个时钟,整个环形结构由一个个小格组成,每一个小格能够存放不少任务,随着时间的流逝,指针转动,而后执行当前指定格子中的任务;任务经过取模的方式决定其应该放在哪一个格子,有点相似hashmap;
HashedWheelTimer hashedWheelTimer = new HashedWheelTimer(1000, TimeUnit.MILLISECONDS, 16);
hashedWheelTimer.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
System.out.println(System.currentTimeMillis() + " === executed");
}
}, 1, TimeUnit.SECONDS);
复制代码
其中初始化的三个参数分别是:
如上面实例配置的参数,每一格时长1秒,时间轮总共16格,若是延迟1秒执行,那就放到编号1的格子中,从0开始;若是延迟18秒,那么会放到编号为2的格子中,同时指定remainingRounds=1,表示第几轮被调用,每转一轮remainingRounds-1,知道remainingRounds=0才会被执行;
以上介绍的几种定时器都是进程内的调度,而Quartz提供了分布式调度,全部被调度的任务均可以存放到数据库中,每一个业务节点经过抢占式的方式去获取须要执行的任务,其中一个节点出现问题并不影响任务的调度;
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/quartz" />
<property name="user" value="root" />
<property name="password" value="root" />
</bean>
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="schedulerName" value="myScheduler"></property>
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:quartz.properties" />
<property name="triggers">
<list>
<ref bean="firstCronTrigger" />
</list>
</property>
</bean>
复制代码
更多关于Quartz的介绍能够参考本人以前的文章:
固然Quartz自己也有不足的地方:底层调度依赖数据库的悲观锁,谁先抢到谁调度,这样会致使节点负载不均衡;还有调度和执行耦合在一块儿,致使调度器会受到业务的影响;
正由于Quartz存在着不少不足的地方,基于Quartz实现的分布式调度解决方案出现了包括Xxl-job/Elastic-job等;
总体思路:调度器和执行器拆成不一样的进程,调度器仍是依赖Quartz自己的调度方式,可是调度的并非具体业务的QuartzJobBean,而是统一的一个RemoteQuartzJobBean,在此Bean中经过远程调用执行器去执行具体业务Bean;具体的执行器在启动时注册到注册中心(如Zookeeper)中,调度器能够在注册中心(如Zookeeper)获取执行器信息,并经过相关的负载算法指定具体的执行器去执行;
还提供了运维管理界面,能够管理任务,好比像xxl-job:
固然还有更多其余的功能,此处就不在介绍了,能够直接去查看官网;
其实总体能够分为两大类:进程内定时器包括和分布式调度器;
进程内定时器:Timer,ScheduledExecutorService,Spring Scheduler,HashedWheelTimer(时间轮);
分布式调度器:Quartz,Xxl-job/Elastic-job; 因此首先根据须要仅仅只须要进程内的定时器,仍是须要分布式调度; 其次在进程内Timer基本能够被淘汰了,彻底可使用ScheduledExecutorService来代替,若是系统使用了Spring那固然应该使用Spring Scheduler; 下面重点看看ScheduledExecutorService和HashedWheelTimer,ScheduledExecutorService内部使用的是DelayedWorkQueue,任务的新增、删除会致使性能降低;而HashedWheelTimer并不受任务数量限制,因此若是任务不少而且任务执行时间很短好比心跳,那么HashedWheelTimer是最好的选择;HashedWheelTimer是单线程的,若是任务很少而且执行时间过长,影响精确度,而ScheduledExecutorService可使用多线程这时候选择ScheduledExecutorService更好; 最后分布式调度器里面Quartz和Xxl-job/Elastic-job,对分布式调度要求不高的状况下才会选择Quartz,否则都应该选择Xxl-job/Elastic-job