This page looks best with JavaScript enabled

Kubernetes util tool 使用 Backoff 指數大漲

 ·  ☕ 6 min read

首先本文所有的 source code 基於 kubernetes 1.19 版本,所有 source code 為了版面的整潔會精簡掉部分 log 相關的程式碼,僅保留核心邏輯,如果有見解錯誤的地方,還麻煩觀看本文的大大們提出,感謝!

本篇文章是基於上一篇 Kubernetes util tool 使用 Backoff 抖了一下的拓展 ,主要會看到 kubernetes 除了透過 Jittered Backoff Manager Impl 實作了 BackoffManager 之外,還額外實作了另外一種 Exponential Backoff Manager Impl命名方式也十分 java xD。

我們也先複習一下上一張有提到的 BackoffManager,先以一個情境來描述為甚麼要backoff !
當 client 發送請求給 server 如果有可重試類型的失敗那我們就會重新發起請求,but!
這裡有個問題如果說我們寫的 client 一次打出去的請求有上百個甚至上千個呢? client 的重試可能會把 server 打掛,所以需要設計一個 backoff 讓重試的請求每次退後一點,每次退後一點(這裡的退後可以想像成重試時間垃長一點)。

1
2
3
4
// The BackoffManager is supposed to be called in a single-threaded environment.
type BackoffManager interface {
Backoff() clock.Timer
}

是不是非常的簡單的,實作 BackoffManager 的物件只要實作 Backoff() clock.Timer 就好了~

我們來看一下今天要講的主題 exponentialBackoffManagerImpl 的資料結構吧!

struct

1
2
3
4
5
6
7
8
type exponentialBackoffManagerImpl struct {
	backoff              *Backoff            //用來計算 back off 延遲時間,等等會看到Backoff物件是如何定義的。
	backoffTimer         clock.Timer         //用 backoff 計算出的延遲時間來建立 timer 
	lastBackoffStart     time.Time           //上一次觸發back off 是什麼時候
	initialBackoff       time.Duration       //初始化 back off 基礎延遲時間
	backoffResetDuration time.Duration       //設定 back off 多久沒被觸發要重置的時間
	clock                clock.Clock         //傳入現在時間
}

其中比較疑慮的參數應該是 backoff 竟然是一個物件,那…到底是長圓的還是扁的?就讓我們來瞧瞧吧!

Backoff

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type Backoff struct {
	
	Duration time.Duration                    //初始要延遲的時間
	
	Factor float64                            //延遲時間倍數因子,等等會看到實作
	
	Jitter float64                            //延遲抖動範圍
	
    
	Steps int                                 //主要用於 backoff 還有幾次,若是次數小於 0 次 延遲時間就不會再改變(除非有用 jitter)
	
    
	Cap time.Duration                         //主要用於設定 backoff 延遲時間成長的最大限制,如果到了最大限制 step 也將歸 0 
	    	                                  //換句話說就是延遲時間就不會再改變(除非有用 jitter)
}

看完了資料結構就要接著看 Backoff 的實作囉,這部分有點小複雜!

step

這裡我看了滿久的,基本上要有幾個情境才能看得出來他的應用,我們先來看程式碼等等再來看使用情境。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func (b *Backoff) Step() time.Duration {

	//如果 step 用完或是超出 duration 超出 cap (底下會看到為什麼)都會進到這個判斷式
	//另外有設定 jitter 的話就會丟給 jitter 運算延遲時間
	//不然都是回傳 Backoff 上一次計算好的延遲時間
	if b.Steps < 1 {
		if b.Jitter > 0 {
			return Jitter(b.Duration, b.Jitter)
		}
		return b.Duration
	}
	//每次使用過都要減少 step
	b.Steps--
	
	//採用 Backoff 上一次計算好的 Duration 數值
	duration := b.Duration

	//如果有設定延遲倍數因子的話,延遲時間就要乘上倍數因子
	//另外如果有設定 cap 數值的話計算完的延遲時間要額外判斷是否超過 cap
	//超過 cap 就以 cap 為 下一次的延遲時間,並且將 step 歸 0
	if b.Factor != 0 {
		b.Duration = time.Duration(float64(b.Duration) * b.Factor)
		if b.Cap > 0 && b.Duration > b.Cap {
			b.Duration = b.Cap
			b.Steps = 0
		}
	}
    
    //如果有設定 jitter 的話,需要 透過 jitter 計算本次延遲時間

	if b.Jitter > 0 {
		duration = Jitter(duration, b.Jitter)
	}
	return duration
}

