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請求
    }(ctx)

    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, 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請求
    }(ctx)

    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() {
    done := make(chan struct{}, 1)

    go func(ctx context.Context) {
        // 發送HTTP請求
        done <- struct{}{}
    }(ctx)

    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

 

如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM