Go:API 併發模式

本文旨在簡潔地向您展示作者花了很長時間組裝起來的東西,從而節省您的時間和精力。希望您發現裏面的內容是一個具有啓發性的、實用的、真實場景的 API 併發案例。

API 併發模式

如果您遵循下面列出的準則,就可以以最少的錯誤和工作量來構建一個高度併發的 API。
1、使用 go 關鍵字來異步執行,可以達到併發的目的
2、創建和關閉專用通道,這樣可以最大限度地減少內存泄漏和死鎖的機會。
3、使用 context.Context,停止不再需要的掛起請求。
4、使用指針代替 channel 返回結果,可以減少需要管理的 channel 數量。
5、通過 <-chan error 返回錯誤,您可以在返回響應之前等待阻塞操作完成。

就是這些!如果你堅持這 5 條規則,就可以寫出可讀的、整潔的 Go 代碼,而且具有高度併發,不會出現死鎖或內存泄漏。

代碼示例

下面是使用這些規則的示例實現。希望可以說明如何寫出易於閱讀、測試和維護的高併發 API 代碼。

API 請求

步驟 1:異步地發出所有 API 請求和阻塞操作。

// Piece 表示部分的響應結果
type Piece struct {
    ID uint `json:"id"`
}

// getPiece 調用`GET /piece/:id`
funcgetPiece(ctx context.Context, id uint, piece *Piece) <-chan error {
    out := make(chan error)
    go func() {
        // 關閉channel,合理管理內存
        defer close(out)

        // NewRequestWithContext在調用者取消ctx時會自動取消請求
        req, err := http.NewRequestWithContext(
            ctx, 
            "GET", 
            fmt.Sprintf("api.url.com/piece/%d", id), 
            nil,
        )
        if err != nil {
            out <- err
            return
        }

        // 發起請求
        rsp, err := http.DefaultClient.Do(req)
        if err != nil {
            out <- err
            return
        } else if rsp.StatusCode != http.StatusOK {
            out <- fmt.Errorf("%d: %s", rsp.StatusCode, rsp.Status)
            return
        }

        // 將響應結果解析到piece
        defer rsp.Body.Close()
        if err := json.NewDecoder(rsp.Body).Decode(piece); err != nil {
            out <- err
            return
        }
    }()
    return out
}

API 響應

步驟 2:將多個阻塞操作和 API 請求組合到一個響應結構體中

//  Result是將併發檢索的多個阻塞操作組合在一起
type Result struct {
    FirstPiece  *Piece `json:"firstPiece,omitempty"`
    SecondPiece *Piece `json:"secondPiece,omitempty"`
    ThirdPiece  *Piece `json:"thirdPiece,omitempty"`
}

// GetResult提供API請求到處理程序
func GetResult(w http.ResponseWriter, r *http.Request) {
    // 解析和驗證請求參數…

    // getResult立即停止如果http.Request被取消
    var result Result
    if err := <-getResult(r.Context(), &result); err != nil {
        w.Write([]byte(err.Error()))
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // 對響應結果進行序列號
    bs, err := json.Marshal(&result)
    if err != nil {
        w.Write([]byte(err.Error()))
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // 成功!
    w.Write(bs)
    w.WriteHeader(http.StatusOK)
}

// getResult 返回多個併發API調用的結果
func getResult(ctx context.Context, result *Result) <-chan error {
    out := make(chan error)
    go func() {
        // 正確管理內存
        defer close(out)

        // 如果有一個請求失敗,cancel func將允許我們停止所有掛起的請求
        ctx, cancel := context.WithCancel(ctx)

        // Merge將所有getPieces返回的errors統一到一個“<-chan error"中
        //如果沒有發生錯誤,Merge會等待所有<-chan error關閉
        for err := range util.Merge(
            getPiece(ctx, 1, result.FirstPiece),
            getPiece(ctx, 2, result.SecondPiece),
            getPiece(ctx, 3, result.ThirdPiece),
        ) {
            if err != nil {

                // 取消所有掛起的請求
                cancel()

                // 將錯誤傳給調用者
                out <- err
                return
            }
        }
    }()
    return out
}

Merge 函數

步驟 3:實現一個聚合函數。即使你非常熟悉 go,這裏也可能是最複雜和最容易出錯的部分。我建議直接複製黏貼這段代碼到你自己的 util 包中。

package util

import (
    "sync"
)

// 合併多個錯誤通道到一個錯誤通道中
func Merge(errChans ...<-chan error) <-chan error {
    mergedChan := make(chan error)

    // 創建WaitGroup等待所有的errChans關閉
    var wg sync.WaitGroup
    wg.Add(len(errChans))
    go func() {
        // 當所有的errchan都關閉時,關閉mergedChan
        wg.Wait()
        close(mergedChan)
    }()

    for i := range errChans {
        go func(errChan <-chan error) {
            // 等待每個errChan關閉
            for err := range errChan {
                if err != nil {
                    // 將每個errChan內容發送到mergedChan
                    mergedChan <- err
                }
            }
            //通知WaitGroup其中一個errChans關閉
            wg.Done()
        }(errChans[i])
    }

    return mergedChan
}

總結

在 Go 中有許多方法來實現併發。根據作者經驗,這是在構建 API 時實現併發的一種清晰而有效的方法,該 API 可以保持代碼的整潔性並最小化內存管理錯誤。

希望這對 Golang 中的併發提供一個實用的說明。也希望在把這些信息拼湊在一起時,它能幫你節省一些時間和煩惱。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xIWP5MHnIM6vmjVlpWTBJA