詳述カフカ(VII)煉獄

DelayedOperationPurgatory遅延バッファは、要求(遅延要求)するために使用されます。いわゆる遅延要求は、これらの条件は、リクエストがですぐに処理することができない瞬間を満たしていません。このようなセットのACK =すべて一度のACK = PRODUCE要求のすべてを、設定すると、その要求は、ISRのすべてのコピーが復帰へのメッセージを受信した後、待たなければならない、要求を処理し、この時間IOスレッドは、他のブローカーを書くために待機する必要がありますその結果。要求はすぐに処理できない場合は、一時的に煉獄に保存されます。終了条件が満たされた際に、IOスレッドは、対応する応答キューに後で要求と応答ネットワーク・スレッドを処理し続けます。

1.タイムラウンドの定義

プログラムのタイムクロックラウンドは、ソフトウェアの設計に実際の生活の概念となり、主なアイデアは、ときポインタが時にすべてのステップ、クロック・サイクル(例えば12時間制など)、および(例えば、一度行くためのクロック1秒など)のステップを定義することですあなたは、現在のクロックは、タスクの規模に取り付けられ、下に示すように、全体的な構造を実行してしまいます。

図からわかるように、計算のための時間は、タイミングタスクを格納することが可能であるように、起因規模でタスクに関連するタイミングへのポインタまたは参照によって行うクロックの組み立て、およびタスクに類似していますそして、時間のデカップリングは、クロック成分が難しいことではありません、データはこれらのタスクに格納されている方法は、時間が重要な丸いプログラムです。

利点は、時間ポインタホイールホイールは、対応する格子は、タスク内のキューへの格子点の有効期限が切れている時間を指していることである場合は、不要な走査タイミングの無意味を減らすことができます。

2.カフカの時間ラウンド

TimingWheelと呼ばれる時間のクラスでカフカホイールは、そのような構造は、タイミングタスク、内部アレイを実装を格納する円形のキューである、配列を格納するために使用されTimerTaskList、TimerTaskList環状の双方向リンクリストエントリTimerTaskEntryパッケージタイミングタスクTimerTaskを、TimerTaskListとオブジェクト両方のタイムアウトフィールドにTimerTaskEntryは、TimerTaskをdelayMsフィールドが記録タスクの遅延時間が使用され、3つのコアクラスカフカ時間ホイールを実現しました。

  • TimingWheelは:時刻ホイールを示し、多層複数TimingWheelオブジェクトラウンド時間も通常存在します。
  • TimerTaskListは:遅延タスクを格納する配列へのオブジェクト、TimerTaskList、tは時間枠が満了される(のみ[T〜T + 10msの]間隔で時間グリッドタスクの有効期限に保存することができる時間グリッドを示し10msの時間グリッド区画)は、各セルは、フロントフレーム時間内に前進する時の有効期限、タスクの時間グリッドを有する時間グリッドの後に満了します。
  • TimerTaskを:遅延タスクを表します。
  • SystemTimer:カフカはTimningWheel、タイミング管理タスクを実行するためのパッケージ内部のタイマを実装しました。

以下は、時間作業工程カフカホイールに例を示します。

  1. タイムホイール初期化:二乗車輪の数初期時間間隔、ポインタの初期化時、時間グリッドに対応するバケットのアレイを作成するために、間隔の間隔の合計を計算します。
  2. 遅延タスクを追加します。現在のラウンド合計時間の作業で時間間隔に基づいて判断した場合、タスクは現在のレベルで保存することができ、キャンセルされた日付の時点で有効期限が切れているスレッドプールにタスクを置くかどうかを判断するか、トップタイムラウンド時間を追加しますラウンドと時間のラウンドにタスクを追加し、再試行してください。
  3. タイムホイールのダウングレードは: 300msのタイマー再実行タスクの後に、現在の時刻は、階層ホイール10である各時間フレーム、時間ラウンドトップは1msの時間フレーム間隔は、全体の時間は、ホイールが10ミリ秒で、タスクを保存することはできませんすることができます。第二層の時間ラウンド、10ミリ秒の時間枠の間隔を作成し、全体の時間のラウンドは100ミリ秒で、まだタスクを保存することはできません。ある期間にわたって、TimerTaskList期限(時間分割)、第3層は、時間のラウンドを作成し、時間グリッド間隔は100ミリ秒、1000ミリ秒ホイールの全体の時間、タスクが時間ラウンドで第三層、第3の時間グリッドに格納され、この時間でありますタスクも90msのは、実行することはできません。この時点で、再度、タイマタスクラウンドまでの時間を追加トップ円形または格納された時間条件を満たすことができない、第二層、第九のレイヤ時間グリッドの第二ラウンドに格納され、その後、タイミングタスク時間ラウンド時間に加え、タスクなど、延滞タスク実行スレッドプールは、時間のラウンドに配置されますまで。

