1. 程式人生 > >Golang 介紹及踩坑系列之三

Golang 介紹及踩坑系列之三

聰明的你,用golang寫後端服務,各種使用channel和goroutine,把java要用執行緒池乾的事兒用攜程都搞定了。服務線下執行一切正常,壓測,單元測,聯調統統通過。你露出得意的微笑,一鍵釋出到生產環境,欣喜的發現服務崩潰了。

為什麼服務會崩潰呢?

channel死鎖

死鎖是golang裡邊最常見的一類問題,我們從java和c/c++轉過來的gopher在程式設計時候會特別注意mutex,semaphore,atomic等等的使用,反覆檢查會不會造成死鎖。但是我們有可能會忽略掉另一個死鎖大元凶:channel。

channel簡直就是用來死鎖的。

unbuffered channel讀和寫都是blocking的,也就是說讀一個沒人寫的channel和寫一個沒人讀的channel都會造成死鎖。死鎖狀態下的goroutine會永遠等待這個channel operation返回。這麼做的goroutine多了,直接就會造成記憶體洩漏,伺服器崩潰。

buffered channel不會嗎?

也會的。buffered channel寫滿了之後再寫就會死給你看。空的channel沒人的情況下去讀也死給你看。

怎麼避免呢?

這在官方的 effective go裡邊就有介紹。

第三方呼叫

我們往往不能天真的認為我們呼叫的上游服務不會掛,我們也不能淳樸的認為我們的網路環境永遠是可靠的。往往一個小波動就會使得我們好多goroutine臨時的卡在第三方api呼叫上。比如正常狀況下我們期待所有goroutine20ms都會完成。但是突然網路抖動,或者第三方服務穩定性出了一點小問題,我們的goroutine都要2秒鐘才會完成。

可能有人會覺得,不就是慢了一點嗎,有什麼大關係。

同志們,關係非常大。本來goroutine 20ms就退出的情況下,我們可能同時也就是幾千個goroutine。如果goroutine生命週期變長,我們就會瞬間攢下來100*幾千個goroutine,如果不加以限制的話,我們的服務就會瞬間崩潰。

所以別人的問題,可能會導致我們自己服務的失敗。

怎麼辦?

使用circuit breaker。

我們想這樣保護我們的服務:

首先設定SLA (service level agreement)

平均響應時間

容錯率

max qps

我們需要monitor第三方服務的調用出錯率。當出錯率高於某個閾值(超時也是一種錯誤),我們需要暫時對服務呼叫這個操作直接報錯,這樣呼叫的goroutine就可以迅速的退出。

我們還需要不斷的嘗試,看看呼叫狀況是不是變回良好可用的狀態,如果是,我們就得恢復正常的呼叫機制。並且重新開始計算出錯率。

這個pattern其實就是大家常說的熔斷器(circuit breaker)

熔斷器的一個golang實現:afex/hystrix-go

怎麼觀測我們的服務承受的壓力?

普羅米修斯:Prometheus

資料狗:Modern monitoring & analytics

NewRelic: Digital Performance Monitoring and Management | New Relic

如何debug到底goroutine卡在什麼地方?

pprof : golang.org/pkg/net/http

這篇文章粗淺無比的談了一下golang服務如何防止,檢測,排查goroutine過多引起的線上故障。意在拋磚引玉。聰明的你,我們一起學習,一起深入討論。

希望我們露出得意微笑部署golang服務的時候,可以更加自信,更加成竹在胸。