問題
前段時間發(fā)現(xiàn)線上有個服務(wù)接口钓葫,總是間歇性告警威兜,有時候一天兩三次嚎尤,有時候一天都沒有。
告警的邏輯是在一個接口中異步調(diào)用了另一個HTTP接口伞鲫,這個HTTP接口調(diào)用出現(xiàn)超時库物。但是我去問了負(fù)責(zé)這個HTTP接口的同學(xué)霸旗,人家說他們的接口相應(yīng)都是毫秒級別,還截圖監(jiān)控了戚揭,有圖有真相诱告,我還能說啥。
但是民晒,超時是確實存在的精居,只是請求還可能沒有到人家服務(wù)那邊锄禽。
這種偶發(fā)性問題不好復(fù)現(xiàn),偶爾來個告警也挺煩的靴姿,第一反應(yīng)還是先解決問題沃但,思路也簡單,失敗后重試佛吓。
解決方法
且不談重試策略宵晚,先說說什么時候觸發(fā)重試。
我們可以在接口請求出錯拋出err的時候重試维雇,但是這種不好控制淤刃,如果一個請求出去,十來秒都沒有響應(yīng)吱型,則這個協(xié)程就要傻傻的等他報錯才能重試逸贾,浪費生命啊~
所以結(jié)合上面同學(xué)給出的毫秒級響應(yīng)指標(biāo),可以設(shè)定一個超時時間津滞,如果在指定超時時間后沒有返回結(jié)果铝侵,則重試(這篇重試不是重點)。
func AsyncCall() {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
defer cancel()
go func(ctx context.Context) {
// 發(fā)送HTTP請求
}()
select {
case <-ctx.Done():
fmt.Println("call successfully!!!")
return
case <-time.After(time.Duration(time.Millisecond * 900)):
fmt.Println("timeout!!!")
return
}
}
說明
1据沈、通過context的WithTimeout設(shè)置一個有效時間為800毫秒的context哟沫。
2、該context會在耗盡800毫秒后或者方法執(zhí)行完成后結(jié)束锌介,結(jié)束的時候會向通道ctx.Done發(fā)送信號嗜诀。
3、有人可能要問孔祸,你這里已經(jīng)設(shè)置了context的有效時間隆敢,為什么還要加上這個time.After呢?
這是因為該方法內(nèi)的context是自己申明的崔慧,可以手動設(shè)置對應(yīng)的超時時間拂蝎,但是在大多數(shù)場景,這里的ctx是從上游一直傳遞過來的惶室,對于上游傳遞過來的context還剩多少時間温自,我們是不知道的,所以這時候通過time.After設(shè)置一個自己預(yù)期的超時時間就很有必要了皇钞。
4悼泌、注意,這里要記得調(diào)用cancel()夹界,不然即使提前執(zhí)行完了馆里,還要傻傻等到800毫秒后context才會被釋放。
總結(jié)
上面的超時控制是搭配使用了ctx.Done和time.After。
Done通道負(fù)責(zé)監(jiān)聽context啥時候完事鸠踪,如果在time.After設(shè)置的超時時間到了丙者,你還沒完事,那我就不等了营密,執(zhí)行超時后的邏輯代碼械媒。
舉一反三
那么,除了上面這種超時控制策略卵贱,還有其他的套路嗎滥沫?
有,但是大同小異键俱。
第一種:使用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) {
// 發(fā)送HTTP請求
}()
select {
case <-ctx.Done():
timer.Stop()
timer.Reset(time.Second)
fmt.Println("call successfully!!!")
return
case <-timer.C:
fmt.Println("timeout!!!")
return
}
}
這里的主要區(qū)別是將time.After換成了time.NewTimer,也是同樣的思路如果接口調(diào)用提前完成世分,則監(jiān)聽到Done信號编振,然后關(guān)閉定時器。
否則的話臭埋,會在指定的timer即900毫秒后執(zhí)行超時后的業(yè)務(wù)邏輯踪央。
第二種:使用通道
func AsyncCall() {
ctx := context.Background()
done := make(chan struct{}, 1)
go func(ctx context.Context) {
// 發(fā)送HTTP請求
done <- struct{}{}
}()
select {
case <-done:
fmt.Println("call successfully!!!")
return
case <-time.After(time.Duration(800 * time.Millisecond)):
fmt.Println("timeout!!!")
return
}
}
1、這里主要利用通道可以在協(xié)程之間通信的特點瓢阴,當(dāng)調(diào)用成功后畅蹂,向done通道發(fā)送信號。
2荣恐、監(jiān)聽Done信號液斜,如果在time.After超時時間之前接收到叠穆,則正常返回少漆,否則走向time.After的超時邏輯,執(zhí)行超時邏輯代碼硼被。
3示损、這里使用的是通道和time.After組合,也可以使用通道和time.NewTimer組合嚷硫。
總結(jié)
本篇主要介紹如何實現(xiàn)超時控制检访,主要有三種
1、context.WithTimeout/context.WithDeadline + time.After
2仔掸、context.WithTimeout/context.WithDeadline + time.NewTimer
3脆贵、channel + time.After/time.NewTimer