<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          圖解 Go 微服務(wù)熔斷器

          共 7985字,需瀏覽 16分鐘

           ·

          2021-08-13 17:06

          微服務(wù)中,熔斷器是必須的。

          前言

          說明

          1. 原文中 Circuit Breakers 這里翻譯為了熔斷器,大家也可以理解叫為斷路器

          2. fallback本文翻譯為兜底,大家也可以理解為容錯補(bǔ)救。

          本文翻譯自https://ioshellboy.medium.com/circuit-breakers-in-golang-1779da9b001,由于本人翻譯水平有限,翻譯不當(dāng)之處煩請指出。希望大家看了這篇文章能有所幫助。感謝捧場。

          什么是熔斷器

          當(dāng)你看到 “熔斷器” 這個術(shù)語時,你會想到什么呢?

          從圖片字面意思理解是使用一個錘子破壞了一個電路。

          我們一般都會在自己家里安裝熔斷器,以阻止異常的電流從電網(wǎng)流向家里。在開始“微服務(wù)的熔斷器”之前,讓我們先看看它是如何工作的。

          如上圖所示,一個典型的熔斷器裝置有 2 個主要部件:

          1. 用火線緊緊包裹的軟鐵芯
          2. 觸體。只要接觸點能夠形成一個連接點,電流就會從外部電源流向我們的房子。相反,如果連接斷開,電流就停止流動。

          當(dāng)電流通過纏繞在軟鐵芯周圍的導(dǎo)線時,軟鐵芯就像一塊電磁鐵,當(dāng)流過它的電流高于預(yù)期的安培時,電磁鐵就會變得強(qiáng)大到足以吸引鄰近的觸點,從而導(dǎo)致短路。

          你一定在想,這與微服務(wù)架構(gòu)有什么關(guān)系呢?在我看來,這是高度相關(guān)的,正如我們下面將要看到的!

          微服務(wù)架構(gòu)中的級聯(lián)故障

          微服務(wù)架構(gòu)已經(jīng)很好地取代了單體架構(gòu),但是為了使我們的系統(tǒng)具有高度的彈性,我們還需要解決一些關(guān)鍵問題。

          微服務(wù)的一個問題是級聯(lián)故障。舉一個例子來更好地理解它。

          在上圖中,參與者調(diào)用我們的主服務(wù),它依賴于上游服務(wù)——A,B,C。現(xiàn)在假定,服務(wù) A 是一個讀取量較大的系統(tǒng),它依賴于數(shù)據(jù)庫。這個數(shù)據(jù)庫有其自身的局限性,并且在過載時,可能導(dǎo)致連接重置。這個問題不僅會影響服務(wù) A 的性能,還會影響主服務(wù)。

          這就是人們所說的“一塊臭肉壞了整鍋湯”,喝過這鍋湯的人肯定會有同感。下面讓我們用一個例子來驗證這一點。

          讓我們構(gòu)建一個 Netflixisc 應(yīng)用程序。其中一個微服務(wù)負(fù)責(zé)提供feed頁面的電影服務(wù)。此服務(wù)還依賴于推薦服務(wù)為用戶提供適當(dāng)?shù)耐扑]。

          // Recommendation Service
          func main() {
           logGoroutines()
           http.HandleFunc("/recommendations", recoHandler)
           log.Fatal(http.ListenAndServe(":9090", nil))
          }

          func logGoroutines() {
           ticker := time.NewTicker(500 * time.Millisecond)
           done := make(chan bool)
           go func() {
            for {
             select {
             case <-done:
              return
             case t := <-ticker.C:
              fmt.Printf("\n%v - %v", t, runtime.NumGoroutine())
             }
            }
           }()
          }

          func recoHandler(w http.ResponseWriter, r *http.Request) {
           a := `{"movies": ["Few Angry Men""Pride & Prejudice"]}`
           w.Write([]byte(a))
          }

          推薦服務(wù)暴露一個路由接口 /recommendations,它返回一個推薦電影列表,同時每 500 毫秒打印一次 goroutine 的數(shù)量。

          // Movies App
          type MovieResponse struct {
           Feed           []string
           Recommendation []string
          }

          func main() {
           http.HandleFunc("/movies", fetchMoviesFeedHandler)
           log.Fatal(http.ListenAndServe(":8080", nil))
          }

          func fetchMoviesFeedHandler(w http.ResponseWriter, r *http.Request) {
           mr := MovieResponse{
            Feed: []string{"Transformers""Fault in our stars""The Old Boy"},
           }
           rms, err := fetchRecommendations()
           if err != nil {
            w.WriteHeader(500)
           }
           mr.Recommendation = rms
           bytes, err := json.Marshal(mr)
           if err != nil {
            w.WriteHeader(500)
           }
           w.Write(bytes)
          }

          func fetchRecommendations() ([]string, error) {
           resp, err := http.Get("http://localhost:9090/recommendations")
           if err != nil {
            return []string{}, err
           }
           defer resp.Body.Close()
           body, err := ioutil.ReadAll(resp.Body)
           if err != nil {
            return []string{}, err
           }
           var mvsr map[string]interface{}
           err = json.Unmarshal(body, &mvsr)
           if err != nil {
            return []string{}, err
           }
           mvsb, err := json.Marshal(mvsr["movies"])
           if err != nil {
            return []string{}, err
           }
           var mvs []string
           err = json.Unmarshal(mvsb, &mvs)
           if err != nil {
            return []string{}, err
           }
           return mvs, nil
          }

          電影服務(wù)暴露一個路由 /movies,它返回電影列表和推薦列表。為了獲取推薦,它反過來調(diào)用上游的推薦服務(wù)

          通過此設(shè)置,讓我們以每秒 100 個請求的速率訪問電影服務(wù),持續(xù) 3 秒鐘。在 99% 的毫秒范圍內(nèi),我們可以獲得 100% 的成功。這是預(yù)期的,因為只提供靜態(tài)數(shù)據(jù)。

          現(xiàn)在,假設(shè)推薦服務(wù)的響應(yīng)時間過長,并在 recoHandler 添加20秒的等待時間,然后重新進(jìn)行測試。成功率會下降,而響應(yīng)時間也會開始受到影響。此外,在測試期間阻塞在推薦服務(wù)上的goroutine數(shù)量將急劇增加。

          推薦服務(wù)的停工時間影響了終端用戶,因為本來可以提供給他的電影feed列表都沒有提供。這正是級聯(lián)故障對我們的系統(tǒng)造成的影響。

          熔斷器救援

          熔斷器是一個非常簡單但相當(dāng)重要的概念,因為它可以讓我們保持服務(wù)的高可用性。熔斷器有三種狀態(tài):

          • Closed State 關(guān)閉狀態(tài)

          關(guān)閉狀態(tài)是指數(shù)據(jù)通過的時候連接處關(guān)閉的狀態(tài)。這是我們的理想狀態(tài),其中上游服務(wù)正如預(yù)期的那樣工作。

          • Open State 開放狀態(tài)

          打開狀態(tài)指的是由于上游服務(wù)未按預(yù)期響應(yīng)而導(dǎo)致電路短路的狀態(tài)。這種短路可以避免上游服務(wù)在已經(jīng)掙扎的情況下不堪重負(fù)。此外,下游服務(wù)的業(yè)務(wù)邏輯可以更快地獲得上游可用性狀態(tài)的反饋,而無需等待上游的響應(yīng)。

          • Half Open State 半開狀態(tài)

          如果熔斷器是打開狀態(tài),我們希望它在上游服務(wù)再次可用時立即關(guān)閉它。雖然你可以通過手動干預(yù)來實現(xiàn),但首選的方法應(yīng)該是在電路最后一次打開,讓一些請求延遲之后通過電路,

          如果這些請求請求上游服務(wù)成功,我們就可以安全地接通整個鏈路。

          另一方面,如果這些請求失敗,熔斷器仍然處于打開狀態(tài)。

          熔斷器的狀態(tài)圖如下:

          1. 初始狀態(tài)下熔斷器是關(guān)閉的,當(dāng)故障超過配置的閾值,則會打開
          2. 熔斷器是打開狀態(tài),在經(jīng)過一段熔斷時間后,部分會打開
          3. 如果熔斷器是半開的,它可以
            • 再次打開,如果允許通過的請求也失敗了
            • 關(guān)閉,如果允許通過的請求成功響應(yīng)

          熔斷器在 Golang 的應(yīng)用

          雖然有多個庫可供選擇,但最常用的是 hystrix[1]。正如文檔建議的那樣,hystrix 是 Netflix 設(shè)計的一個延遲和容錯庫,用于隔離遠(yuǎn)程系統(tǒng)、服務(wù)和第三方庫的訪問,阻止級聯(lián)故障,并在不可避免的故障發(fā)生的復(fù)雜分布式系統(tǒng)中實現(xiàn)恢復(fù)能力。

          Hystrix 熔斷器的實現(xiàn)取決于以下配置:

          1. 超時 ー 上游服務(wù)響應(yīng)的等待時間
          2. 最大并發(fā)請求 ー 上游服務(wù)允許調(diào)用的最大并發(fā)
          3. 請求容量閾值 ー 在熔斷之前的請求數(shù),斷路器在需要更改狀態(tài)時無法評估的請求數(shù)量
          4. 睡眠窗口 ー 開放狀態(tài)與半開放狀態(tài)之間的延遲時間
          5. 誤差百分比閾值ー熔斷器短路時的誤差百分比閾值

          接下來讓我們在電影和推薦示例中使用它,并在獲取推薦時實現(xiàn)熔斷器模式。

          var downstreamErrCount int
          var circuitOpenErrCount int

          func main() {
           downstreamErrCount = 0
           circuitOpenErrCount = 0
           hystrix.ConfigureCommand("recommendation", hystrix.CommandConfig{
            Timeout: 100,
            RequestVolumeThreshold: 25,
            ErrorPercentThreshold:  5,
            SleepWindow:            1000,
           })
           http.HandleFunc("/movies", fetchMoviesFeedHandlerWithCircuitBreaker)
           log.Fatal(http.ListenAndServe(":8080", nil))
          }

          func fetchMoviesFeedHandlerWithCircuitBreaker(w http.ResponseWriter, r *http.Request) {
           mr := MovieResponse{
            Feed: []string{"Transformers""Fault in our stars""The Old Boy"},
           }
           
           output := make(chan bool, 1)
           errors := hystrix.Go("recommendation", func() error {
            // talk to other services
            rms, err := fetchRecommendations()
            if err != nil {
             return err
            }
            mr.Recommendation = rms
            output <- true
            return nil
           }, func(err error) error {
               // 寫你的fallback(兜底)邏輯
            return nil
           })

           select {
           case err := <-errors:
            if err == hystrix.ErrCircuitOpen {
             circuitOpenErrCount = circuitOpenErrCount + 1
            } else {
             downstreamErrCount = downstreamErrCount + 1
            }

           case _ = <-output:

           }

           bytes, err := json.Marshal(mr)
           if err != nil {
            w.WriteHeader(500)
           }
           fmt.Printf("\ndownstreamErrCount=%d, circuitOpenErrCount=%d", downstreamErrCount, circuitOpenErrCount)
           w.Write(bytes)
          }

          使用 Hystrix,您還可以在熔斷器打開時實現(xiàn)兜底邏輯。這種邏輯可能因情況而異。如果熔斷器打開,則從緩存中獲取。

          使用這個更新的邏輯,讓我們嘗試以每秒100個請求的速率重新攻擊 3 秒鐘。

          哇! !100% 的成功率,在打開的情況下,我們只提供 Feed 和返回 0個推薦。此外,由于每當(dāng)熔斷器熔斷,我們不再調(diào)用上游服務(wù),因此推薦服務(wù)不會不堪重負(fù),阻塞的 goroutine 數(shù)量不會像以前那么多。

          擴(kuò)展閱讀

          我的建議:

          1. 關(guān)于 Netflix Hystrix[2]
          2. Hystrix 是怎樣工作的?[3]
          3. Hystrix bucketing[4]


          參考資料

          [1] 

          hystrix: https://github.com/afex/hystrix-go/hystrix

          [2] 

          關(guān)于 Netflix Hystrix: https://github.com/Netflix/Hystrix/wiki

          [3] 

          Hystrix 是怎樣工作的?: https://github.com/Netflix/Hystrix/wiki/How-it-Works

          [4] 

          Hystrix bucketing: https://raw.githubusercontent.com/wiki/Netflix/Hystrix/images/circuit-breaker-1280.png



          往期推薦


          我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標(biāo)準(zhǔn)庫》等。


          堅持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio


          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩性交一级黄 | av天堂亚洲 | 亚洲欧洲小视频 | 操操操操操操操操操骚逼网 | 成人午夜精品视频在线观看 |