基於golang的熔斷器調研

背景

從單體服務拆分到微服務過程當中,原來模塊間交互逐漸抽離成遠程調用,可能http,rpc,tcp,,,等等,那麼這些模塊在調用中必定存在某種依賴關係。這時一旦下游某個 服務超時或者down,請求量還很大的時候,那麼最壞狀況是上游服務也會所以超時或者down掉。它的上游也如此,如此「遞歸」同樣的出錯在微服務中叫作雪崩效應。那麼做爲微服務架構中的三劍客之一--熔斷,就是爲了解決這個問題,熔斷器像是一個保險絲。當咱們依賴的服務出現問題時,能夠及時容錯。一方面能夠減小依賴服務對自身訪問的依賴,防止出現雪崩效應;另外一方面下降請求頻率以方便上游儘快恢復服務。本文結合調研結果進行講解,目的也是爲了接下來的自研熔斷器作準備。git

由來

畢竟你們都見識過了2020美股熔斷時刻,想必這個詞也並不陌生。熔斷一詞最先是對電路中對引出線過載,使保險絲斷掉而進行保護機制,這一過程的描述。後來是指爲控制股票、期貨或其餘金融衍生產品的交易風險,爲其單日價格波動幅度規定區間限制,一旦成交價觸及區間上下限,交易則自動中斷一段時間。因此你們能夠認爲,所謂熔斷便是一種保護機制,出了事,緩一緩,等一等,稍後看看能不能恢復。github

1、功能簡介

熔斷器要最基本完成一下功能list:golang

功能點 說明
通路 當請求符合預期結果將和正常調用無區別
熔斷 當請求符合不預期結果必定時間內必定數量下將斷開方法執行再也不請求下游方法
半通路 在熔斷狀況下,當請求符合預期結果開始符合預期結果必定時間內必定數量下將放行部分請求到下游方法
熔斷休眠 在熔斷後一段時間後轉換成爲半通路

畫圖來說的話: ·初始爲close狀態,一旦遇到請求失敗時,會觸發熔斷檢測,熔斷檢測來決定是否將狀態從closed轉爲open。 ·當熔斷器爲open狀態時,會熔斷全部當前方法的執行,直到冷卻時間結束,會從open轉變爲half-open狀態。 ·當熔斷器爲half-open狀態時,以檢測時間爲週期去發送請求。請求成功則計數器加1,當計數器達到必定閾值時則轉爲close狀態;請求失敗則轉爲open狀態。json

2、調研方向:

調研中主要調研了目前比較主流的兩個熔斷器,在設計上有所不一樣,Hystrix更注重異步請求,統計收集更全面,雖然使用數佔上風可是總體過重,gobreaker在使用上更方便,總體及其輕量知足同步調用的各類需求,介紹完使用稍後咱們再來作對比。

一、hystrix

Hystrix的golang版本項目地址是:https://github.com/afex/hystrix-go
Hystrix是Netflix開源的一個限流熔斷的項目、主要有如下功能:
    1)隔離(線程池隔離和信號量隔離):限制調用分佈式服務的資源使用,某一個調用的服務出現問題不會影響其餘服務調用。
    2)優雅的降級機制:超時降級、資源不足時(線程或信號量)降級,降級後能夠配合降級接口返回託底數據。
    3)融斷:當失敗率達到閥值自動觸發降級(如因網絡故障/超時形成的失敗率高),熔斷器觸發的快速失敗會進行快速恢復。
    4)緩存:提供了請求緩存、請求合併實現。支持實時監控、報警、控制(修改配置)

一、添加配置

使用時建議首先添加配置,其中可配置項有:
Timeout                         int `json:"timeout"`
MaxConcurrentRequests           int `json:"max_concurrent_requests"`
RequestVolumeThreshold          int `json:"request_volume_threshold"`
SleepWindow                     int `json:"sleep_window"`
ErrorPercentThreshold           int `json:"error_percent_threshold"`

其中:緩存

字段 說明
Timeout 執行command的超時時間。默認時間是1000毫秒
MaxConcurrentRequests command的最大併發量 默認值是10
SleepWindow 當熔斷器被打開後,SleepWindow的時間就是控制過多久後去嘗試服務是否可用了。默認值是5000毫秒
RequestVolumeThreshold 一個統計窗口10秒內請求數量。達到這個請求數量後纔去判斷是否要開啓熔斷。默認值是20
ErrorPercentThreshold 錯誤百分比,請求數量大於等於RequestVolumeThreshold而且錯誤率到達這個百分比後就會啓動熔斷 默認值是50

添加配置eg:網絡

hystrix.ConfigureCommand(strategyName, hystrix.CommandConfig{
        Timeout:                1000,
        MaxConcurrentRequests:  100,
        RequestVolumeThreshold: 10,
        SleepWindow:            2,
        ErrorPercentThreshold:  50,
    })

二、方法調用

hystrix的使用是很是簡單的,同步執行,直接調用Do方法。架構