3. DelayedOperationPurgatoryコア構造

番組以下のチャートは、DelayedProducePurgatoryはタイマであり、一方がウォッチャーのマップであり、2つのコア・コンポーネントを有します。対応DelayedProduceは、2つの役割があります:1はDelayedOperationあり、それはまた、TimerTaskをです。

プロセスProduceRequestは、DelayedProduceオブジェクトがウォッチャーに追加され生成されるたびに、それはまた、TimerTaskをのように、タイマに加えました。

最後に、これは次のDelayedProduceが会うを取得することができ、それはタイマースーパーリーグでは、クライアントのタイムアウトエラーに戻ったことがあります。かつての場合は、タイマーからタスクを削除するには、TimerTask.cancelを呼び出す必要があります。

書き込み絵は、ここで説明しました

使用の3.1タイマー

書き込み絵は、ここで説明しました

上に示したタイマーのカフカの使用:

  • 调用者(也就是DelayedOperationPurgatory)不断调用timer.add函数加入新的Task;另一方面,不是Timer内部有线程驱动,而是有一个外部线程ExpiredOperationReaper,不断调用timer.advanceClock函数,来驱动整个Timer。
  • 同时,当某一个TimerTask到期之后,不是由Timer直接执行此TimerTask。而是交由一个executor,来执行所有过期的TimerTask。之所以这么做,是因为不能让TimerTask的执行阻塞Timer本身的进度。

总结一下:这里有2个外部线程,一个驱动Timer,一个executor,专门用来执行过期的Task。这2个线程,都是DelayedOperationPurgatory的内部变量。

3.2 Timer内部结构

下面先看一下Timer的内部结构:

書き込み絵は、ここで説明しました

  • Timer是最外层类,表示一个定时器。其内部有一个TimingWheel对象,TimingWheel是有层次结构的,每个TimingWheel可能有parent TimingWheel(这个原理就类似我们生活中的水表,不同表盘有不同刻度)。
  • TimingWheel是一个时间刻度盘,每个刻度上有一个TimerTask的双向链表,称之为一个bucket。同1个bucket里面的所有Task,其过期时间相等。因此,每1个bucket有一个过期时间的字段。
  • 所有TimingWheel共用了一个DelayedQueue,这个DelayedQueue存储了所有的bucket,而不是所有的TimerTask。

3.3 Timer的3大核心功能

对于一个Timer来说,有3大功能:

  1. 添加:把一个TimerTask加入Timer
  2. 过期:时间到了,执行所有那些过期的TimerTask
  3. 取消:时间未到,取消TimerTask。把TimerTask删除

3.3.1 添加

//Timer的对外接口add
class Timer(taskExecutor: ExecutorService, tickMs: Long = 1, wheelSize: Int = 20, startMs: Long = System.currentTimeMillis) {
  ...
  def add(timerTask: TimerTask): Unit = {
    readLock.lock()
    try {
      addTimerTaskEntry(new TimerTaskEntry(timerTask))  //把TimerTask包装成一个TimerTaskEntry节点,添加进去
    } finally {
      readLock.unlock()
    }
  }

