Go語言實(shí)現(xiàn)請(qǐng)求超時(shí)處理的方法總結(jié)
1. 簡介
本文將介紹Go語言中實(shí)現(xiàn)請(qǐng)求的超時(shí)控制的方法,主要是通過timer
和timerCtx
來實(shí)現(xiàn)請(qǐng)求的超時(shí)控制。
但是在本文中,暫未展示在哪些場(chǎng)景下,timerCtx
實(shí)現(xiàn)超時(shí)控制相對(duì)于timer
實(shí)現(xiàn)的優(yōu)點(diǎn),或者在哪些場(chǎng)景下,timer
相對(duì)于timerCtx
在哪些場(chǎng)景下使用更為合適,后續(xù)將會(huì)再進(jìn)行描述。
2. 問題引入
當(dāng)使用Go語言進(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí),程序可能會(huì)因?yàn)檎?qǐng)求處理時(shí)間過長而被卡住,無法繼續(xù)執(zhí)行后續(xù)代碼。這種情況會(huì)導(dǎo)致程序性能下降,用戶體驗(yàn)變差,甚至?xí)?dǎo)致系統(tǒng)崩潰。特別是在高并發(fā)場(chǎng)景下,這種問題更加突出。
舉個(gè)例子,假設(shè)我們需要從一個(gè)遠(yuǎn)程服務(wù)獲取一些數(shù)據(jù),我們可以使用Go標(biāo)準(zhǔn)庫中的http包進(jìn)行網(wǎng)絡(luò)請(qǐng)求。代碼可能類似于以下示例:
func makeRequest(url string) (string, error) { // 創(chuàng)建 http.Client 客戶端實(shí)例 client := &http.Client{} // 創(chuàng)建請(qǐng)求 req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } // 執(zhí)行請(qǐng)求 resp, err := client.Do(req) if err != nil { return "", err } // 讀取響應(yīng)內(nèi)容 defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } return string(body), nil } func main() { url := "https://baidu.com" result, err := makeRequest(url) if err != nil { return } }
這里定義了一個(gè)makeRequest
函數(shù),該函數(shù)使用http.Client
客戶端發(fā)送HTTP
請(qǐng)求并返回響應(yīng)體。
但是,如果請(qǐng)求響應(yīng)時(shí)間過長,程序就會(huì)一直等待直到請(qǐng)求超時(shí)或者響應(yīng)返回。如果是單個(gè)請(qǐng)求的情況下,這種等待可能不會(huì)對(duì)系統(tǒng)產(chǎn)生太大的影響。但是在高并發(fā)場(chǎng)景下,這種情況可能會(huì)導(dǎo)致系統(tǒng)性能大幅下降。
因此,我們需要一種方法來對(duì)請(qǐng)求進(jìn)行超時(shí)處理,確保程序能夠及時(shí)響應(yīng)其他請(qǐng)求,而不是一直等待。
3. timer的實(shí)現(xiàn)方案
3.1 timer的基本介紹
Timer
可以通過time.NewTimer()
或time.AfterFunc()
函數(shù)創(chuàng)建。NewTimer()
函數(shù)創(chuàng)建一個(gè)Timer
對(duì)象,該對(duì)象在指定的時(shí)間間隔后向一個(gè)通道發(fā)送一個(gè)當(dāng)前時(shí)間。AfterFunc()
函數(shù)則會(huì)在指定的時(shí)間間隔后執(zhí)行一個(gè)函數(shù)。
通過timer
,可以實(shí)現(xiàn)許多常見的任務(wù),比如定期執(zhí)行某個(gè)操作、超時(shí)控制、任務(wù)調(diào)度等。同時(shí),在Go語言中,timer
還可以方便地取消或重置,能夠更加靈活地控制程序的運(yùn)行。
所以,這里我們可以使用timer
實(shí)現(xiàn)請(qǐng)求的超時(shí)控制,下面我們來看使用timer
來實(shí)現(xiàn)超時(shí)控制的具體步驟。
3.2 timer實(shí)現(xiàn)超時(shí)控制
如果需要使用timer
實(shí)現(xiàn)請(qǐng)求的超時(shí)控制,可以通過以下步驟來實(shí)現(xiàn)請(qǐng)求的超時(shí)處理,具體如下:
- 創(chuàng)建一個(gè)
timer
對(duì)象??梢允褂?code>time.NewTimer()函數(shù)創(chuàng)建一個(gè)新的timer對(duì)象 - 啟動(dòng)一個(gè)goroutine來執(zhí)行具體的業(yè)務(wù)邏輯
- 在
select
語句中處理超時(shí)事件。在select
語句中,使用一個(gè)case來處理timer
的超時(shí)事件 - 在需要控制超時(shí)的地方使用上述邏輯
下面是一個(gè)示例代碼,演示了如何使用timer實(shí)現(xiàn)超時(shí)控制:
package main import ( "fmt" "time" ) func main() { // 1. 創(chuàng)建一個(gè)timer對(duì)象,等待5秒鐘 timeout := time.NewTimer(5 * time.Second) ch := make(chan string, 1) go func() { // 2. 這里我們簡單模擬一個(gè)需要執(zhí)行10秒的操作 time.Sleep(10 * time.Second) ch <- "hello world" }() // 3. 在select語句中處理超時(shí)事件 或者請(qǐng)求正常返回 select { case <-timeout.C: // 執(zhí)行任務(wù)超時(shí)處理 fmt.Println("操作超時(shí)") return case result := <-ch: // 執(zhí)行正常業(yè)務(wù)流程 fmt.Println(result) } // 停止timer if !timeout.Stop() { <-timeout.C } // 操作執(zhí)行完成 fmt.Println("操作執(zhí)行完成") }
這里在主協(xié)程處通過NewTimer
創(chuàng)建一個(gè)定時(shí)器,然后啟動(dòng)一個(gè)協(xié)程對(duì)任務(wù)進(jìn)行處理,當(dāng)處理完成后,通過channel
告知其他協(xié)程。
在主協(xié)程中,通過select
語句,對(duì)定時(shí)器timer
和channel
同時(shí)進(jìn)行監(jiān)聽,當(dāng)任務(wù)執(zhí)行超時(shí)時(shí),則執(zhí)行超時(shí)邏輯;如果任務(wù)在超時(shí)前完成,則執(zhí)行正常處理流程。
通過這種方式,實(shí)現(xiàn)了請(qǐng)求的超時(shí)處理。
3.3 對(duì)問題的解決
下面展示使用 timer
來實(shí)現(xiàn)對(duì)請(qǐng)求的超時(shí)處理,從而避免程序長期處于等待狀態(tài),造成系統(tǒng)性能大幅下降。
func makeRequest(url string) (string, error) { // 具體的業(yè)務(wù)邏輯 } func main() { url := "https://baidu.com" // 設(shè)置超時(shí)時(shí)間為5秒 timeout := 5 * time.Second // 創(chuàng)建一個(gè)計(jì)時(shí)器,等待超時(shí) timer := time.NewTimer(timeout) // 創(chuàng)建一個(gè) channel,用于接收請(qǐng)求的結(jié)果 ch := make(chan string, 1) // 啟動(dòng)協(xié)程執(zhí)行請(qǐng)求 go func() { result, err := makeRequest(url) if err != nil { ch <- fmt.Sprintf("Error: %s", err.Error()) return } ch <- result }() // 等待超時(shí)或者請(qǐng)求結(jié)果返回 select { case result := <-ch: fmt.Println(result) case <-timer.C: fmt.Println("Request timed out") } // 請(qǐng)求完成后,停止定時(shí)器 if !timer.Stop() { <-timer.C } }
在這個(gè)示例中,我們使用 time
包創(chuàng)建一個(gè)計(jì)時(shí)器,等待超時(shí)。同時(shí),我們還創(chuàng)建了一個(gè) channel,用于接收請(qǐng)求的結(jié)果。然后我們啟動(dòng)一個(gè)協(xié)程執(zhí)行請(qǐng)求,一旦請(qǐng)求返回,就會(huì)將結(jié)果發(fā)送到 channel 中。在主協(xié)程中,我們使用 select
語句等待超時(shí)或者請(qǐng)求結(jié)果返回。如果請(qǐng)求在超時(shí)之前返回,就會(huì)從 channel 中接收到結(jié)果并打印出來。如果請(qǐng)求超時(shí),就會(huì)打印出相應(yīng)的錯(cuò)誤信息。
從而實(shí)現(xiàn)了避免了處理某些場(chǎng)景請(qǐng)求時(shí),避免系統(tǒng)進(jìn)入長時(shí)間等待的問題的出現(xiàn)。
4.timetCtx的實(shí)現(xiàn)方案
雖然,timer
和select
實(shí)現(xiàn)超時(shí)控制的邏輯并不復(fù)雜,但是在某些場(chǎng)景下,使用timerCtx
來實(shí)現(xiàn)超時(shí)控制,相對(duì)來說是更為簡單的,而且現(xiàn)有開源框架基本上也是通過該方式來實(shí)現(xiàn)的。所以接下來,我們來對(duì)timerCtx
進(jìn)行基本介紹,同時(shí)使用timerCtx
來實(shí)現(xiàn)超時(shí)控制。
4.1 timerCtx的基本介紹
timerCtx
是一種在Go語言中使用Context
和Timer
結(jié)合實(shí)現(xiàn)超時(shí)控制的方式。它是一個(gè)自定義的結(jié)構(gòu)體類型,用于封裝定時(shí)器和取消函數(shù),并提供一種方便的方式來取消goroutine
的執(zhí)行,從而避免出現(xiàn)goroutine
泄露等問題。
4.2 timerCtx的基本使用方式
當(dāng)使用timetCtx
實(shí)現(xiàn)超時(shí)控制,通常需要以下幾個(gè)步驟:
- 調(diào)用
context.WithTimeout()
方法,創(chuàng)建一個(gè)超時(shí)控制的子上下文。 - 啟動(dòng)一個(gè)協(xié)程來執(zhí)行任務(wù)。
- 在主協(xié)程中,通過
select
語句調(diào)用Done()
方法來判斷是否超時(shí)。如果Done()
方法返回的channel
被關(guān)閉,則意味著已經(jīng)超時(shí),需要及時(shí)停止當(dāng)前任務(wù)并返回。 - 在函數(shù)返回時(shí),調(diào)用取消函數(shù)
cancel()
,釋放占用的資源。
下面是一個(gè)示例代碼,演示了如何使用timerCtx
實(shí)現(xiàn)超時(shí)控制:
package main import ( "context" "fmt" "time" ) func main() { // 創(chuàng)建一個(gè)timerCtx,設(shè)置超時(shí)時(shí)間為3秒 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 調(diào)用cancel函數(shù),釋放占用的資源 defer cancel() // 開啟一個(gè)協(xié)程執(zhí)行任務(wù) ch := make(chan string, 1) go func() { // 模擬任務(wù)執(zhí)行,休眠5秒 time.Sleep(5 * time.Second) ch <- "hello world" }() // 在主協(xié)程中等待timerCtx超時(shí)或任務(wù)完成 select { case <-ctx.Done(): fmt.Println("timeout") case result := <-ch: fmt.Println(result) } }
這里在主協(xié)程處通過context.WithTimeout
創(chuàng)建一個(gè)timerCtx
,然后啟動(dòng)一個(gè)協(xié)程對(duì)任務(wù)進(jìn)行處理,當(dāng)處理完成后,通過channel
告知其他協(xié)程。
其次,對(duì)于timerCtx
來說,調(diào)用Done
方法將會(huì)返回一個(gè)channal
,當(dāng)超時(shí)后,該channel
將會(huì)自動(dòng)被關(guān)閉,此時(shí)通過select
,將能夠從該處于close
狀態(tài)的channel
中接收到數(shù)據(jù)。
因此,在主協(xié)程中,通過select
語句,對(duì)這兩個(gè)channel
同時(shí)進(jìn)行監(jiān)聽,當(dāng)任務(wù)執(zhí)行超時(shí)時(shí),則執(zhí)行超時(shí)邏輯;如果任務(wù)在超時(shí)前完成,則執(zhí)行正常處理流程。通過這種方式,實(shí)現(xiàn)了請(qǐng)求的超時(shí)處理。
4.3 對(duì)問題的解決
下面使用 context.WithTimeout
和 select
來實(shí)現(xiàn)請(qǐng)求的超時(shí)處理,通過這種方式,避免程序長期處于等待狀態(tài),具體代碼實(shí)現(xiàn)如:
// 執(zhí)行具體的業(yè)務(wù)邏輯 func makeRequest(ctx context.Context, url string) (string, error) {} func main() { url := "https://baidu.com" // 創(chuàng)建一個(gè)不帶超時(shí)的context ctx := context.Background() // 1. 創(chuàng)建一個(gè)帶超時(shí)的timerCtx timeout := 5 * time.Second timerCtx, cancel := context.WithTimeout(ctx, timeout) //5. 在函數(shù)返回時(shí),調(diào)用取消函數(shù) cancel(),釋放占用的資源。 defer cancel() // 創(chuàng)建一個(gè) channel,用于接收請(qǐng)求的結(jié)果 ch := make(chan string, 1) // 2. 將子上下文傳遞給需要進(jìn)行超時(shí)控制的函數(shù), 啟動(dòng)協(xié)程執(zhí)行請(qǐng)求 go func() { result, err := makeRequest(ctx,url) if err != nil { ch <- fmt.Sprintf("Error: %s", err.Error()) return } ch <- result }() // 函數(shù)可以通過調(diào)用 context.Context 對(duì)象的 Done() 方法來判斷是否超時(shí)。 // 如果 Done() 方法返回的 channel 被關(guān)閉,則意味著已經(jīng)超時(shí),需要及時(shí)停止當(dāng)前任務(wù)并返回。 select { case result := <-ch: fmt.Println(result) case <-timerCtx.Done(): fmt.Println("Request timed out") } }
在這個(gè)例子中,我們使用 context.WithTimeout
創(chuàng)建一個(gè)帶有超時(shí)的 context 對(duì)象,設(shè)置超時(shí)時(shí)間為 5秒鐘。handleRequest
來執(zhí)行對(duì)應(yīng)的任務(wù),將timeCtx
傳遞給handleRequest
,如果沒有在對(duì)應(yīng)時(shí)間內(nèi)正常返回,此時(shí)任務(wù)會(huì)直接返回,不會(huì)無限期執(zhí)行下去。
在任務(wù)執(zhí)行過程中,通過select
不斷檢查 ctx.Done()
方法的返回值,如果超時(shí)時(shí)間到了,ctx.Done()
的結(jié)果將變?yōu)橐粋€(gè)非 nil
的值,這時(shí)我們就可以在 select
語句中執(zhí)行超時(shí)處理的邏輯。
最后,在任務(wù)返回后,調(diào)用取消函數(shù) cancel()
,釋放占用的資源。
從上面timer
實(shí)現(xiàn)超時(shí)控制,或者是使用timerCtx
的實(shí)現(xiàn)來看,其實(shí)二者區(qū)別并不大,但是事實(shí)上,現(xiàn)在任務(wù)的超時(shí)控制,基本上都是使用timerCtx
實(shí)現(xiàn)的,并非使用timer
來實(shí)現(xiàn)的,后續(xù)將會(huì)對(duì)其進(jìn)行說明。
5. 總結(jié)
在這篇文章中,我們通過網(wǎng)絡(luò)請(qǐng)求這個(gè)常見的場(chǎng)景,描述其可能導(dǎo)致的問題,從而引出了請(qǐng)求的超時(shí)控制。同時(shí),在Go
語言中,可以同時(shí)通過timer
和timerCtx
來實(shí)現(xiàn)超時(shí)控制,在這篇文章中,主要的內(nèi)容,便是簡單介紹了如何通過timer
和timerCtx
來實(shí)現(xiàn)超時(shí)控制,希望對(duì)你有所幫助。
但是,在這篇文章中,并沒有介紹timerCtx
或者timer
的實(shí)現(xiàn)原理。同時(shí),也暫未展示在哪些場(chǎng)景下,timerCtx
實(shí)現(xiàn)超時(shí)控制相對(duì)于timer
實(shí)現(xiàn)的優(yōu)點(diǎn),或者在哪些場(chǎng)景下,timer
相對(duì)于timerCtx
在哪些場(chǎng)景下使用更為合適,這些內(nèi)容將會(huì)在后文進(jìn)行描述。
到此這篇關(guān)于Go語言實(shí)現(xiàn)請(qǐng)求超時(shí)處理的方法總結(jié)的文章就介紹到這了,更多相關(guān)Go語言請(qǐng)求超時(shí)處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中文件目錄操作的實(shí)現(xiàn)步驟詳解
在Golang中,文件目錄是指計(jì)算機(jī)文件系統(tǒng)中的文件夾或目錄。目錄是用于組織和存儲(chǔ)文件的一種方式,可以包含文件和其他子目錄,本文主要介紹了Golang中文件目錄操作的實(shí)現(xiàn)方法,需要的朋友可以參考下2023-05-05Golang中基礎(chǔ)的命令行模塊urfave/cli的用法說明
這篇文章主要介紹了Golang中基礎(chǔ)的命令行模塊urfave/cli的用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12go-zero熔斷機(jī)制組件Breaker接口定義使用解析
這篇文章主要為大家介紹了go-zero熔斷機(jī)制組件Breaker接口定義使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05golang使用go test輸出單元測(cè)試覆蓋率的方式
單元測(cè)試覆蓋率是衡量代碼質(zhì)量的一個(gè)重要指標(biāo),重要的代碼文件覆蓋率應(yīng)該至少達(dá)到80%以上,Java 可以通過JaCoCo 統(tǒng)計(jì)覆蓋率,那么go 項(xiàng)目如何進(jìn)行代碼覆蓋率測(cè)試呢,本文將給大家詳細(xì)的介紹一下golang使用go test輸出單元測(cè)試覆蓋率的方式,需要的朋友可以參考下2024-02-02