Go: 屏障併發模式

什麼是屏障併發模式?

假設有這麼一種場景,一個微服務需要調其他兩個微服務,通過組合他們的響應結果來返回給客戶端。屏障併發模式在這裏就有用武之地了。

屏障併發模式會使一個服務阻塞等待給客戶端響應結果,直到從其他一個或多個不同的 Goroutine(服務) 中獲取到返回內容。怎樣才能使服務具有阻塞性質?我們可以用鎖,但是在 Go 中更習慣使用通道。

目標

顧名思義,屏障模式就是讓程序阻塞直到準備就緒爲止。其目標就是:

聚合 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