JStorm与Storm源码分析(八)--计时器工具-mk-timer

来源:互联网 发布:分布式通信 java 编辑:程序博客网 时间:2024/05/21 14:40

Storm使用计时器线程来处理一些周期性调度事件.
与计时器相关的操作主要有:创建计时器线程、查看线程是否活跃、向线程中加入新的待调度事件、取消计时器线程
mk-timer方法用于创建一个计时器线程.
其基本思想:首先定义一个优先级队列,队列的元素类型<目标执行时间,执行函数,序列号>,然后在当前时间大于队列中的目标执行时间时,取出队列中的元素并调用其执行函数.

;;mk-timer定义如下:(defnk mk-timer [:kill-fn (fn [& _] )]  ;;定义函数变量queue为优先级队列PriorityQueue,其初始化为10,  ;;同时传入一个比较器Comparator,并按照元素的第一部分(目标执行时间)进行比较,  ;;将队列中的元素按照执行时间由小到大排列  (let [queue (PriorityQueue. 10                              (reify Comparator                                (compare [this o1 o2]                                  (- (first o1) (first o2))                                  )                                (equals [this obj]                                  true                                  )))        ;;定义active变量用于表明该定时器是否处于活跃状态        active (atom true)        ;;lock为Object对象,用来为队列对象的操作加锁.        ;;这是由于向队列中插入元素以及处理队列中的元素这两项操作在不同线程里执行的。        lock (Object.)        ;;notifier为信号量.计时器线程在结束时,会调用该信号量的release方法释放.        notifier (Semaphore. 0)        ;;定义计时器线程.线程的执行函数为一个while循环体        timer-thread (Thread.                      (fn []                        (while @active                          (try                            ;;调用队列的peek函数得到队列的一个元素,:as操作将元素的所有项放入变量elem中,                            ;;所以elem的内容:<目标执行时间,执行函数,元素符号>                            (let [[time-millis _ _ :as elem] (locking lock (.peek queue))]                              ;;如果elem不为null,并且当前时间大于或等于元素的执行时间time-millis,                              ;;则调用poll方法从queue中获取该元素,并调用second方法获得元素的第二列,                              ;;即待执行函数afn,然后调用待执行函数afn,                              (if (and elem (>= (current-time-millis) time-millis))                                ;; imperative to not run the function                                ;; inside the timer lock                                ;; otherwise, it's possible to deadlock                                 ;;if function deals with other locks                                ;; (like the submit lock)                                (let [afn (locking lock (second (.poll queue)))]                                  (afn))                                ;;线程睡眠                                (if time-millis ;; if any events are scheduled                                  ;; sleep until event generation                                  ;; note that if any recurring events are scheduled                                   ;; then we will always go through                                  ;; this branch, sleeping only the exact                                   ;;necessary amount of time                                  (Time/sleep (- time-millis (current-time-millis)))                                  ;; else poll to see if any new event was scheduled                                  ;; this is in essence the response time for                                   ;; detecting any new event schedulings when                                  ;; there are no scheduled events                                  (Time/sleep 1000))                                ))                            ;;捕获异常,并对异常进行处理                            (catch Throwable t                              ;; because the interrupted exception can be wrapped                               ;;in a runtimeexception                              (when-not (exception-cause? InterruptedException t)                                ;;如果不为InterruptedException异常,则调用kill-fn                                (kill-fn t)                                ;;设置active为false                                (reset! active false)                                ;;并最终抛出异常                                (throw t))                              )))                        ;;当线程结束循环后,调用notifier的release方法                        (.release notifier)))]    ;;将线程设置为后台线程并赋予其最高优先级,然后启动线程    (.setDaemon timer-thread true)    (.setPriority timer-thread Thread/MAX_PRIORITY)    (.start timer-thread)    ;;设置返回值,这里返回的是一个哈希表,它包含了定义计时器线程    ;;优先级队列、活跃状态和锁对象等信息.返回后,外部环境就可使用这些信息了    {:timer-thread timer-thread     :queue queue     :active active     :lock lock     :cancel-notifier notifier}))

下面函数用来检测timer(定时器)的active变量

;;若定时器处于非活跃状态,则抛出异常(defn- check-active! [timer]  (when-not @(:active timer)    (throw (IllegalStateException. "Timer is not active"))))

schedule函数

;;schedule函数用于将一个事件注册到定时器中,;;其参数包括定时器,延迟执行时间,事件执行函数,;;以及确定是否检测定时器活跃的参数check-active(默认值为true)(defnk schedule [timer delay-secs afn :check-active true]  ;;调用check-active!函数查看当前定时器活跃情况  (when check-active (check-active! timer))  ;;调用uuid函数产生一个随机数来表示该事件的事件号.  (let [id (uuid)        ;;获取定时器的队列对象,并向其添加元素.        ^PriorityQueue queue (:queue timer)]    (locking (:lock timer)      ;;向队列添加<目标执行时间,执行函数,序列号>,      ;;其中事件的目标执行时间为当前时间加上delay-secs(延迟时间)      (.add queue [(+ (current-time-millis) (* 1000 (long delay-secs))) afn id])      )))

上述schedule方法只执行以此注册函数,而schedule-recurring则可以按照设定的时间反复执行

;;其基本思路:执行完当前函数后调用函数schedule再次注册自身.;;参数:定时器,第一次延迟执行时间,事件循环执行的间隔时间,时间执行函数(defn schedule-recurring [timer delay-secs recur-secs afn]  (schedule timer            delay-secs            (fn this []              (afn)              (schedule timer recur-secs this :check-active false)) ; this avoids a race condition with cancel-timer            ));;PS:重新注册函数时将不会定时器的活跃状态进行检查.;;否则,同时调用cancle-timer和schedule函数可能会有竞争,;;而先调用cancel-timer然后调用schedule方法,则check-active函数又会抛出异常

cancel-timer方法用于结束一个定时器

;;它会调用定时器notifier的acquire操作,等待定时器正常结束(defn cancel-timer [timer]  (check-active! timer)  (locking (:lock timer)    (reset! (:active timer) false)    (.interrupt (:timer-thread timer)))  (.acquire (:cancel-notifier timer)))

注:学习李明老师等的Storm源码分析的笔记整理。
欢迎关注下面二维码进行技术交流:
这里写图片描述

原创粉丝点击