何爲定時器,說白了就是指定一個延遲時間,到期執行,就像咱們早上定的鬧鈴同樣,天天定點提醒咱們起牀;固然在咱們各個系統中也是無處不在,好比定時備份數據,定時拉取文件,定時刷新數據等等;定時器工具也是層出不窮好比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;segmentfault
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。