datatest := []byte{}
err := hystrix.Do("my_command", func() error {
   res, err := http.Get("www.baidu.com")
   if err != nil {
     return err
   }
   defer res.Body.Close()
 
   data, err := ioutil.ReadAll(res.Body)
   if err != nil {
     return err
   }
   datatest = data
   return nil
}, func(err error) error {
   fmt.Println("任務失敗")
   return nil
})

異步執行Go方法,內部實現是啓動了一個gorouting,若是想獲得自定義方法的數據,須要你傳channel來處理數據,或者輸出。返回的error也是一個channel併發

output := make(chan []byte, 1)
errors := hystrix.Go("my_command", func() error {
    res, err := http.Get("www.baidu.com")
    if err != nil {
      return err
    }
    defer res.Body.Close()
 
    data, err := ioutil.ReadAll(res.Body)
    if err != nil {
      return err
    }
    output <- data
    return nil
}, nil)
 
select {
case out := <-output:
    fmt.Println("任務成功", string(out))
case err := <-errors:
    fmt.Println("任務失敗", err.Errors())
}
 
/*
這裏能用得上得就是配置裏得併發數(不能聽任開太多協程)
同步調用底層用得也是異步調用,不過會本身處理等待errors chan
*/

這裏介紹一下hystrix的實現,首先總體流程如上圖,hystrix在初始化時就已經初始化了一個基於channel實現的令牌桶,當請求到達的時候,首先第一件事先判斷是否熔斷,此處降會調用熔斷對象(circuit.go)的AllowRequest() 函數,該函數從指標類(metrics.go)中統計出請求數和錯誤指數,前者判斷是否執行熔斷的必要條件,後者是充分條件(很好理解qps=1基本就告別限流了)。而後邏輯到了限流模塊,成功拿到令牌的繼續執行,沒有的走回調函數或者直接返回。執行中會記錄各類事件,好比請求數,成功數,失敗數,超時等。。。在這個請求終態時此時觸發golang的條件鎖(sync.Cond)喚醒協程返回令牌上報事件,事件模塊異步統計。 這裏值得提的是hystrix的統計模塊,採用滑動窗口計數(下圖), hystrix採用滑動窗口計數很好地解決了時間軸上的時間間隔問題同時還支持指標採集,可是在滑動窗口的實現上採用golang的map類型,過時元素將delete掉,這種方法只是標記此塊內存不可用並無真正釋放內存,須要設置gc指數進行回收。異步

二、gobreaker

項目地址爲:https://github.com/sony/gobreaker
gobreaker是索尼的開源的一個限流熔斷的項目、主要有如下功能:
    1)簡單的代碼結構,一共300多行,極容易閱讀。
    2)一樣提供降級機制:可是隻會根據當前連續錯誤數在熔斷時進行降級。
    3)融斷:當失敗數達到閥值自動觸發降級(如因網絡故障/超時形成的失敗率高),熔斷器觸發的快速失敗會進行快速恢復。

一、添加配置

type Settings struct {
    Name          string
    MaxRequests   uint32
    Interval      time.Duration
    Timeout       time.Duration
    ReadyToTrip   func(counts Counts) bool
    OnStateChange func(name string, from State, to State)
}
字段 說明
MaxRequests 最大請求數。當在最大請求數下,均請求正常的狀況下,會關閉熔斷器
interval 一個正常的統計週期。若是爲0,那每次都會將計數清零
timeout 進入熔斷後,能夠再次請求的時間
readyToTrip 判斷熔斷生效的鉤子函數(經過Counts 判斷是否開啓熔斷。須要自定義也能夠走默認配置)
onStateChagne 狀態變動的鉤子函數(看是否須要)
var cb *breaker.CircuitBreaker
cb = breaker.NewCircuitBreaker(breaker.Settings{
  Name:          "Get",
  MaxRequests:   100,
  Interval:      time.Second*2,
  Timeout:       time.Second*2
})

二、調用過程

熔斷器的執行操做,主要包括三個階段;①請求以前的斷定;②服務的請求執行;③請求後的狀態和計數的更新tcp

func Get(url string) ([]byte, error) {
    body, err := cb.Execute(func() (interface{}, error) {
        resp, err := http.Get(url)
        if err != nil {
            return nil, err
        }
 
        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            return nil, err
        }
 
        return body, nil
    })
    if err != nil {
        return nil, err
    }
 
    return body.([]byte), nil
}

gobreaker不一樣採用原子計數,加鎖統計,過時清零的操做,屬於簡單粗暴不支持指標採集,是對熔斷的這個操做(以下圖)的短小精悍的實現。 其中原子計數帶來的問題會比較大,以下圖,當59秒的時候尚未到達數量閾值,可是1:01時又來大量請求,此時由於進入下一時刻計數早就清空,這樣對於故障判斷的準確性帶來了挑戰

以上是對golang熔斷器的調研結果,相信不一樣場景會有不一樣需求,不一樣需求能夠對熔斷器來回選擇,下一篇會介紹自研的熔斷器。

相關文章
相關標籤/搜索