Step function 大概會包含以下四種情境,需要搭配著 code 閱讀會比較好理解一點。

以下情境全部 b.Duration 保持 0.5 s

  1. 僅設定 step
    • step 設定 4 次
      程式流程會怎麼樣呢?

第一次近來來 step – (step=3),duration := b.Duration <0.5>,回傳 duration 0.5 s。

第二次近來來 step – (step=2),duration := b.Duration <0.5>,回傳 duration 0.5 s。

第三次近來來 step – (step=1),duration := b.Duration <0.5>,回傳 duration 0.5 s。

第四次近來來 step – (step=0),duration := b.Duration <0.5>,回傳 duration 0.5 s。


  1. 僅設定 step 與 factor
    • step 設定 4 次
    • factor 設定 2
      程式流程會怎麼樣呢?
  • 第一次近來來
    step – (step=3)
    duration := b.Duration <0.5>
    b.Duration = b.Duration<0.5> * factor <2>
    回傳 duration 0.5 s。

  • 第二次近來來
    step – (step=2)
    duration := b.Duration <1>
    b.Duration = b.Duration<1> * factor <2>
    回傳 duration 1 s。

  • 第三次近來來
    step – (step=1)
    duration := b.Duration <2>
    b.Duration = b.Duration<2> * factor <2>
    回傳 duration 2 s。

  • 第四次近來來
    step – (step=0)
    duration := b.Duration <2>
    b.Duration = b.Duration<4> * factor <2>
    回傳 duration 4 s。


  1. 設定 step 、 factor 與 Cap
    • step 設定 4 次
    • factor 設定 2
    • Cap 設定 2 s
      程式流程會怎麼樣呢?
  • 第一次近來
    step – (step=3)
    duration := b.Duration <0.5>
    b.Duration = b.Duration<0.5> * factor <2>
    回傳 duration 0.5 s。

  • 第二次近來
    step – (step=2)
    duration := b.Duration <1>
    b.Duration = b.Duration<1> * factor <2>
    回傳 duration 1 s。

  • 第三次近來
    step – (step=1)
    duration := b.Duration <2>
    b.Duration = b.Duration<2> * factor <2>
    b.Duration = b.Cap <2>
    b.Step = 0
    回傳 duration 2 s。

  • 第四次近來
    duration := b.Duration <2>
    回傳 duration 2 s。


  1. 設定 step 、 factor 、 Cap 與 jitter
    • step 設定 4 次
    • factor 設定 2
    • Cap 設定 2 s
    • jitter 設定 1 s

程式流程會怎麼樣呢?

  • 第一次近來
    step – (step=3)
    duration := b.Duration <0.5>
    b.Duration = b.Duration<0.5> * factor <2>
    假設 jitter 回傳的結果為 0.6
    回傳 duration 0.6 s。

  • 第二次近來
    step – (step=2)
    duration := b.Duration <1>
    b.Duration = b.Duration<1> * factor <2>
    假設 jitter 回傳的結果為 1.3
    回傳 duration 1.3 s。

  • 第三次近來
    step – (step=1)
    duration := b.Duration <2>
    b.Duration = b.Duration<2> * factor <2>
    b.Duration = b.Cap <2>
    b.Step = 0
    假設 jitter 回傳的結果為 2.6
    回傳 duration 2.6 s。

  • 第四次近來
    假設 jitter 回傳的結果為 4.1
    回傳 duration 4.1 s。


看完比較神秘的 backoff 物件後 要回來理解要如何把 Exponential Backoff Manager 建立出來,觀察他的 new function 有沒有偷藏什麼!

new function

在這之前先再次複習一下 exponentialBackoffManagerImpl 的資料結構

