1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Go 微服務(wù)過載保護(hù)原理與實(shí)戰(zhàn)

        共 3461字,需瀏覽 7分鐘

         ·

        2020-12-30 21:51

        在微服務(wù)中由于服務(wù)間相互依賴很容易出現(xiàn)連鎖故障,連鎖故障可能是由于整個(gè)服務(wù)鏈路中的某一個(gè)服務(wù)出現(xiàn)故障,進(jìn)而導(dǎo)致系統(tǒng)的其他部分也出現(xiàn)故障。例如某個(gè)服務(wù)的某個(gè)實(shí)例由于過載出現(xiàn)故障,導(dǎo)致其他實(shí)例負(fù)載升高,從而導(dǎo)致這些實(shí)例像多米諾骨牌一樣一個(gè)個(gè)全部出現(xiàn)故障,這種連鎖故障就是所謂的雪崩現(xiàn)象。

        比如,服務(wù)A依賴服務(wù)C,服務(wù)C依賴服務(wù)D,服務(wù)D依賴服務(wù)E,當(dāng)服務(wù)E過載會(huì)導(dǎo)致響應(yīng)時(shí)間變慢甚至服務(wù)不可用,這個(gè)時(shí)候調(diào)用方D會(huì)出現(xiàn)大量超時(shí)連接資源被大量占用得不到釋放,進(jìn)而資源被耗盡導(dǎo)致服務(wù)D也過載,從而導(dǎo)致服務(wù)C過載以及整個(gè)系統(tǒng)雪崩。

        某一種資源的耗盡可以導(dǎo)致高延遲、高錯(cuò)誤率或者相應(yīng)數(shù)據(jù)不符合預(yù)期的情況發(fā)生,這些的確是在資源耗盡時(shí)應(yīng)該出現(xiàn)的情況,在負(fù)載不斷上升直到過載時(shí),服務(wù)器不可能一直保持完全的正常。而CPU資源的不足導(dǎo)致的負(fù)載上升是我們工作中最常見的,如果CPU資源不足以應(yīng)對(duì)請(qǐng)求負(fù)載,一般來說所有的請(qǐng)求都會(huì)變慢,CPU負(fù)載過高會(huì)造成一系列的副作用,主要包括以下幾項(xiàng):

        • 正在處理的(in-flight) 的請(qǐng)求數(shù)量上升

        • 服務(wù)器逐漸將請(qǐng)求隊(duì)列填滿,意味著延遲上升,同時(shí)隊(duì)列會(huì)用更多的內(nèi)存

        • 線程卡住,無法處理請(qǐng)求

        • cpu死鎖或者請(qǐng)求卡主

        • rpc服務(wù)調(diào)用超時(shí)

        • cpu的緩存效率下降

        由此可見防止服務(wù)器過載的重要性不言而喻,而防止服務(wù)器過載又分為下面幾種常見的策略:

        • 提供降級(jí)結(jié)果

        • 在過載情況下主動(dòng)拒絕請(qǐng)求

        • 調(diào)用方主動(dòng)拒絕請(qǐng)求

        • 提前進(jìn)行壓測以及合理的容量規(guī)劃

        今天我們主要討論的是第二種防止服務(wù)器過載的方案,即在過載的情況下主動(dòng)拒絕請(qǐng)求,下面我統(tǒng)一使用”過載保護(hù)“來表述,過載保護(hù)的大致原理是當(dāng)探測到服務(wù)器已經(jīng)處于過載時(shí)則主動(dòng)拒絕請(qǐng)求不進(jìn)行處理,一般做法是快速返回error

        很多微服務(wù)框架中都內(nèi)置了過載保護(hù)能力,本文主要分析go-zero中的過載保護(hù)功能,我們先通過一個(gè)例子來感受下go-zero的中的過載保護(hù)是怎么工作的

        首先,我們使用官方推薦的goctl生成一個(gè)api服務(wù)和一個(gè)rpc服務(wù),生成服務(wù)的過程比較簡單,在此就不做介紹,可以參考官方文檔,我的環(huán)境是兩臺(tái)服務(wù)器,api服務(wù)跑在本機(jī),rpc服務(wù)跑在遠(yuǎn)程服務(wù)器

        遠(yuǎn)程服務(wù)器為單核CPU,首先通過壓力工具模擬服務(wù)器負(fù)載升高,把CPU打滿

        stress -c 1 -t 1000

        此時(shí)通過uptime工具查看服務(wù)器負(fù)載情況,-d參數(shù)可以高亮負(fù)載的變化情況,此時(shí)的負(fù)載已經(jīng)大于CPU核數(shù),說明服務(wù)器正處于過載狀態(tài)

        watch -d uptime

        19:47:45 up 5 days, 21:55, 3 users, load average: 1.26, 1.31, 1.44

        此時(shí)請(qǐng)求api服務(wù),其中ap服務(wù)內(nèi)部依賴rpc服務(wù),查看rpc服務(wù)的日志,級(jí)別為stat,可以看到cpu是比較高的

        "level":"stat","content":"(rpc) shedding_stat [1m], cpu: 986, total: 4, pass: 2, drop: 2"

        并且會(huì)打印過載保護(hù)丟棄請(qǐng)求的日志,可以看到過載保護(hù)已經(jīng)生效,主動(dòng)丟去了請(qǐng)求

        adaptiveshedder.go:185 dropreq, cpu: 990, maxPass: 87, minRt: 1.00, hot: true, flying: 2, avgFlying: 2.07

        這個(gè)時(shí)候調(diào)用方會(huì)收到 "service overloaded" 的報(bào)錯(cuò)

        通過上面的試驗(yàn)我們可以看到當(dāng)服務(wù)器負(fù)載過高就會(huì)觸發(fā)過載保護(hù),從而避免連鎖故障導(dǎo)致雪崩,接下來我們從源碼來分析下過載保護(hù)的原理,go-zero在http和rpc框架中都內(nèi)置了過載保護(hù)功能,代碼路徑分別在go-zero/rest/handler/sheddinghandler.go和go-zero/zrpc/internal/serverinterceptors/sheddinginterceptor.go下面,我們就以rpc下面的過載保護(hù)進(jìn)行分析,在server啟動(dòng)的時(shí)候回new一個(gè)shedder 代碼路徑: go-zero/zrpc/server.go:119, 然后當(dāng)收到每個(gè)請(qǐng)求都會(huì)通過Allow方法判斷是否需要進(jìn)行過載保護(hù),如果err不等于nil說明需要過載保護(hù)則直接返回error

        promise, err = shedder.Allow()
        if err != nil {
        metrics.AddDrop()
        sheddingStat.IncrementDrop()
        return
        }

        實(shí)現(xiàn)過載保護(hù)的代碼路徑為: go-zero/core/load/adaptiveshedder.go,這里實(shí)現(xiàn)的過載保護(hù)基于滑動(dòng)窗口可以防止毛刺,有冷卻時(shí)間防止抖動(dòng),當(dāng)CPU>90%的時(shí)候開始拒絕請(qǐng)求,Allow的實(shí)現(xiàn)如下

        func (as *adaptiveShedder) Allow() (Promise, error) {
        if as.shouldDrop() {
        as.dropTime.Set(timex.Now())
        as.droppedRecently.Set(true)

        return nil, ErrServiceOverloaded // 返回過載錯(cuò)誤
        }

        as.addFlying(1) // flying +1

        return &promise{
        start: timex.Now(),
        shedder: as,
        }, nil
        }

        sholdDrop實(shí)現(xiàn)如下,該函數(shù)用來檢測是否符合觸發(fā)過載保護(hù)條件,如果符合的話會(huì)記錄error日志

        func (as *adaptiveShedder) shouldDrop() bool {
        if as.systemOverloaded() || as.stillHot() {
        if as.highThru() {
        flying := atomic.LoadInt64(&as.flying)
        as.avgFlyingLock.Lock()
        avgFlying := as.avgFlying
        as.avgFlyingLock.Unlock()
        msg := fmt.Sprintf(
        "dropreq, cpu: %d, maxPass: %d, minRt: %.2f, hot: %t, flying: %d, avgFlying: %.2f",
        stat.CpuUsage(), as.maxPass(), as.minRt(), as.stillHot(), flying, avgFlying)
        logx.Error(msg)
        stat.Report(msg)
        return true
        }
        }

        return false
        }

        判斷CPU是否達(dá)到預(yù)設(shè)值,默認(rèn)90%

        systemOverloadChecker = func(cpuThreshold int64) bool {
        return stat.CpuUsage() >= cpuThreshold
        }

        CPU的負(fù)載統(tǒng)計(jì)代碼如下,每隔250ms會(huì)進(jìn)行一次統(tǒng)計(jì),每一分鐘會(huì)記錄一次統(tǒng)計(jì)日志

        func init() {
        go func() {
        cpuTicker := time.NewTicker(cpuRefreshInterval)
        defer cpuTicker.Stop()
        allTicker := time.NewTicker(allRefreshInterval)
        defer allTicker.Stop()

        for {
        select {
        case <-cpuTicker.C:
        threading.RunSafe(func() {
        curUsage := internal.RefreshCpu()
        prevUsage := atomic.LoadInt64(&cpuUsage)
        // cpu = cpu??1 * beta + cpu? * (1 - beta)
        usage := int64(float64(prevUsage)*beta + float64(curUsage)*(1-beta))
        atomic.StoreInt64(&cpuUsage, usage)
        })
        case <-allTicker.C:
        printUsage()
        }
        }
        }()
        }

        其中CPU統(tǒng)計(jì)實(shí)現(xiàn)的代碼路徑為: go-zero/core/stat/internal,在該路徑下使用linux結(jié)尾的文件,因?yàn)樵趃o語言中會(huì)根據(jù)不同的系統(tǒng)編譯不同的文件,當(dāng)為linux系統(tǒng)時(shí)會(huì)編譯以linux為后綴的文件

        func init() {
        cpus, err := perCpuUsage()
        if err != nil {
        logx.Error(err)
        return
        }

        cores = uint64(len(cpus))
        sets, err := cpuSets()
        if err != nil {
        logx.Error(err)
        return
        }

        quota = float64(len(sets))
        cq, err := cpuQuota()
        if err == nil {
        if cq != -1 {
        period, err := cpuPeriod()
        if err != nil {
        logx.Error(err)
        return
        }

        limit := float64(cq) / float64(period)
        if limit < quota {
        quota = limit
        }
        }
        }

        preSystem, err = systemCpuUsage()
        if err != nil {
        logx.Error(err)
        return
        }

        preTotal, err = totalCpuUsage()
        if err != nil {
        logx.Error(err)
        return
        }
        }

        在linux中,通過/proc虛擬文件系統(tǒng)向用戶控件提供了系統(tǒng)內(nèi)部狀態(tài)的信息,而/proc/stat提供的就是系統(tǒng)的CPU等的任務(wù)統(tǒng)計(jì)信息,這里主要原理就是通過/proc/stat來計(jì)算CPU的使用率

        本文主要介紹了過載保護(hù)的原理,以及通過實(shí)驗(yàn)觸發(fā)了過載保護(hù),最后分析了實(shí)現(xiàn)過載保護(hù)功能的代碼,相信通過本文大家對(duì)過載保護(hù)會(huì)有進(jìn)一步的認(rèn)識(shí),過載保護(hù)不是萬金油,對(duì)服務(wù)來說是有損的,所以在服務(wù)上線前我們最好是進(jìn)行壓測做好資源規(guī)劃,盡量避免服務(wù)過載

        寫作不易,如果覺得文章不錯(cuò),歡迎 github star ?

        項(xiàng)目地址:https://github.com/tal-tech/go-zero



        推薦閱讀


        福利

        我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

        瀏覽 50
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            天天综合色中文字幕 | av水果派 | 免费看一区二区三区A片 | 日本护士 | 天天日bb | 五月天无码AV | 自拍偷拍第8页 | 欧美日韩免费大片 | 中国美女光屁股无遮挡 | 伊人在线观看免费完整版 |