網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
goroutine 泄漏和避免泄漏的最佳實(shí)踐
Go的奇妙之處在于,我們可以使用goroutines和channel輕松地執(zhí)行并發(fā)任務(wù)。如果在生產(chǎn)環(huán)境中使用goroutines和channel,但是不了解它們的行為方式,會(huì)造成一些嚴(yán)重的影響。
好吧,我們就面臨著這樣的影響,我們?cè)趃oroutines中出現(xiàn)了泄漏,導(dǎo)致應(yīng)用服務(wù)器隨著時(shí)間的推移而膨脹,消耗了大量的CPU和頻繁的GC,影響了多個(gè)服務(wù)的SLA。
從本文中可以看到什么
理解什么是goroutine泄露。 理解goroutine泄漏的多種方式。 詳細(xì)了解造成goroutine泄露的一個(gè)真實(shí)場(chǎng)景。 我們是如何找到goroutine泄漏原因? 阻止goroutine泄漏的最佳實(shí)踐是什么?
正如你在上面所附的指標(biāo)中所看到的,goroutines開(kāi)始隨著時(shí)間的推移成倍地飆升。唯一的一次下降是當(dāng)我們的一個(gè)正在運(yùn)行的實(shí)例被AWS調(diào)度走,新的實(shí)例被啟動(dòng),或者有一個(gè)新的版本,殺死了現(xiàn)有的容器并產(chǎn)生了新的容器。
如果你觀察GC暫停的時(shí)間,它會(huì)隨著活動(dòng)的goroutine的數(shù)量不斷增加。GC暫停的次數(shù)越多,CPU利用率就越高,響應(yīng)時(shí)間也越來(lái)越長(zhǎng)。
什么是goroutine泄漏?
goroutine泄漏是指客戶端生成一個(gè)goroutine來(lái)做一些異步任務(wù),并在任務(wù)完成后將一些數(shù)據(jù)寫(xiě)入一個(gè)channel,但是
沒(méi)有監(jiān)聽(tīng)程序消耗該channel的數(shù)據(jù)寫(xiě)入。
在上述情況下,代碼成功地完成了執(zhí)行,好像根本就沒(méi)有問(wèn)題。但這里發(fā)生的情況是,會(huì)有一個(gè)沒(méi)有被管理的goroutine駐留在內(nèi)存中,占用CPU和RAM。
原因分析
主要原因是第3行,我們正在向一個(gè)通道寫(xiě)入數(shù)據(jù),但根據(jù)Go原則,一個(gè)未緩沖的通道會(huì)阻止向通道的寫(xiě)入,直到消費(fèi)者從該channel取走信息。
所以在這種情況下,第4行的返回將永遠(yuǎn)不會(huì)被執(zhí)行,并且newgoroutine函數(shù)在整個(gè)應(yīng)用程序生命周期中都被卡住,因?yàn)檫@個(gè)channel沒(méi)有消費(fèi)者。
在goroutine啟動(dòng)和channel監(jiān)聽(tīng)器之間有一些條件邏輯。
在這個(gè)案例中,有一個(gè)小小的改進(jìn)。我們有一個(gè)消費(fèi)者從dataChan中消費(fèi)數(shù)據(jù),但是從我們生成goroutine開(kāi)始,到我們開(kāi)始從通道中消費(fèi)數(shù)據(jù)之前,有大量的應(yīng)用程序代碼駐留在那里,這些代碼可以在一些處理錯(cuò)誤|DB錯(cuò)誤|無(wú)指針異常|panic的情況下退出主函數(shù),由于這些原因,channel的數(shù)據(jù)可能從未被執(zhí)行。
這就是一個(gè)goroutine看似正常,實(shí)際可能導(dǎo)致泄漏的情況。
我們不能在應(yīng)用處理之前將channel中的值提前消費(fèi),因?yàn)橄M(fèi)者會(huì)阻止剩下業(yè)務(wù)邏輯的處理,直到它收到數(shù)據(jù),從而消除了并發(fā)任務(wù)的執(zhí)行。
發(fā)送完成立刻返回 以上兩種情況是當(dāng)goroutine因?yàn)闆](méi)有channle的消費(fèi)者而被阻塞,或者消費(fèi)者從channel中消費(fèi)數(shù)據(jù)的代碼塊被跳過(guò)。
當(dāng)我們把一個(gè)channel傳遞給goroutine去消費(fèi)時(shí),當(dāng)發(fā)送者向通道發(fā)送數(shù)據(jù)時(shí)出現(xiàn)了問(wèn)題,這是否也是同樣的情況?
好吧,95%的goroutine泄露都是因?yàn)檫@3種情況中的一種,在我們的案例中,是由于情景-2。我們?cè)贕oIbibo-Makemytrip的工作是折扣和便利費(fèi)服務(wù)。
當(dāng)客戶應(yīng)用一個(gè)促銷代碼時(shí),我們有一套規(guī)則要執(zhí)行,以找出正確的折扣。我們有另一個(gè)微服務(wù),我們稱之為實(shí)時(shí)動(dòng)態(tài)折扣器(DD),它試圖根據(jù)一些算法(黑盒子)來(lái)計(jì)算折扣。
這個(gè)動(dòng)態(tài)折扣是一個(gè)A/B實(shí)驗(yàn),只有10%的用戶會(huì)參與其中。只有當(dāng)我們的靜態(tài)規(guī)則中存在有效的折扣時(shí),我們才會(huì)覆蓋DD折扣。
偽代碼
我們只有在處理完靜態(tài)規(guī)則后才需要DD的響應(yīng)。所以來(lái)自ddChan的消費(fèi)將在最后進(jìn)行。
如果靜態(tài)規(guī)則的評(píng)估有問(wèn)題|如果沒(méi)有滿足請(qǐng)求的有效規(guī)則|如果用戶應(yīng)用了一些假的促銷活動(dòng),我們從ddChan中消耗數(shù)據(jù)的代碼將無(wú)法到達(dá),這導(dǎo)致loadDDDiscount函數(shù)成為一個(gè)無(wú)法控制的goroutine。
有什么方法可以解決這個(gè)問(wèn)題?
方法-1 方法 -> 從我們啟動(dòng)goroutine開(kāi)始,到我們從退出channel的消耗數(shù)據(jù)為止,我們識(shí)別每一個(gè)錯(cuò)誤條件,并在每一個(gè)返回語(yǔ)句前放置一個(gè)接收者,以解除對(duì)生成的goroutine的封鎖。
陷阱 -> 我們必須手動(dòng)找到所有的邊緣情況,并且在將來(lái),如果我們必須處理更多的錯(cuò)誤情況,我們需要記住在返回之前我們需要消耗哪些channel的數(shù)據(jù)。
方法-2 方法 -> 與其在每個(gè)錯(cuò)誤的情況下放置一個(gè)接收者,為什么不設(shè)置一個(gè)可以從channel中接收數(shù)據(jù)的延遲函數(shù)。
陷阱 -- 在成功的情況下,數(shù)據(jù)將在處理完靜態(tài)規(guī)則后從通道中讀取。因此,如果我們?cè)赿efer函數(shù)中開(kāi)始接收通道中的數(shù)據(jù),那么在成功的情況下就會(huì)阻塞主goroutine。
方法-3 沒(méi)有完美的方法。在上述所有場(chǎng)景中,我們創(chuàng)建了一個(gè)無(wú)緩沖的通道,阻止發(fā)送者向該通道發(fā)送數(shù)據(jù),直到接收者收到數(shù)據(jù)。這里的主要問(wèn)題是我們不確定由于我們的應(yīng)用處理,接收方是否會(huì)被執(zhí)行。那么,簡(jiǎn)單的解決方案是創(chuàng)建一個(gè)上限為1的緩沖通道。有了這個(gè),即使沒(méi)有消費(fèi)者,或者消費(fèi)者代碼沒(méi)有達(dá)到,發(fā)送者也不會(huì)被阻止寫(xiě)一次數(shù)據(jù)。 圖片 陷阱 -> 絕對(duì)是零。這與非緩沖通道的工作原理完全相同,但為我們提供了一個(gè)額外的能力,即發(fā)送者在發(fā)送數(shù)據(jù)時(shí)不會(huì)受到阻礙,而消費(fèi)者可以在任何時(shí)候消費(fèi)它,而且生成的goroutine也不會(huì)等待消費(fèi)者的到來(lái)。 我們用第三種方法將變化帶入生產(chǎn)環(huán)境,你可以看到顯著的影響。
圖片 以前是線性增長(zhǎng)的goroutine數(shù)量,現(xiàn)在下降到150個(gè),我們的GC暫停頻率也是如此。
整個(gè)事情中最痛苦的部分是,如何找到代碼中存在goroutine泄漏的部分?
所以我的方法是這樣的。
當(dāng)服務(wù)器啟動(dòng)時(shí),使用debug.SetGCPercent(-1)禁用垃圾收集器。 現(xiàn)在運(yùn)行代碼中每一個(gè)使用Go程序的流程(Dev Env)。 在每個(gè)API的入口處,打印在開(kāi)始和執(zhí)行API之前和之后運(yùn)行的goroutines的數(shù)量。
func ApplyPromo() { fmt.Println(runtime.NumGoroutine()) defer fmt.Println(runtime.NumGoroutine() // Process your application logic }
現(xiàn)在,如果一個(gè)服務(wù)在前后返回不同的Goroutines數(shù)量,那么這個(gè)邏輯就存在泄漏。
我們有近20個(gè)API和大約35-40個(gè)地方使用了goroutines以改善并發(fā)性。幸運(yùn)的是,我能夠在前3次迭代中找出泄漏問(wèn)題,并發(fā)現(xiàn)了這個(gè)存在泄漏的邏輯。
原文鏈接:https://juejin.cn/post/7179445013640642615
相關(guān)推薦
- 2022-03-25 .NET微服務(wù)架構(gòu)CI/CD鏡像自動(dòng)分發(fā)_實(shí)用技巧
- 2023-10-11 lambda Collectors類的靜態(tài)工廠方法
- 2022-07-29 Jetpack?Compose實(shí)現(xiàn)列表和動(dòng)畫(huà)效果詳解_Android
- 2022-01-01 使用el-date-picker根據(jù)開(kāi)始月份,動(dòng)態(tài)禁用結(jié)束月份
- 2021-12-09 VS2017開(kāi)發(fā)C語(yǔ)言出現(xiàn)“no_init_all“的解決辦法_C 語(yǔ)言
- 2022-03-29 一篇文章帶你理解React?Props的?原理_React
- 2022-08-25 Asp.net?core中依賴注入的實(shí)現(xiàn)_實(shí)用技巧
- 2022-07-28 C++超詳細(xì)講解強(qiáng)制類型轉(zhuǎn)換_C 語(yǔ)言
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支