今天經過一個由於併發控制不當致使下游服務崩潰的案例,給你們分享一個關於併發控制的誤區。前端
Go
語言原生支持併發,只要使用go
關鍵字就能把函數交給goroutine
來併發地執行一段程序,正是由於併發難度特別低,有很多人在掌握語法後就特別喜歡嘗試進行併發編程,包括我也是,不過我向來保持着對編程的敬畏之心(就是膽兒小~)因此那會剛用Go寫代碼時對併發嘗試地比較剋制,生怕寫出了線上BUG。編程
當時咱們是有一個異步給不活躍的用戶補發一些滿減卡券並觸達用戶的任務,是由組裏的一個從前端轉過來的老哥寫的,因爲全部接口都是現成的他只要寫個任務查出最近一週知足條件的用戶,異步地去調用公司內部的卡券服務就好了,維護卡券服務的同事給咱們的標準是併發請求的最大請求數不要超過500,結果當天上線後晚流量高峯的時候直接壓掛卡券系統。markdown
作代碼排查的時候,發現老哥直接用的time.Sleep
作的併發控制。像下面這段代碼同樣:併發
func badConcurrency() {
batchSize := 500
for {
data, _ := queryDataWithSizeN(batchSize)
if len(data) == 0 {
break
}
for _, uid := range data {
go func(i int) {
doSomething(i)
}(uid)
}
// 休眠1s
time.Sleep(time.Second * 1)
}
}
複製代碼
每查500條數據後,交給500個協程去執行髮卡券的任務,而後休眠1秒再重複上面的過程。有人確定要問了,誒~這麼看每秒是不超過500個請求啊!異步
em ~ 從調用方的角度看確實超不過500,可是卻沒有考慮下游服務也是分忙時和閒時的(由於是比較基礎的服務,不光一個業務方調)。若是下游服務正好在忙時,在1s內沒有處理完上一批發過來的500個請求,上游就又發過來 500 個請求,用不了多少時間服務就會達到過載狀態。函數
那麼有人會問,下游服務難道本身沒有作限流或者隊列暫存嗎?當時老哥也是這麼問的,理想狀況確實應該有,但那是理想狀況,現實狀況就是這個內部服務本身沒作限流,我相信這在你們的公司也是比較廣泛的狀況 。ui
那麼又有人會問:「這個事故不應是記在維護卡卷服務的研發頭上嗎?」 ,大哥們,若是真是這樣記事故,各研發組之間的部門牆得多厚啊,只要領導不太偏袒一方固然是各打五十大板,都整改啦。spa
針對這種請求,若是是你來寫這個調用方的程序,你認爲有哪些的方法既能使用併發增長調用方發起請求的能力又能照顧到下游服務的忙時負載呢?歡迎你們積極留言,下篇文章我會彙總幾個方案分享給你們。code