Sentinel限流熔斷降級

ice_moss發表於2022-11-13

[toc]

文章介紹

本文將介紹什麼是限流和熔斷, alibaba開源的Sentinel、安裝、實戰

什麼是限流\熔斷\降級

限流:在我們的後天系統中,如果那一天突然進入大量流量,我們服務原本最高只能處理同時2k的請求,突然一

下就來來了5k的請求,這對伺服器的壓力是不是很要命,這很可能直接導致伺服器當機,崩潰,導致原本2K的處

理量都不能處理了,這時候我們需要限流,限流的作用就是保持訪問量到達伺服器最高的情況下,對多餘的請求

不做處理,相比之下,比伺服器直接掛掉是好很多的。例如在雙十一的時候,我們要下單就會看到類似”請求繁

忙,請稍後重試!”。

熔斷:相信大家對斷路器並不陌生,它就相當於一個開關,開啟後可以阻止流量透過。比如保險絲,當電流過大

時,就會熔斷,從而避免元器件損壞。

服務熔斷是指呼叫方訪問服務時透過斷路器做代理進行訪問,斷路器會持續觀察服務返回的成功、失敗的狀態,

當失敗超過設定的閾值時斷路器開啟,請求就不能真正地訪問到服務了。

使用場景

  • 服務故障或者升級時,讓客戶端快速失敗
  • 失敗處理邏輯容易定義
  • 響應耗時較長,客戶端設定的read timeout會比較長,防止客戶端大量重試請求導致的連線、執行緒資源不能釋放

*降級 *:服務降級是從整個系統的負荷情況出發和考慮的,對某些負荷會比較高的情況,為了預防某些功能(業務

場景)出現負荷過載或者響應慢的情況,在其內部暫時捨棄對一些非核心的介面和資料的請求,而直接返回一個

提前準備好的fallback(退路)錯誤處理資訊。這樣,雖然提供的是一個有損的服務,但卻保證了整個系統的穩

定性和可用性。

什麼是Sentinel

Sentinel是阿里開源的專案,提供了流量控制、熔斷降級、系統負載保護等多個維度來保障服務之間的穩定性。
官網:github.com/alibaba/Sentinel/wiki

2012年,Sentinel誕生於阿里巴巴,其主要目標是流量控制。2013-2017年,Sentinel迅速發展,併成為阿里巴巴所有微服務的基本組成部分。 它已在6000多個應用程式中使用,涵蓋了幾乎所有核心電子商務場景。2018年,Sentinel演變為一個開源專案。2020年,Sentinel Golang釋出。

特點

豐富的應用場景 :Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即
突發流量控制在系統容量可以承受的範圍)、訊息削峰填谷、叢集流量控制、實時熔斷下游不可用應用等。
完備的實時監控 :Sentinel 同時提供實時的監控功能。您可以在控制檯中看到接入應用的單臺機
器秒級資料,甚至 500 臺以下規模的叢集的彙總執行情況。

*生態廣廣泛 *

Sentinel 的歷史

  • 2012 年,Sentinel 誕生,主要功能為入口流量控制。
  • 2013-2017 年,Sentinel 在阿里巴巴集團內部迅速發展,成為基礎技術模組,覆蓋了所有的核心場景。Sentinel 也因此積累了大量的流量歸整場景以及生產實踐。
  • 2018 年,Sentinel 開源,並持續演進。
  • 2019 年,Sentinel 朝著多語言擴充套件的方向不斷探索,推出 C++ 原生版本,同時針對 Service Mesh 場景也推出了 Envoy 叢集流量控制支援,以解決 Service Mesh 架構下多語言限流的問題。
  • 2020 年,推出 Sentinel Go 版本,繼續朝著雲原生方向演進。
  • 2021 年,Sentinel 正在朝著 2.0 雲原生高可用決策中心元件進行演進;同時推出了 Sentinel Rust 原生版本。同時我們也在 Rust 社群進行了 Envoy WASM extension 及 eBPF extension 等場景探索。
  • 2022 年,Sentinel 品牌升級為流量治理,領域涵蓋流量路由/排程、流量染色、流控降級、過載保護/例項摘除等;同時社群將流量治理相關標準抽出到 OpenSergo 標準中,Sentinel 作為流量治理標準實現。

Sentinel-go的安裝

Sentinel-go開源地址

官網文件

安裝:go get github.com/alibaba/sentinel-golang/api

Go限流實戰

qps限流

package main

