DelayedOperationPurgatory机制(一):TimingWheel

DelayedOperationPurgatory是一个相对独立的组件,主要功能是管理延迟操作。因jdk提供的timer和delayedQueue无法支持大量的定时任务,所以kafka自己实现了延迟组件。
TimingWheel是以储存定时任务的环形队列,底层使用数组实现,数组中的每个元素可以存放一个TimerTaskEntry中封装了真正的定时任务TimerTask。TimerTaskList使用expiration字段记录了整个TimerTaskList的超时时间,timerTask字段指向了对应的TimerTask任务,delayMs记录了任务的延迟时间,这三个字段是TimingWheel的基础
TimingWheel核心字段如下:

private[timer] class TimingWheel(
                                // 当前时间轮中一个时间格表示的时间跨度
                                tickMs: Long, 
                                // 当前时间轮的格子数,也是buckets数组的大小
                                wheelSize: Int, 
                                // 当前时间轮的创建时间
                                startMs: Long, 
                                // 各个层级时间轮中任务的总数
                                taskCounter: AtomicInteger, 
                                // 整个层级时间轮公用一个任务队列,其元素类型的是TimerTaskList
                                queue: DelayQueue[TimerTaskList]) {
  // 当前时间轮的时间跨度。当前时间轮只能处理时间范围在currentTime~currentTime + tickMs*WheelSize之间的定时任务,超过这个范围,则需要将任务添加到上层时间轮中。
  private[this] val interval = tickMs * wheelSize
  // 每个项都对应时间轮中的一个时间格,用于保存timerTaskList数组。在TimingWheel中,同一个TimerTaskList中的不同定时任务的到期时间可能不同,但是相差时间在一个时间格的范围内。
  private[this] val buckets = Array.tabulate[TimerTaskList](wheelSize) { _ => new TimerTaskList(taskCounter) }
  // 时间轮的指针,将整个时间轮划分为到期部分和未到期部分,在初始化时,currentTime被修剪成tickMs的倍数。
  private[this] var currentTime = startMs - (startMs % tickMs) // rounding down to multiple of tickMs

  // overflowWheel can potentially be updated and read by two concurrent threads through add().
  // Therefore, it needs to be volatile due to the issue of Double-Checked Locking pattern with JVM
  @volatile private[this] var overflowWheel: TimingWheel = null
}
// add方法实现了想时间轮中添加定时任务的功能,同时也会检测待添加的任务是否已经到期
  def add(timerTaskEntry: TimerTaskEntry): Boolean = {
    val expiration = timerTaskEntry.expirationMs
    // 任务已经被取消
    if (timerTaskEntry.cancelled) {
      // Cancelled
      false
    } else if (expiration < currentTime + tickMs) {
      // 任务已经到期
      // Already expired
      false
    } else if (expiration < currentTime + interval) {
      // Put in its own bucket
      // 任务在当前时间轮的跨度范围内
      // 按照任务的到期时间查到此任务属于的时间格,将任务添加到对应的TimerTaskList中
      val virtualId = expiration / tickMs
      val bucket = buckets((virtualId % wheelSize.toLong).toInt)
      bucket.add(timerTaskEntry)

      // Set the bucket expiration time
      // 真个时间轮表示的时间跨度是不变的,随着指针currentTime的后移,当前时间轮能处理的时间段也在不断后移,新来的timeTaskEntry会复用原来已经清理过的TimerTaskList。此时需要充值timerTaskList的到期时间,并将bucket重新添加到DelayQueue中,
      if (bucket.setExpiration(virtualId * tickMs)) {//设置bucket的到期时间
        // The bucket needs to be enqueued because it was an expired bucket
        // We only need to enqueue the bucket when its expiration time has changed, i.e. the wheel has advanced
        // and the previous buckets gets reused; further calls to set the expiration within the same wheel cycle
        // will pass in the same value and hence return false, thus the bucket with the same expiration will not
        // be enqueued multiple times.

        queue.offer(bucket)
      }
      true
    } else {
      // Out of the interval. Put it into the parent timer
      // 超出了当前时间轮的时间跨度范围,则将任务添加到上层时间轮中处理
      if (overflowWheel == null) 
          // 创建上层时间轮
          addOverflowWheel()
      overflowWheel.add(timerTaskEntry)
    }
  }

addOverflowWheel方法会创建上层时间轮,默认情况下,上层时间轮的tickMs是当前整个时间轮的时间跨度interval

  private[this] def addOverflowWheel(): Unit = {
    synchronized {
      if (overflowWheel == null) {
          //创建上层时间轮,注意上层时间轮的tickMs更大,wheelSize不变,则表示时间的跨度也越大
          // 随着上层时间轮表针的转动,任务还是会回到最的层的时间轮上,等待最终超时
        overflowWheel = new TimingWheel(
          tickMs = interval,
          wheelSize = wheelSize,
          startMs = currentTime,
          taskCounter = taskCounter,//任务计数器
          queue// 任务队列
        )
      }
    }
  }

advanceClock尝试推进昂前时间轮的表针currentTime,同时也会推动上层时间轮的表针

  def advanceClock(timeMs: Long): Unit = {
      // 尝试移动表针currentTime,推进可能不止一格
    if (timeMs >= currentTime + tickMs) {
      currentTime = timeMs - (timeMs % tickMs)

      // Try to advance the clock of the overflow wheel if present
      // 尝试推进上层时间轮的表针
      if (overflowWheel != null) overflowWheel.advanceClock(currentTime)
    }
  }

 

你可能感兴趣的:(Kafka)