最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go?模塊在下游服務(wù)抖動(dòng)恢復(fù)后CPU占用無法恢復(fù)原因

 更新時(shí)間:2022年11月13日 14:29:59   作者:xargin  
這篇文章主要為大家介紹了Go?模塊在下游服務(wù)抖動(dòng)恢復(fù)后CPU占用無法恢復(fù)原因詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

某團(tuán)圓節(jié)日公司服務(wù)到達(dá)歷史峰值 10w+ QPS,而之前沒有預(yù)料到營銷系統(tǒng)又在峰值期間搞事情,雪上加霜,流量增長到 11w+ QPS,本組服務(wù)差點(diǎn)被打掛(汗

所幸命大雖然 CPU idle 一度跌至 30 以下,最終還是幸存下來,沒有背上過節(jié)大鍋。與我們的服務(wù)代碼寫的好不無關(guān)系(拍飛

事后回顧現(xiàn)場(chǎng),發(fā)現(xiàn)服務(wù)恢復(fù)之后整體的 CPU idle 和正常情況下比多消耗了幾個(gè)百分點(diǎn),感覺十分驚詫。恰好又禍不單行,工作日午后碰到下游系統(tǒng)抖動(dòng),雖然短時(shí)間恢復(fù),我們的系統(tǒng)相比恢復(fù)前還是多消耗了兩個(gè)百分點(diǎn)。如下圖:

確實(shí)不太符合直覺,cpu 的使用率上會(huì)發(fā)現(xiàn) GC 的各個(gè)函數(shù)都比平常用的 cpu 多了那么一點(diǎn)點(diǎn),那我們只能看看 inuse 是不是有什么變化了,一看倒是嚇了一跳:

這個(gè) mstart -> systemstack -> newproc -> malg 顯然是 go func 的時(shí)候的函數(shù)調(diào)用鏈,按道理來說,創(chuàng)建 goroutine 結(jié)構(gòu)體時(shí),如果可用的 g 和 sudog 結(jié)構(gòu)體能夠復(fù)用,會(huì)優(yōu)先進(jìn)行復(fù)用:

優(yōu)先復(fù)用

func gfput(_p_ *p, gp *g) {
	if readgstatus(gp) != _Gdead {
		throw("gfput: bad status (not Gdead)")
	}
	stksize := gp.stack.hi - gp.stack.lo
	if stksize != _FixedStack {
		// non-standard stack size - free it.
		stackfree(gp.stack)
		gp.stack.lo = 0
		gp.stack.hi = 0
		gp.stackguard0 = 0
	}
	_p_.gFree.push(gp)
	_p_.gFree.n++
	if _p_.gFree.n >= 64 {
		lock(&sched.gFree.lock)
		for _p_.gFree.n >= 32 {
			_p_.gFree.n--
			gp = _p_.gFree.pop()
			if gp.stack.lo == 0 {
				sched.gFree.noStack.push(gp)
			} else {
				sched.gFree.stack.push(gp)
			}
			sched.gFree.n++
		}
		unlock(&sched.gFree.lock)
	}
}
func gfget(_p_ *p) *g {
retry:
	if _p_.gFree.empty() && (!sched.gFree.stack.empty() || !sched.gFree.noStack.empty()) {
		lock(&sched.gFree.lock)
		for _p_.gFree.n < 32 {
			// Prefer Gs with stacks.
			gp := sched.gFree.stack.pop()
			if gp == nil {
				gp = sched.gFree.noStack.pop()
				if gp == nil {
					break
				}
			}
			sched.gFree.n--
			_p_.gFree.push(gp)
			_p_.gFree.n++
		}
		unlock(&sched.gFree.lock)
		goto retry
	}
	gp := _p_.gFree.pop()
	if gp == nil {
		return nil
	}
	_p_.gFree.n--
	if gp.stack.lo == 0 {
		systemstack(func() {
			gp.stack = stackalloc(_FixedStack)
		})
		gp.stackguard0 = gp.stack.lo + _StackGuard
	} else {
        // ....
	}
	return gp
}

創(chuàng)建 g

怎么會(huì)出來這么多 malg 呢?再來看看創(chuàng)建 g 的代碼:

func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) {
	_g_ := getg()
    // .... 省略無關(guān)代碼
	_p_ := _g_.m.p.ptr()
	newg := gfget(_p_)
	if newg == nil {
		newg = malg(_StackMin)
		casgstatus(newg, _Gidle, _Gdead)
		allgadd(newg) // 重點(diǎn)在這里
	}
}

一旦在 當(dāng)前 p 的 gFree 和全局的 gFree 找不到可用的 g,就會(huì)創(chuàng)建一個(gè)新的 g 結(jié)構(gòu)體,該 g 結(jié)構(gòu)體會(huì)被 append 到全局的 allgs 數(shù)組中:

var (
	allgs    []*g
	allglock mutex
)

allgs 在什么地方會(huì)用到

GC 的時(shí)候

func gcResetMarkState() {
	lock(&amp;allglock)
	for _, gp := range allgs {
		gp.gcscandone = false  // set to true in gcphasework
		gp.gcscanvalid = false // stack has not been scanned
		gp.gcAssistBytes = 0
	}
}

檢查死鎖的時(shí)候:

func checkdead() {
    // ....
	grunning := 0
	lock(&amp;allglock)
	for i := 0; i &lt; len(allgs); i++ {
		gp := allgs[i]
		if isSystemGoroutine(gp, false) {
			continue
		}
    }
}

檢查死鎖這個(gè)操作在每次 sysmon、創(chuàng)建 templateThread、線程進(jìn) idle 隊(duì)列的時(shí)候都會(huì)調(diào)用,調(diào)用頻率也不能說特別低。

翻閱了所有 allgs 的引用代碼,發(fā)現(xiàn)該數(shù)組創(chuàng)建之后,并不會(huì)收縮。

我們可以根據(jù)上面看到的所有代碼,來還原這種抖動(dòng)情況下整個(gè)系統(tǒng)的情況了:

  • 下游系統(tǒng)超時(shí),很多 g 都被阻塞了,掛在 gopark 上,相當(dāng)于提高了系統(tǒng)的并發(fā)
  • 因?yàn)?gFree 沒法復(fù)用,導(dǎo)致創(chuàng)建了比平時(shí)更多的 goroutine(具體有多少,就看你超時(shí)設(shè)置了多少
  • 抖動(dòng)時(shí)創(chuàng)建的 goroutine 會(huì)進(jìn)入全局 allgs 數(shù)組,該數(shù)組不會(huì)進(jìn)行收縮,且每次 gc、sysmon、死鎖檢查期間都會(huì)進(jìn)行全局掃描
  • 上述全局掃描導(dǎo)致我們的系統(tǒng)在下游系統(tǒng)抖動(dòng)恢復(fù)之后,依然要去掃描這些抖動(dòng)時(shí)創(chuàng)建的 g 對(duì)象,使 cpu 占用升高,idle 降低。
  • 只能重啟

看起來并沒有什么解決辦法,如果想要復(fù)現(xiàn)這個(gè)問題的讀者,可以試一下下面這個(gè)程序:

package main
import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"time"
)
func sayhello(wr http.ResponseWriter, r *http.Request) {}
func main() {
	for i := 0; i < 1000000; i++ {
		go func() {
			time.Sleep(time.Second * 10)
		}()
	}
	http.HandleFunc("/", sayhello)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

啟動(dòng)后等待 10s,待所有 goroutine 都散過后,pprof 的 inuse 的 malg 依然有百萬之巨。

循環(huán)查看單個(gè)進(jìn)程的 cpu 消耗:

import psutil
import time
p = psutil.Process(1) # 改成你自己的 pid 就行了
while 1:
    v = str(p.cpu_percent())
    if "0.0" != v:
        print(v, time.time())
    time.sleep(1)

以上就是Go 模塊在下游服務(wù)抖動(dòng)恢復(fù)后CPU占用無法恢復(fù)原因的詳細(xì)內(nèi)容,更多關(guān)于Go CPU占用無法恢復(fù)原因的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言中的匿名結(jié)構(gòu)體用法實(shí)例

    Go語言中的匿名結(jié)構(gòu)體用法實(shí)例

    這篇文章主要介紹了Go語言中的匿名結(jié)構(gòu)體用法,實(shí)例分析了匿名結(jié)構(gòu)體的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • golang的tunny的用法示例教程

    golang的tunny的用法示例教程

    這篇文章主要為大家介紹了golang的tunny的用法示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Golang并發(fā)繞不開的重要組件之Goroutine詳解

    Golang并發(fā)繞不開的重要組件之Goroutine詳解

    Goroutine、Channel、Context、Sync都是Golang并發(fā)編程中的幾個(gè)重要組件,這篇文中主要為大家介紹了Goroutine的相關(guān)知識(shí),需要的可以參考一下
    2023-06-06
  • golang1.16新特性速覽(推薦)

    golang1.16新特性速覽(推薦)

    這篇文章主要介紹了golang1.16新特性速覽,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • GoFrame框架數(shù)據(jù)校驗(yàn)之校驗(yàn)對(duì)象校驗(yàn)結(jié)構(gòu)體

    GoFrame框架數(shù)據(jù)校驗(yàn)之校驗(yàn)對(duì)象校驗(yàn)結(jié)構(gòu)體

    這篇文章主要為大家介紹了GoFrame框架數(shù)據(jù)校驗(yàn)之校驗(yàn)對(duì)象校驗(yàn)結(jié)構(gòu)體示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • golang json性能分析詳解

    golang json性能分析詳解

    json格式可以算我們?nèi)粘W畛S玫男蛄谢袷街涣?,Go語言作為一個(gè)由Google開發(fā),號(hào)稱互聯(lián)網(wǎng)的C語言的語言,自然也對(duì)JSON格式支持很好。下面這篇文章主要給大家詳細(xì)分析介紹了golang json性能的相關(guān)資料,需要的朋友可以參考下。
    2018-02-02
  • Go語言編譯原理之變量捕獲

    Go語言編譯原理之變量捕獲

    這篇文章主要為大家介紹了Go語言編譯原理之變量捕獲示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go語言的互斥鎖的詳細(xì)使用

    Go語言的互斥鎖的詳細(xì)使用

    本文主要介紹了Go語言的互斥鎖的詳細(xì)使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 淺析Golang中rune類型的使用

    淺析Golang中rune類型的使用

    從golang源碼中看出,rune關(guān)鍵字是int32的別名(-231~231-1),對(duì)比byte(-128~127),可表示的字符更多,本文就來簡單聊聊它的使用方法吧,希望對(duì)大家有所幫助
    2023-05-05
  • 談?wù)剬?duì)Golang IO讀寫的困惑

    談?wù)剬?duì)Golang IO讀寫的困惑

    這篇文章主要介紹了談?wù)剬?duì)Golang IO讀寫的困惑,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08

最新評(píng)論

木里| 泰州市| 彩票| 博罗县| 定边县| 许昌县| 宜君县| 海南省| 苗栗县| 抚州市| 岱山县| 清远市| 周至县| 石台县| 广州市| 昌都县| 论坛| 乌兰察布市| 台前县| 宁强县| 西乌珠穆沁旗| 绍兴县| 资中县| 新邵县| 小金县| 平塘县| 云霄县| 辽阳县| 镇原县| 岐山县| 昌吉市| 河西区| 凌云县| 贵阳市| 于田县| 津市市| 沁水县| 崇文区| 镇康县| 内乡县| 鹤岗市|