1. 程式人生 > 程式設計 >Golang實現超時退出的三種方式

Golang實現超時退出的三種方式

前段時間發現線上有個服務介面,總是間歇性告警,有時候一天兩三次,有時候一天都沒有。

告警的邏輯是在一個介面中非同步呼叫了另一個HTTP介面,這個HTTP介面調用出現超時。但是我去問了負責這個HTTP介面的同學,人家說他們的介面相應都是毫秒級別,還截圖監控了,有圖有真相,我還能說啥。

但是,超時是確實存在的,只是請求還可能沒有到人家服務那邊。

這種偶發性問題不好復現,偶爾來個告警也挺煩的,第一反應還是先解決問題,思路也簡單,失敗後重試。

解決方法

且不談重試策略,先說說什麼時候觸發重試。

我們可以在介面請求出錯丟擲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
 }
}

說明

1、通過context的WithTimeout設定一個有效時間為800毫秒的context。

2、該context會在耗盡800毫秒後或者方法執行完成後結束,結束的時候會向通道ctx.Done傳送訊號。

3、有人可能要問,你這裡已經設定了context的有效時間,為什麼還要加上這個time.After呢?

這是因為該方法內的context是自己申明的,可以手動設定對應的超時時間,但是在大多數場景,這裡的ctx是從上游一直傳遞過來的,對於上游傳遞過來的context還剩多少時間,我們是不知道的,所以這時候通過time.After設定一個自己預期的超時時間就很有必要了。

4、注意,這裡要記得呼叫cancel()

,不然即使提前執行完了,還要傻傻等到800毫秒後context才會被釋放。

總結

上面的超時控制是搭配使用了ctx.Done和time.After。

Done通道負責監聽context啥時候完事,如果在time.After設定的超時時間到了,你還沒完事,那我就不等了,執行超時後的邏輯程式碼。

舉一反三

那麼,除了上面這種超時控制策略,還有其他的套路嗎?

有,但是大同小異。

第一種:使用time.NewTimer

func AsyncCall() {
 ctx,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
 }
}

1、這裡主要利用通道可以在協程之間通訊的特點,當呼叫成功後,向done通道傳送訊號。

2、監聽Done訊號,如果在time.After超時時間之前接收到,則正常返回,否則走向time.After的超時邏輯,執行超時邏輯程式碼。

3、這裡使用的是通道和time.After組合,也可以使用通道和time.NewTimer組合。

總結

本篇主要介紹如何實現超時控制,主要有三種

1、context.WithTimeout/context.WithDeadline + time.After

2、context.WithTimeout/context.WithDeadline + time.NewTimer

3、channel + time.After/time.NewTimer

到此這篇關於Golang三種方式實現超時退出的文章就介紹到這了,更多相關Golang超時退出內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!