網站首頁 編程語言 正文
goroutine 泄漏和避免泄漏的最佳實踐
Go的奇妙之處在于,我們可以使用goroutines和channel輕松地執行并發任務。如果在生產環境中使用goroutines和channel,但是不了解它們的行為方式,會造成一些嚴重的影響。
好吧,我們就面臨著這樣的影響,我們在goroutines中出現了泄漏,導致應用服務器隨著時間的推移而膨脹,消耗了大量的CPU和頻繁的GC,影響了多個服務的SLA。
從本文中可以看到什么
理解什么是goroutine泄露。 理解goroutine泄漏的多種方式。 詳細了解造成goroutine泄露的一個真實場景。 我們是如何找到goroutine泄漏原因? 阻止goroutine泄漏的最佳實踐是什么?
正如你在上面所附的指標中所看到的,goroutines開始隨著時間的推移成倍地飆升。唯一的一次下降是當我們的一個正在運行的實例被AWS調度走,新的實例被啟動,或者有一個新的版本,殺死了現有的容器并產生了新的容器。
如果你觀察GC暫停的時間,它會隨著活動的goroutine的數量不斷增加。GC暫停的次數越多,CPU利用率就越高,響應時間也越來越長。
什么是goroutine泄漏?
goroutine泄漏是指客戶端生成一個goroutine來做一些異步任務,并在任務完成后將一些數據寫入一個channel,但是
沒有監聽程序消耗該channel的數據寫入。
在上述情況下,代碼成功地完成了執行,好像根本就沒有問題。但這里發生的情況是,會有一個沒有被管理的goroutine駐留在內存中,占用CPU和RAM。
原因分析
主要原因是第3行,我們正在向一個通道寫入數據,但根據Go原則,一個未緩沖的通道會阻止向通道的寫入,直到消費者從該channel取走信息。
所以在這種情況下,第4行的返回將永遠不會被執行,并且newgoroutine函數在整個應用程序生命周期中都被卡住,因為這個channel沒有消費者。
在goroutine啟動和channel監聽器之間有一些條件邏輯。
在這個案例中,有一個小小的改進。我們有一個消費者從dataChan中消費數據,但是從我們生成goroutine開始,到我們開始從通道中消費數據之前,有大量的應用程序代碼駐留在那里,這些代碼可以在一些處理錯誤|DB錯誤|無指針異常|panic的情況下退出主函數,由于這些原因,channel的數據可能從未被執行。
這就是一個goroutine看似正常,實際可能導致泄漏的情況。
我們不能在應用處理之前將channel中的值提前消費,因為消費者會阻止剩下業務邏輯的處理,直到它收到數據,從而消除了并發任務的執行。
發送完成立刻返回 以上兩種情況是當goroutine因為沒有channle的消費者而被阻塞,或者消費者從channel中消費數據的代碼塊被跳過。
當我們把一個channel傳遞給goroutine去消費時,當發送者向通道發送數據時出現了問題,這是否也是同樣的情況?
好吧,95%的goroutine泄露都是因為這3種情況中的一種,在我們的案例中,是由于情景-2。我們在GoIbibo-Makemytrip的工作是折扣和便利費服務。
當客戶應用一個促銷代碼時,我們有一套規則要執行,以找出正確的折扣。我們有另一個微服務,我們稱之為實時動態折扣器(DD),它試圖根據一些算法(黑盒子)來計算折扣。
這個動態折扣是一個A/B實驗,只有10%的用戶會參與其中。只有當我們的靜態規則中存在有效的折扣時,我們才會覆蓋DD折扣。
偽代碼
我們只有在處理完靜態規則后才需要DD的響應。所以來自ddChan的消費將在最后進行。
如果靜態規則的評估有問題|如果沒有滿足請求的有效規則|如果用戶應用了一些假的促銷活動,我們從ddChan中消耗數據的代碼將無法到達,這導致loadDDDiscount函數成為一個無法控制的goroutine。
有什么方法可以解決這個問題?
方法-1 方法 -> 從我們啟動goroutine開始,到我們從退出channel的消耗數據為止,我們識別每一個錯誤條件,并在每一個返回語句前放置一個接收者,以解除對生成的goroutine的封鎖。
陷阱 -> 我們必須手動找到所有的邊緣情況,并且在將來,如果我們必須處理更多的錯誤情況,我們需要記住在返回之前我們需要消耗哪些channel的數據。
方法-2 方法 -> 與其在每個錯誤的情況下放置一個接收者,為什么不設置一個可以從channel中接收數據的延遲函數。
陷阱 -- 在成功的情況下,數據將在處理完靜態規則后從通道中讀取。因此,如果我們在defer函數中開始接收通道中的數據,那么在成功的情況下就會阻塞主goroutine。
方法-3 沒有完美的方法。在上述所有場景中,我們創建了一個無緩沖的通道,阻止發送者向該通道發送數據,直到接收者收到數據。這里的主要問題是我們不確定由于我們的應用處理,接收方是否會被執行。那么,簡單的解決方案是創建一個上限為1的緩沖通道。有了這個,即使沒有消費者,或者消費者代碼沒有達到,發送者也不會被阻止寫一次數據。 圖片 陷阱 -> 絕對是零。這與非緩沖通道的工作原理完全相同,但為我們提供了一個額外的能力,即發送者在發送數據時不會受到阻礙,而消費者可以在任何時候消費它,而且生成的goroutine也不會等待消費者的到來。 我們用第三種方法將變化帶入生產環境,你可以看到顯著的影響。
圖片 以前是線性增長的goroutine數量,現在下降到150個,我們的GC暫停頻率也是如此。
整個事情中最痛苦的部分是,如何找到代碼中存在goroutine泄漏的部分?
所以我的方法是這樣的。
當服務器啟動時,使用debug.SetGCPercent(-1)禁用垃圾收集器。 現在運行代碼中每一個使用Go程序的流程(Dev Env)。 在每個API的入口處,打印在開始和執行API之前和之后運行的goroutines的數量。
func ApplyPromo() { fmt.Println(runtime.NumGoroutine()) defer fmt.Println(runtime.NumGoroutine() // Process your application logic }
現在,如果一個服務在前后返回不同的Goroutines數量,那么這個邏輯就存在泄漏。
我們有近20個API和大約35-40個地方使用了goroutines以改善并發性。幸運的是,我能夠在前3次迭代中找出泄漏問題,并發現了這個存在泄漏的邏輯。
原文鏈接:https://juejin.cn/post/7179445013640642615
相關推薦
- 2022-07-13 Pycharm使用技巧_Pycharm配置autopep8
- 2022-12-21 C語言中continue的用法詳解_C 語言
- 2022-10-20 Python?BautifulSoup?節點信息_python
- 2022-12-15 Native?Memory?Tracking追蹤區域示例分析_React
- 2023-01-31 golang定時任務cron項目實操指南_Golang
- 2022-11-14 python?pycharm中使用opencv時沒有代碼自動補全提示的解決方案_python
- 2022-08-30 C語言深入詳解四大內存函數的使用_C 語言
- 2022-10-27 C++設計模式中的工廠模式詳細介紹_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支