iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0

本日參考 : https://arrow-kt.io/learn/resilience/circuitbreaker/

把 function 當作參數,我們可以作更多的想像跟操作。而且是在編譯的階段就可以決定驗證,而不用等到 runtime 注入,例如 spring boot。甚至可以讓我們的 function 更有還原能力(Resilience)

今天就來談談 Arrow KT 的 Resilience 模組,是在 Cloud Native 時代很重要的 pattern

來點小菜 : Repeat(重覆) 與 Retry (重試)

當遇到某些不利的情況時,我們常常需要對 function 進行重試或重複。通常,重試或重複並不是立即進行的,而是基於某種策略進行的。例如,從網路請求中提取內容時,如果失敗,我們可能希望使用指數後退演算法(exponential backoff algorithm)重試,最長15秒或5次嘗試,以先到者為主。

此模式的額外補充

重試模式(Retry Pattern) 在雲計算中的應用

Schedule

Schedule 允許你定義和組合強大而簡單的策略。使用 Schedule 有兩個步驟

  1. 建立一個策略,指定重複的次數和延遲
  2. 然後我們根據指定的動作運行這個計劃。有兩種方法可以這麼做
suspend fun download(url: Url): ByteArray = 
  Schedule.recurs(5).retry{
    url.readBytes()
  }

Repeat

同理我們也可利用 Schedule 作到 repeat

最簡單的策略是重複十次。如果我們調用 repeat,則相同的操作將被執行十次,如果我們調用 retry,則最多嘗試十次直到成功。

fun <A> recurTenTimes() = Schedule.recurs<A>(10)

指數後退演算法(exponential backoff algorithm)是一種標準的重試與外部服務通信的操作演算法,例如網絡請求。大致上,這表示著嘗試之間的延遲按給定因子增加。

@ExperimentalTime
val exponential = Schedule.exponential<Unit>(250.milliseconds)

大菜-電路斷路器 (Circuit Breaker)

當服務過載時,額外的互動可能只會使其過載狀態惡化。當與如 Schedule 這樣的重試機制結合時,可能變成滾雪球的放大。有時,在高峰流量期間,僅使用 backoff 重試策略可能不夠。為了防止這些過載的資源過度載入,電路斷路器(Circuit Breaker)通過快速失敗來保護該服務。這有助於我們實現穩定性並防止分散式系統中的滾雪球的錯誤放大。

https://ithelp.ithome.com.tw/upload/images/20231005/20135701gjoqnHn2CW.png

🔀關閉

這是電路斷路器開始的狀態
在此狀態下正常進行請求:
當出現異常時,它將增加失敗計數器
當失敗計數器達到給定的 maxFailures 閾值時,斷路器移至打開狀態。
成功的請求將將失敗計數器重置為零

⏹️打開

在此狀態下,電路斷路器會快速短路/失敗所有請求。
如果在配置的 resetTimeout 之後發出請求,斷路器將移至半打開狀態,允許一個請求作為測試通過。

⤴️半打開

當允許一個請求作為測試請求通過時,電路斷路器處於此狀態。
在測試請求仍在運行時所做的所有其他請求都將短路/快速失敗。
如果測試請求成功,電路斷路器將跳回關閉,並將resetTimeout和failures計數也重置為初始值。
如果測試請求失敗,電路斷路器將返回打開,並將resetTimeout乘以exponentialBackoffFactor,直至設置的maxResetTimeout

如此一來,我們就可以利用 Circuit Breaker 來保護我們的服務

@ExperimentalTime
suspend fun main(): Unit {
  suspend fun apiCall(): Unit {
    println("apiCall . . .")
    throw RuntimeException("Overloaded service")
  }

  val circuitBreaker = CircuitBreaker(
    openingStrategy = OpeningStrategy.Count(2),
    resetTimeout = 2.seconds,
    exponentialBackoffFactor = 1.2,
    maxResetTimeout = 60.seconds,
  )

  suspend fun <A> resilient(schedule: Schedule<Throwable, *>, f: suspend () -> A): A =
    schedule.retry { circuitBreaker.protectOrThrow(f) }

  // simulate getting overloaded
  Either.catch {
    resilient(Schedule.recurs(5), ::apiCall)
  }.let { println("recurs(5) apiCall twice and 4x short-circuit result from CircuitBreaker: $it") }

  // simulate reset timeout
  delay(2000)
  println("CircuitBreaker ready to half-open")

  // retry once,
  // and when the CircuitBreaker opens after 2 failures
  //    retry with exponential back-off with same time as CircuitBreaker's resetTimeout
  val fiveTimesWithBackOff = Schedule.recurs<Throwable>(1) andThen
    Schedule.exponential(2.seconds) and Schedule.recurs(5)

  Either.catch {
    resilient(fiveTimesWithBackOff, ::apiCall)
  }.let { println("exponential(2.seconds) and recurs(5) always retries with actual apiCall: $it") }
}

後記

在 JVM 的世界裡,有 Resillane4j 這樣的函式庫提供類似的功能。但 Arrow KT 的能夠更 Kotlin 而且是能跟著 KMM

每日一推 (G)I-DLE

Lion 舞台
Yes


上一篇
D21: 寫在 JCConf 前 - Data class 與 Arrow KT Lens
下一篇
D23: Effective Kotlin 第三部分 - 效率優化
系列文
讓 Kotlin 程式碼更道地 - 談 Effective Kotlin 與相關的 Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言