Golang HTTP 请求工作池

Kri*_*urg 3 api multithreading go goroutine

我正在尝试构建一个系统、工作池/作业队列,以http requests在每个 API 端点上尽可能多地处理。我查看了这个示例并让它工作得很好,只是我偶然发现了一个问题,我不明白如何将它扩展pool / jobqueue到不同的端点。

出于场景考虑,让我们绘制一个 Golang http 服务器,它在不同的端点和请求类型GETPOSTETC上有 100 万个请求/分钟。

我如何扩展这个概念?我是否应该为每个端点创建不同的工作池和作业。或者我可以创建不同的作业并将它们输入同一个队列并让相同的池处理这些作业吗?

我想保持简单,如果我创建一个新的 API 端点,我不必创建新的工作池,所以我可以只关注 api。但性能也非常重要。

我正在尝试构建的代码取自前面链接的示例,这里是其他人的 github 'gist' 使用此代码。

Pet*_*ter 9

预先做一件事:如果你正在运行一个 HTTP 服务器(无论如何是 Go 的标准服务器),你不能在不停止和重新启动服务器的情况下控制 goroutine 的数量。每个请求至少启动一个 goroutine,对此您无能为力。好消息是这通常不是问题,因为 goroutine 非常轻量级。但是,您希望控制执行繁重工作的 goroutine 的数量是完全合理的。

您可以将任何值放入通道中,包括函数。因此,如果目标是只需要在 http 处理程序中编写代码,那么让工作成为闭包——工作人员不知道(或关心)他们在做什么。

package main

import (
    "encoding/json"
    "io/ioutil"
    "net/http"
)

var largePool chan func()
var smallPool chan func()

func main() {
    // Start two different sized worker pools (e.g., for different workloads).
    // Cancelation and graceful shutdown omited for brevity.

    largePool = make(chan func(), 100)
    smallPool = make(chan func(), 10)

    for i := 0; i < 100; i++ {
            go func() {
                    for f := range largePool {
                            f()
                    }
            }()
    }

    for i := 0; i < 10; i++ {
            go func() {
                    for f := range smallPool {
                            f()
                    }
            }()
    }

    http.HandleFunc("/endpoint-1", handler1)
    http.HandleFunc("/endpoint-2", handler2) // naming things is hard, okay?

    http.ListenAndServe(":8080", nil)
}

func handler1(w http.ResponseWriter, r *http.Request) {
    // Imagine a JSON body containing a URL that we are expected to fetch.
    // Light work that doesn't consume many of *our* resources and can be done
    // in bulk, so we put in in the large pool.
    var job struct{ URL string }

    if err := json.NewDecoder(r.Body).Decode(&job); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
    }

    go func() {
            largePool <- func() {
                    http.Get(job.URL)
                    // Do something with the response
            }
    }()

    w.WriteHeader(http.StatusAccepted)
}

func handler2(w http.ResponseWriter, r *http.Request) {
    // The request body is an image that we want to do some fancy processing
    // on. That's hard work; we don't want to do too many of them at once, so
    // so we put those jobs in the small pool.

    b, err := ioutil.ReadAll(r.Body)
    if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
    }

    go func() {
            smallPool <- func() {
                    processImage(b)
            }
    }()
    w.WriteHeader(http.StatusAccepted)
}

func processImage(b []byte) {}
Run Code Online (Sandbox Code Playgroud)

这是一个非常简单的例子来说明问题。如何设置工作池并不重要。你只需要一个聪明的工作定义。在上面的例子中,它是一个闭包,但你也可以定义一个 Job 接口,例如。

type Job interface {
    Do()
}

var largePool chan Job
var smallPool chan Job
Run Code Online (Sandbox Code Playgroud)

现在,我不会将整个工作池方法称为“简单”。你说你的目标是限制 goroutines(正在工作)的数量。这根本不需要工人;它只需要一个限制器。这是与上面相同的示例,但使用通道作为信号量来限制并发。

package main

import (
    "encoding/json"
    "io/ioutil"
    "net/http"
)

var largePool chan struct{}
var smallPool chan struct{}

func main() {
    largePool = make(chan struct{}, 100)
    smallPool = make(chan struct{}, 10)

    http.HandleFunc("/endpoint-1", handler1)
    http.HandleFunc("/endpoint-2", handler2)

    http.ListenAndServe(":8080", nil)
}

func handler1(w http.ResponseWriter, r *http.Request) {
    var job struct{ URL string }

    if err := json.NewDecoder(r.Body).Decode(&job); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
    }

    go func() {
            // Block until there are fewer than cap(largePool) light-work
            // goroutines running.
            largePool <- struct{}{}
            defer func() { <-largePool }() // Let everyone that we are done

            http.Get(job.URL)
    }()

    w.WriteHeader(http.StatusAccepted)
}

func handler2(w http.ResponseWriter, r *http.Request) {
    b, err := ioutil.ReadAll(r.Body)
    if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
    }

    go func() {
            // Block until there are fewer than cap(smallPool) hard-work
            // goroutines running.
            smallPool <- struct{}{}
            defer func() { <-smallPool }() // Let everyone that we are done

            processImage(b)
    }()

    w.WriteHeader(http.StatusAccepted)
}

func processImage(b []byte) {}
Run Code Online (Sandbox Code Playgroud)