import (
    "fmt"
    "log"

    sentinel "github.com/alibaba/sentinel-golang/api"
    "github.com/alibaba/sentinel-golang/core/base"
    "github.com/alibaba/sentinel-golang/core/flow"
)

func main() {
    //基於sentinel的qps限流
    //必須初始化
    err := sentinel.InitDefault()
    if err != nil {
        log.Fatalf("Unexpected error: %+v", err)
    }

    //配置限流規則:1秒內透過10次
    _, err = flow.LoadRules([]*flow.Rule{
        {
            Resource:               "some_test",
            TokenCalculateStrategy: flow.Direct,
            ControlBehavior:        flow.Reject, //超過直接拒絕
            Threshold:              10,          //請求次數
            StatIntervalInMs:       1000,        //允許時間內
        },
    })

    if err != nil {
        log.Fatalf("Unexpected error: %+v", err)
        return
    }

    for i := 0; i < 12; i++ {
        e, b := sentinel.Entry("some_test", sentinel.WithTrafficType(base.Inbound))
        if b != nil {
            fmt.Println("限流了")
        } else {
            fmt.Println("檢查透過")
            e.Exit()
        }
    }
}

列印結果:

檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
限流了
限流了

Thrnotting

package main

import (
    "fmt"
    "log"
    "time"

    sentinel "github.com/alibaba/sentinel-golang/api"
    "github.com/alibaba/sentinel-golang/core/base"
    "github.com/alibaba/sentinel-golang/core/flow"
)

func main() {
    //基於sentinel的qps限流
    //必須初始化
    err := sentinel.InitDefault()
    if err != nil {
        log.Fatalf("Unexpected error: %+v", err)
    }

    //配置限流規則
    _, err = flow.LoadRules([]*flow.Rule{
        {
            Resource:               "some_test",
            TokenCalculateStrategy: flow.Direct,
            ControlBehavior:        flow.Throttling, //勻速透過
            Threshold:              10,              //請求次數
            StatIntervalInMs:       1000,            //允許時間內
        },
    })

    if err != nil {
        log.Fatalf("Unexpected error: %+v", err)
        return
    }

    for i := 0; i < 12; i++ {
        e, b := sentinel.Entry("some_test", sentinel.WithTrafficType(base.Inbound))
        if b != nil {
            fmt.Println("限流了")
        } else {
            fmt.Println("檢查透過")
            e.Exit()
        }
        time.Sleep(time.Millisecond * 100)
    }
}
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過
檢查透過

Warrm_up

package main

import (
    "fmt"
    "log"
    "math/rand"
    "time"

    sentinel "github.com/alibaba/sentinel-golang/api"
    "github.com/alibaba/sentinel-golang/core/base"
    "github.com/alibaba/sentinel-golang/core/flow"
)

func main() {
    //先初始化sentinel
    err := sentinel.InitDefault()
    if err != nil {
        log.Fatalf("初始化sentinel 異常: %v", err)
    }

    var globalTotal int
    var passTotal int
    var blockTotal int
    ch := make(chan struct{})

    //配置限流規則
    _, err = flow.LoadRules([]*flow.Rule{
        {
            Resource:               "some-test",
            TokenCalculateStrategy: flow.WarmUp, //冷啟動策略
            ControlBehavior:        flow.Reject, //直接拒絕
            Threshold:              1000,
            WarmUpPeriodSec:        30,
        },
    })

    if err != nil {
        log.Fatalf("載入規則失敗: %v", err)
    }

    //我會在每一秒統計一次,這一秒只能 你透過了多少,總共有多少, block了多少, 每一秒會產生很多的block
    for i := 0; i < 100; i++ {
        go func() {
            for {
                globalTotal++
                e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound))
                if b != nil {
                    //fmt.Println("限流了")
                    blockTotal++
                    time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
                } else {
                    passTotal++
                    time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
                    e.Exit()
                }
            }
        }()
    }

    go func() {
        var oldTotal int //過去1s總共有多少個
        var oldPass int  //過去1s總共pass多少個
        var oldBlock int //過去1s總共block多少個
        for {
            oneSecondTotal := globalTotal - oldTotal
            oldTotal = globalTotal

            oneSecondPass := passTotal - oldPass
            oldPass = passTotal

            oneSecondBlock := blockTotal - oldBlock
            oldBlock = blockTotal

            time.Sleep(time.Second)
            fmt.Printf("total:%d, pass:%d, block:%d\n", oneSecondTotal, oneSecondPass, oneSecondBlock)
        }
    }()

    <-ch
}

列印結果:逐漸到達1k,在1k位置上下波動

