Go: 屏障併發模式
什麼是屏障併發模式?
假設有這麼一種場景,一個微服務需要調其他兩個微服務,通過組合他們的響應結果來返回給客戶端。屏障併發模式在這裏就有用武之地了。
屏障併發模式會使一個服務阻塞等待給客戶端響應結果,直到從其他一個或多個不同的 Goroutine(服務) 中獲取到返回內容。怎樣才能使服務具有阻塞性質?我們可以用鎖,但是在 Go 中更習慣使用通道。
目標
顧名思義,屏障模式就是讓程序阻塞直到準備就緒爲止。其目標就是:
-
組合來自一個或多個 goroutine 的數據。
-
判斷這些來自不同 goroutine 返回的結果是否正確。必須要所有的 goroutine 返回的結果都正確才能繼續執行。
聚合 HTTP 請求
例如,在一個微服務當中需要執行兩個 HTTP GET 請求,然後將請求的返回結果組合成一個完整內容打印到控制檯。這裏 HTTP 請求必須在不同的 Goroutine 中執行,只有兩個請求都返回正確纔會打印。如果任意請求返回錯誤,只打印錯誤。設計必須併發執行,可以利用多核併發執行 HTTP 請求。
在上面的圖中,實線表示 HTTP 請求,虛線表示請求響應通道。main 函數通過兩個 Goroutine 發起兩個請求。兩個 Goroutine 共享一個通道將結果返回給 main 函數。這個應用程序的主要目標是獲得兩個不同調用的合併結果。
首先需要一個函數來爲每個 HTTP 請求創建 Goroutine。你還記得 Goroutine 之間如何通信的吧?沒錯是用 channel!因此需要一個 channel 來處理請求的返回結果或錯誤。但可以稍微簡化,將每個請求的響應和錯誤放在一個 barrierResp 結構體中,如以下代碼所示:
代碼實現
import (
"fmt"
"io/ioutil"
"net/http"
"time"
)
var timeoutMilliseconds int = 5000
type barrierResp struct {
Err error
Resp string
}
funcbarrier(endpoints ...string) {
requestNumber := len(endpoints)
in := make(chan barrierResp, requestNumber)
defer close(in)
responses := make([]barrierResp, requestNumber)
for _, endpoint := range endpoints {
go makeRequest(in, endpoint)
}
var hasError bool
for i := 0; i < requestNumber; i++ {
resp := <-in
if resp.Err != nil {
fmt.Println("ERROR: ", resp.Err)
hasError = true
}
responses[i] = resp
}
if !hasError {
for _, resp := range responses {
fmt.Println(resp.Resp)
}
}
}
代碼很簡單,每個 Goroutine 都返回一個 barrierResp 類型。通過一個緩衝通道來接收不同 Goroutine 的返回結果,並對返回的結果進行檢查是否返回有錯誤。
func makeRequest(out chan<- barrierResp, url string) {
res := barrierResp{}
client := http.Client{
Timeout: time.Duration(timeoutMilliseconds) * time.Millisecond,
}
resp, err := client.Get(url)
if err != nil {
res.Err = err
out <- res
return
}
byt, err := ioutil.ReadAll(resp.Body)
if err != nil {
res.Err = err
out <- res
return
}
res.Resp = string(byt)
out <- res
}
makeRequest 函數很簡單,接受一個 channel 來發送返回結果。這裏通過 http.Client 設置請求超時。最後將請求結果通過 out 通道發送給 barrier 函數。
注意:發起 HTTP 請求必須設置超時時間,否則程序可能會阻塞。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/I31Q7Dw0DAXTa-91ZuOpDA