  private def addTimerTaskEntry(timerTaskEntry: TimerTaskEntry): Unit = {
    if (!timingWheel.add(timerTaskEntry)) {
      // Already expired or cancelled
      if (!timerTaskEntry.cancelled) //关键点:如果该TimerTask不能被加入timingWheel,说明其已经过期了。同时该任务又没有被取消,则直接执行此任务
        taskExecutor.submit(timerTaskEntry.timerTask)
    }
  }

//关键的TimingWheel的add函数
  def add(timerTaskEntry: TimerTaskEntry): Boolean = {
    val expiration = timerTaskEntry.timerTask.expirationMs

    if (timerTaskEntry.cancelled) {
      // 如果该任务已经被取消,则不加入timingWheel
      false
    } else if (expiration < currentTime + tickMs) {
      //如果该Task的过期时间已经小于当前时间 + 基本的tick单位(1ms),说明此任务已经过期了,不用再加入timingWheel
      false
    } else if (expiration < currentTime + interval) {
      // 如果过期时间 < 当前时间 + interval,则说明当前的刻度盘可以表达此过期时间。这里的interval就是当前刻度盘所能表达的最大时间范围:tickMs * wheelSize

      //这里tickMs设置的是1ms,所以virtualId = expiration
      val virtualId = expiration / tickMs

      //关键的hash函数:根据过期时间,计算出bucket的位置
      val bucket = buckets((virtualId % wheelSize.toLong).toInt) 

      //把该Task加入bucket
      bucket.add(timerTaskEntry)

      //同一个bucket,所有task的expiration是相等的。因此,expiration相等的task,会hash到同1个bucket,然后此函数只第1次调用会成功
      if (bucket.setExpiration(virtualId * tickMs)) {
        queue.offer(bucket) //该桶只会被加入delayedQueue1次
      }
      true
    } else {
      //过期时间超出了currentTime + interval,说明该过期时间超出了当前刻度盘所能表达的最大范围,则调用其parent刻度盘,来试图加入此Task
      if (overflowWheel == null) addOverflowWheel()
      overflowWheel.add(timerTaskEntry)
    }
  }

3.3.2 过期

正如上面的图所示,外部线程每隔200ms调用1次advanceClock,从而驱动时钟不断运转。在驱动过程中,发现过期的Task,放入executors执行。

  private class ExpiredOperationReaper extends ShutdownableThread(
    "ExpirationReaper-%d".format(brokerId),
    false) {

    override def doWork() {
      //不断循环,每200ms调用1次advanceClock
      timeoutTimer.advanceClock(200L)  
      ...
    }
}


  def advanceClock(timeoutMs: Long): Boolean = {
    //关键点:这里判断一个Task是否过期,其实还是用delayedQueue来判断的。而不是TimingWheel本事
    //过期的bucket会从队列的首部出对
    var bucket = delayQueue.poll(timeoutMs, TimeUnit.MILLISECONDS)
    if (bucket != null) {
      writeLock.lock()
      try {
        while (bucket != null) {
          //把timingWheel的进度,调整到队列首部的bucket的过期时间,也就是当前时间
          timingWheel.advanceClock(bucket.getExpiration())

          //清空bucket,执行bucket中每个Task的过期函数(执行方式就是把所有这些过期的Task,放入executors)
          bucket.flush(reinsert)

          //再次从队列首部拿下1个过期的bucket。如果没有,直接返回null。该函数不会阻塞
          bucket = delayQueue.poll()
        }
      } finally {
        writeLock.unlock()
      }
      true
    } else {
      false
    }
  }

//TimingWheel
  def advanceClock(timeMs: Long): Unit = {
    if (timeMs >= currentTime + tickMs) {
      //更新currentTime(把timeMs取整,赋给currentTime)
      currentTime = timeMs - (timeMs % tickMs)

      //更新parent timingWheel的currentTime
      if (overflowWheel != null) overflowWheel.advanceClock(currentTime)
    }
  }

3.3.3 取消

Task的取消,并不是在Timer里面实现的。而是TimerTask自身,定义了一个cancel函数。所谓cancel,就是自己把自己用TimerTaskEntryList这个双向链表中删除。

trait TimerTask extends Runnable {

  val expirationMs: Long // timestamp in millisecond

  private[this] var timerTaskEntry: TimerTaskEntry = null

  def cancel(): Unit = {
    synchronized {
      if (timerTaskEntry != null) timerTaskEntry.remove()
      timerTaskEntry = null
    }
  }

  def remove(): Unit = {
    var currentList = list
    while (currentList != null) {
      currentList.remove(this)  //从链表中,把自己删掉
      currentList = list
    }
  }

 //remove函数。因为是双向链表,所以删除不需要遍历链表。删除复杂度是O(1)
  def remove(timerTaskEntry: TimerTaskEntry): Unit = {
    synchronized {
      timerTaskEntry.synchronized {
        if (timerTaskEntry.list eq this) {
          timerTaskEntry.next.prev = timerTaskEntry.prev
          timerTaskEntry.prev.next = timerTaskEntry.next
          timerTaskEntry.next = null
          timerTaskEntry.prev = null
          timerTaskEntry.list = null
          taskCounter.decrementAndGet()
        }
      }
    }
  }

3.4 TimingWheel本质

3.4.1 DelayedQueue

从上面代码中可以看到,添加/取消的时间复杂度都是O(1)。并且在上面的代码中,大家可以看出,TimingWheel.advanceClock()函数里面其实什么都没做,就只是更新了一下所有刻度盘的currentTime。真正的判断哪个Task过期的逻辑,其实是用DelayedQueue来判断的,而不是通过TimingWheel判断的。

那TimingWheel在此处到底起了一个什么作用呢?

前面讲过,expiration相等的TimerTask,会组成一个双向链表,称之为一个bucket。DelayedQueue的每个节点,放入的就是一个bucket,而不是单个的TimerTask。过期的判断,就是通过DelayedQueue来实现的。

書き込み絵は、ここで説明しました

3.4.2 计算bucket

但这里有个问题,当我要把某个TimerTask加入这个DelayedQueue时,我怎么计算出所在的bucket呢??

答案就是TimingWheel。这里TimingWheel本质上就是充当了一个hash函数,通过Task的expiration time,hash出所在的bucket。如下图所示:

跟水表一样,刻度盘有多级,每个刻度上对应一个bucket。根据expiration计算所属的bucket的代码,就在上面的add函数里面。

書き込み絵は、ここで説明しました

3.4.3 刻度盘的层次

每个刻度盘都有个变量,记录currentTime。所有刻度盘的currentTime基本是相等的(会根据自己的tickMs取整)。advanceClock函数,就是更新这个currentTime。

在这里,不同的刻度盘单位其实都是ms。只是不同的刻度盘上,1格所代表的时间长度是不一样的。这里有个关系:

parent 刻度盘的1格表示的时间长度 = child刻度盘的整个表盘所表示的时间范围

在代码中,即:

  private[this] def addOverflowWheel(): Unit = {
    synchronized {
      if (overflowWheel == null) {
        overflowWheel = new TimingWheel(
          tickMs = interval,  //parent刻度盘的刻度tickMs = child刻度盘的整个表盘范围 interval(tickMs * wheelSize)
          wheelSize = wheelSize,
          startMs = currentTime,
          taskCounter = taskCounter,
          queue
        )
      }
    }
  }

因此,从底往上: tickMs = 1ms, wheelSize = 20格

第1层刻度盘能表达的expiration的范围就是[currentTime, currentTime + tickMs*wheelSize]; //每1格1ms,总共20ms范围

第2层刻度盘能表达的expiration的范围就是[currentTime, currentTime + tickMs*wheelSize*wheelSize]; //每1格20ms,总共400ms范围

第3层刻度盘能表达的expiration的范围就是[currentTime, currentTime + tickMs*wheelSize*wheelSize*WheelSize]; //每1格400ms,总共8000ms范围

这里有个细节:

  private[this] val reinsert = (timerTaskEntry: TimerTaskEntry) => addTimerTaskEntry(timerTaskEntry)

  //advanceClock函数
  bucket.flush(reinsert)

严格来讲,同1个bucket里面装的并不是expiration精确相等的Task,而是在当前刻度下,经过取整,落在同1个bucket里面的所有Task。

最底层的精度是1ms,往上1层精度是20ms,意味着相差在20ms以内的所有Task都会落到同1个bucket里面,以此类推。。。

所以当从上层的TimingWheel的bucket中取出来的Task,要重新从最低层加入TimingWheel体系,只有最底层都加不进去,才说明真正的过期!!

 

リリース8元の記事 ウォンの賞賛0 ビュー7278

おすすめ

転載: blog.csdn.net/fedorafrog/article/details/103991698