1
2
3
4
5
6
7
8
9
type exponentialBackoffManagerImpl struct {
	backoff              *Backoff            //用 back off 物件計算延遲時間, back off 透過 基礎延遲時間 ,延遲倍數因子 , step , jitter 以及 cap 等來計算出下一次要延遲多久。
	backoffTimer         clock.Timer         //用 backoff 計算出的延遲時間來建立對應的 timer 
	lastBackoffStart     time.Time           //上一次觸發back off 是什麼時候
	initialBackoff       time.Duration       //初始化 back off 基礎延遲時間
	backoffResetDuration time.Duration       //設定 back off 多久沒被觸發要重置的時間
	clock                clock.Clock         //傳入現在時間
}

複習完了 exponentialBackoffManagerImpl 的資料結構後,我們馬上來看看怎麼新增這個物件吧!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func NewExponentialBackoffManager(initBackoff, maxBackoff, resetDuration time.Duration, backoffFactor, jitter float64, c clock.Clock) BackoffManager {
	return &exponentialBackoffManagerImpl{
		backoff: &Backoff{                          //建構 backoff 物件
			Duration: initBackoff,                        //backoff 初始延遲時間
            
			Factor:   backoffFactor,                      //backoff 延遲倍數因子
            
			Jitter:   jitter,                             //backoff 抖動數值
			
			Steps: math.MaxInt32,                         //backoff 剩下幾次預設...很多次xDDD
            
			Cap:   maxBackoff,                            //backoff 延遲最大閥值
		},
		backoffTimer:         nil,                  //這時候還不會賦值,等到有人呼叫 backoff 的時候會計算出來 
		initialBackoff:       initBackoff,          //backoff 初始延遲時間
		lastBackoffStart:     c.Now(),              //用來記住上次什麼時候呼叫 backoff 的
		backoffResetDuration: resetDuration,        //設定多久 backoff 物件需要重置
		clock:                c,                    //用來對準時間
	}
}

Backoff

瞭解了 exponentialBackoffManagerImpl 怎麼新增物件後,趕緊來看實作的部分廢話就不多說直接上 code !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func (b *exponentialBackoffManagerImpl) Backoff() clock.Timer {
	//如果還沒設定 backoffTimer 那麼就從 getNextBackoff() 算一個延遲時間作為 backoff timer 
	//如果有了 backoff timer 就需要刷新 timmer
	if b.backoffTimer == nil {                        
		b.backoffTimer = b.clock.NewTimer(b.getNextBackoff())
	} else {
		b.backoffTimer.Reset(b.getNextBackoff())
	}
	return b.backoffTimer
}


func (b *exponentialBackoffManagerImpl) getNextBackoff() time.Duration {
	//如果現在時間 剪去 lastBackoffStart [上次 backoff 開始時間] 大於 backoffResetDuration [預設重置時間] 的話
	//    設定 backoff 物件的 step 為最大數值
	//    設定 backoff 物件的基礎延遲時間
	if b.clock.Now().Sub(b.lastBackoffStart) > b.backoffResetDuration {
		b.backoff.Steps = math.MaxInt32
		b.backoff.Duration = b.initialBackoff
	}
	//設定 lastBackoffStart [上次 backoff 開始時間] 為現在
	b.lastBackoffStart = b.clock.Now()
    //透過 backoff 計算需要延遲多久
	return b.backoff.Step()
}

exponentialBackoffManagerImpl Backoff function 的重點基本上在於 backoff 物件如何計算延遲時間,以及當執行時間超過 backoffResetDuration 就要將 backoff 物件設定成預設的閥值。

小結

簡單的來看這個 kubernetes utils tool ,屬於 wait package 的一部分主要實作 backoff 邏輯,防止 client 端因為大量的請求打壞 server ,透過上一篇提到的 jitter 結合本篇的 exponential Backoff 可以讓 user 只要輕鬆進行幾個簡單的設定例如 initBackoff 基礎延遲時間 ,backoffFactor 延遲倍數因子 , jitter 抖動範圍, maxBackoff 最大延遲時間 以及當多久沒有呼叫 backoff 需要重置的 resetDuration 就能獲得有 backoff 功能 clock ,整個過程相當的簡單~


Meng Ze Li
WRITTEN BY
Meng Ze Li
Kubernetes / DevOps / Backend

What's on this Page