total:11, pass:9, block:0
total:21966, pass:488, block:21420
total:21793, pass:339, block:21414
total:21699, pass:390, block:21255
total:21104, pass:393, block:20654
total:21363, pass:453, block:20831
total:21619, pass:491, block:21052
total:21986, pass:533, block:21415
total:21789, pass:594, block:21123
total:21561, pass:685, block:20820
total:21663, pass:873, block:20717
total:20904, pass:988, block:19831
total:21500, pass:996, block:20423
total:21769, pass:1014, block:20682
total:20893, pass:1019, block:19837
total:21561, pass:973, block:20524
total:21601, pass:1014, block:20517
total:21475, pass:993, block:20420
total:21457, pass:983, block:20418
total:21397, pass:1024, block:20320
total:21690, pass:996, block:20641
total:21526, pass:991, block:20457
total:21779, pass:1036, block:20677

Go熔斷實戰

這裡我們介紹一個錯誤數量的,檢視詳細熔斷機制

error_count

package main

import (
    "errors"
    "fmt"
    "log"
    "math/rand"
    "time"

    sentinel "github.com/alibaba/sentinel-golang/api"
    "github.com/alibaba/sentinel-golang/core/circuitbreaker"
    "github.com/alibaba/sentinel-golang/core/config"
    "github.com/alibaba/sentinel-golang/logging"
    "github.com/alibaba/sentinel-golang/util"
)

type stateChangeTestListener struct {
}

func (s *stateChangeTestListener) OnTransformToClosed(prev circuitbreaker.State, rule circuitbreaker.Rule) {
    fmt.Printf("rule.steategy: %+v, From %s to Closed, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

func (s *stateChangeTestListener) OnTransformToOpen(prev circuitbreaker.State, rule circuitbreaker.Rule, snapshot interface{}) {
    fmt.Printf("rule.steategy: %+v, From %s to Open, snapshot: %d, time: %d\n", rule.Strategy, prev.String(), snapshot, util.CurrentTimeMillis())
}

func (s *stateChangeTestListener) OnTransformToHalfOpen(prev circuitbreaker.State, rule circuitbreaker.Rule) {
    fmt.Printf("rule.steategy: %+v, From %s to Half-Open, time: %d\n", rule.Strategy, prev.String(), util.CurrentTimeMillis())
}

func main() {
    //基於連線數的降級模式
    total := 0
    totalPass := 0
    totalBlock := 0
    totalErr := 0
    conf := config.NewDefaultConfig()
    // for testing, logging output to console
    conf.Sentinel.Log.Logger = logging.NewConsoleLogger()
    err := sentinel.InitWithConfig(conf)
    if err != nil {
        log.Fatal(err)
    }
    ch := make(chan struct{})
    // Register a state change listener so that we could observer the state change of the internal circuit breaker.
    circuitbreaker.RegisterStateChangeListeners(&stateChangeTestListener{})

    _, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{
        // Statistic time span=10s, recoveryTimeout=3s, maxErrorCount=50
        {
            Resource:         "abc",
            Strategy:         circuitbreaker.ErrorCount,
            RetryTimeoutMs:   3000, //3s只有嘗試回覆
            MinRequestAmount: 10,   //靜默數
            StatIntervalMs:   5000,
            Threshold:        50,
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    logging.Info("[CircuitBreaker ErrorCount] Sentinel Go circuit breaking demo is running. You may see the pass/block metric in the metric log.")
    go func() {
        for {
            total++
            e, b := sentinel.Entry("abc")
            if b != nil {
                // g1 blocked
                totalBlock++
                fmt.Println("協程熔斷了")
                time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
            } else {
                totalPass++
                if rand.Uint64()%20 > 9 {
                    totalErr++
                    // Record current invocation as error.
                    sentinel.TraceError(e, errors.New("biz error"))
                }
                // g1 passed
                time.Sleep(time.Duration(rand.Uint64()%20+10) * time.Millisecond)
                e.Exit()
            }
        }
    }()
    go func() {
        for {
            total++
            e, b := sentinel.Entry("abc")
            if b != nil {
                // g2 blocked
                totalBlock++
                time.Sleep(time.Duration(rand.Uint64()%20) * time.Millisecond)
            } else {
                // g2 passed
                totalPass++
                time.Sleep(time.Duration(rand.Uint64()%80) * time.Millisecond)
                e.Exit()
            }
        }
    }()

    go func() {
        for {
            time.Sleep(time.Second)
            fmt.Println(totalErr)
        }
    }()
    <-ch
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章