前段時間發現線上有個服務接口,老是間歇性告警,有時候一天兩三次,有時候一天都沒有。異步
告警的邏輯是在一個接口中異步調用了另外一個HTTP接口,這個HTTP接口調用出現超時。可是我去問了負責這個HTTP接口的同窗,人家說他們的接口相應都是毫秒級別,還截圖監控了,有圖有真相,我還能說啥。ui
可是,超時是確實存在的,只是請求還可能沒有到人家服務那邊。spa
這種偶發性問題很差復現,偶爾來個告警也挺煩的,第一反應仍是先解決問題,思路也簡單,失敗後重試。code
且不談重試策略,先說說何時觸發重試。cdn
咱們能夠在接口請求出錯拋出err的時候重試,可是這種很差控制,若是一個請求出去,十來秒都沒有響應,則這個協程就要傻傻的等他報錯才能重試,浪費生命啊~協程
因此結合上面同窗給出的毫秒級響應指標,能夠設定一個超時時間,若是在指定超時時間後沒有返回結果,則重試(這篇重試不是重點)。接口
func AsyncCall() {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
defer cancel()
go func(ctx context.Context) {
// 發送HTTP請求
}()
select {
case <-ctx.Done():
fmt.Println("call successfully!!!")
return
case <-time.After(time.Duration(time.Millisecond * 900)):
fmt.Println("timeout!!!")
return
}
}
複製代碼
說明string
一、經過context的WithTimeout設置一個有效時間爲800毫秒的context。it
二、該context會在耗盡800毫秒後或者方法執行完成後結束,結束的時候會向通道ctx.Done發送信號。io
三、有人可能要問,你這裏已經設置了context的有效時間,爲何還要加上這個time.After呢?
這是由於該方法內的context是本身申明的,能夠手動設置對應的超時時間,可是在大多數場景,這裏的ctx是從上游一直傳遞過來的,對於上游傳遞過來的context還剩多少時間,咱們是不知道的,因此這時候經過time.After設置一個本身預期的超時時間就頗有必要了。
四、注意,這裏要記得調用cancel(),否則即便提早執行完了,還要傻傻等到800毫秒後context纔會被釋放。
總結
上面的超時控制是搭配使用了ctx.Done和time.After。
Done通道負責監聽context啥時候完事,若是在time.After設置的超時時間到了,你還沒完事,那我就不等了,執行超時後的邏輯代碼。
那麼,除了上面這種超時控制策略,還有其餘的套路嗎?
有,可是大同小異。
第一種:使用time.NewTimer
func AsyncCall() {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond * 800))
defer cancel()
timer := time.NewTimer(time.Duration(time.Millisecond * 900))
go func(ctx context.Context) {
// 發送HTTP請求
}()
select {
case <-ctx.Done():
timer.Stop()
timer.Reset(time.Second)
fmt.Println("call successfully!!!")
return
case <-timer.C:
fmt.Println("timeout!!!")
return
}
}
複製代碼
這裏的主要區別是將time.After換成了time.NewTimer,也是一樣的思路若是接口調用提早完成,則監聽到Done信號,而後關閉定時器。
不然的話,會在指定的timer即900毫秒後執行超時後的業務邏輯。
第二種:使用通道
func AsyncCall() {
ctx := context.Background()
done := make(chan struct{}, 1)
go func(ctx context.Context) {
// 發送HTTP請求
done <- struct{}{}
}()
select {
case <-done:
fmt.Println("call successfully!!!")
return
case <-time.After(time.Duration(800 * time.Millisecond)):
fmt.Println("timeout!!!")
return
}
}
複製代碼
一、這裏主要利用通道能夠在協程之間通訊的特色,當調用成功後,向done通道發送信號。
二、監聽Done信號,若是在time.After超時時間以前接收到,則正常返回,不然走向time.After的超時邏輯,執行超時邏輯代碼。
三、這裏使用的是通道和time.After組合,也可使用通道和time.NewTimer組合。
本篇主要介紹如何實現超時控制,主要有三種
一、context.WithTimeout/context.WithDeadline + time.After
二、context.WithTimeout/context.WithDeadline + time.NewTimer
三、channel + time.After/time.NewTimer
我的公衆號JackieZheng,歡迎關注~