iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 7
1
自我挑戰組

Let's Eat GO ! 實務開發雜談by Golang系列 第 7

Day7 .[重災經驗篇] 爆連線問題,TCP ESTABLISHED 咬著不放

這篇跟大家分享Golang 連線重複利用的重要,以及遭遇沒辦法用完即丟的窘境。

如標題,最主要探討的問題是TCP ESTABLISHED ,遇到了一旦tcp 連線建立起來,幾乎不肯釋放。

詳細一點說,我們的程式會呼叫API,DB連線以及Redis連線,這些都會使用到tcp連線,一但程式開始執行,機器就監控到tcp連線不斷飆升。

查看連線數的指令

# linux 系統地查看連線數指令
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n

netstat -ant | wc -l

實際觀測結果,當程式已經沒有在執行什麼東西,但建立起來的連線不會關閉或釋放,當需要連線的時候,整個重新建立連線,而不是重複利用已經存在的連線。

於是我們花了一些時間探討與研究。

1. Golang的資源釋放問題

連線放不掉,也意味該部分使用的記憶體也還沒放掉,GC的問題與heap和stack的記憶體使用概念有深刻的相關。

遇到問題的不是第一個使用Golang開發的專案,和之前的專案,不同的地方是業務性質,和內容的寫法有變化。

隨著經驗,當然專案規模會變大,寫法會進化,架構會改進,這是自然而然的事情,那為什麼看似更進步的寫法,反而會造成以前不會發生的問題,這點是非常地耐人尋味。

後來找到之前的使用連線方式寫法,那段程式碼比較單純和基本,變數宣告出來是屬於stack,離開了function當然所有東西都被消滅乾淨,因此不會有回收不了資源的問題。

而有架構化的寫法,連線使用相關的變數已經變成heap,屬於heap的資源什麼時候被回收,看Golang的GC演算法,看天意。

有實驗精神之下,我們好好地觀察什麼時候這些連線會被放掉,結果超過24小時,這些連線都還在,一個都不少。

那要改回舊寫法嗎?不!這個問題要知道根本原因。

不是說連線相關的變數弄成stack,控制在一個function寫完所有東西就好,這觀念在處理連線這塊並不正確。

2. 對待連線,想法和概念應該調整

比較正確的想法,應該是『連線建置的成本昂貴,應該重複利用』。

也許是在寫php這部分的概念比較薄弱,跟語言特性可能也有關係,也許公司的機器規格也不差,建了又砍,砍了又建對機器的負擔完全消費得起。

講到這裡,你可能也注意到連線的程式碼弄成stack有什麼弊病了,沒錯!死貧道不死道友,這種方式對自己的機器沒什麼差異,就是個程式變數重新宣告,然後重新去做連線而已,而倒霉的是我們的呼叫API對象,那台load balance機器不是只有我們在用,而它卻要承擔我們的連線時常建了又砍,每次都要建一個新的,這種鳥事的成本變成它一力負擔。

正確的方式,是Golang的http client 應該在第一次宣告出來之後,重複利用,database sql 也是一樣,重複地使用它才是最好的。

稍微看一下底層就可以知道,這兩者都幫你寫好一個很完善的連線池機制,為了就是讓你達到高效能,重複利用連線。

而每次都重新宣告一個新的實體,連線池的機制根本用不到也用不上,根本違背了Golang設計好的使用方式。

後續解決結果

我們將宣告出來的連線實體放到了全域變數,要使用連線時就重複使用這個變數,從此一帆風順。

也因為這個教訓,讓我們重新正視連線問題,也做個提醒,Golang要處理的問題面向,跟以前會遇到的狀況也不一樣。


上一篇
Day6 .[重災經驗篇] … 的傳入參數方式,自由度很大,但還是要小心
下一篇
Day8 .[正確資料篇] slice 與 map 加lock
系列文
Let's Eat GO ! 實務開發雜談by Golang30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言