http - Go Web 服务器的进程管理

标签 http go scale net-http

我是一名新的 Go 程序员,来自 Web 应用程序和服务开发领域。抱歉,这是一个 Herp de-derp 问题,但我在谷歌上搜索答案并没有找到任何东西。此外,这是边缘服务器故障领域,但由于我对 API/编程接口(interface)更感兴趣,所以我在这里问。

我已经使用 net/http 编写了一个小的 go 程序包的内置 Web 服务器。我正准备部署到生产环境,但我对 Go 模型的网络服务器的过程以及我应该如何部署有点不清楚。

具体来说——在我习惯的环境(PHP、Ruby、Python)中,我们有一个 Web 服务器(Apache、Nginx 等)位于我们的应用程序前面,我们将这些 Web 服务器配置为使用一定数量的工作进程/线程,并配置每个线程应该处理多少个单独的 HTTP(S) 连接。

我无法找到有关 Go 的 Web 服务器如何处理此问题的信息,或有关如何为 Go Web 服务器扩展/计划扩展的实用信息。

即——如果我有一个简单的程序正在运行,准备好处理一个 HTTP 请求

func main() {
   http.HandleFunc("/", processRequest)
   http.ListenAndServe(":8000", nil)    
}

有多少连接HandleFunc尝试一下处理?或者它会在连接打开时开始阻塞,并且只有在连接关闭后才为下一个连接提供服务?

或者我应该不担心这个并将所有东西都塞进go例程中?但是,如果我这样做了,我该如何防止系统被过多的执行线程所拖累?

我基本上试图
  • 了解go web server的进程模式
  • 找到 go 的内置功能来调整它,和/或人们正在使用的任何标准包

  • 就像我说的,我很新,所以如果我完全错过了这个情节,请告诉我!

    最佳答案

    调整/配置 HTTP 服务器

    实现HTTP服务器的类型是 http.Server .如果您不创建 http.Server你自己因为你调用 http.ListenAndServe() 函数,创建一个 http.Server在引擎盖下为您:

    func ListenAndServe(addr string, handler Handler) error {
        server := &Server{Addr: addr, Handler: handler}
        return server.ListenAndServe()
    }
    

    所以如果你想调整/自定义 HTTP 服务器,那么你自己创建一个并调用它的 Server.ListenAndServe() 自己方法。 http.Server是一个结构,它的零值是一个有效的配置。查看它的文档它有哪些字段以及您可以调整/配置的内容。

    HTTP 服务器的“进程管理”记录在 Server.Serve() :

    Serve accepts incoming connections on the Listener l, creating a new service goroutine for each. The service goroutines read requests and then call srv.Handler to reply to them. Serve always returns a non-nil error.



    因此,每个传入的 HTTP 请求都在其新的 goroutine 中处理,这意味着它们是并发服务的。不幸的是,API 没有记录任何方式来加入和改变它的工作方式。

    查看当前的实现(Go 1.6.2),也没有未公开的方法可以做到这一点。 server.go , currently line #2107-2139 :
    2107    func (srv *Server) Serve(l net.Listener) error {
    2108        defer l.Close()
    2109        if fn := testHookServerServe; fn != nil {
    2110            fn(srv, l)
    2111        }
    2112        var tempDelay time.Duration // how long to sleep on accept failure
    2113        if err := srv.setupHTTP2(); err != nil {
    2114            return err
    2115        }
    2116        for {
    2117            rw, e := l.Accept()
    2118            if e != nil {
    2119                if ne, ok := e.(net.Error); ok && ne.Temporary() {
    2120                    if tempDelay == 0 {
    2121                        tempDelay = 5 * time.Millisecond
    2122                    } else {
    2123                        tempDelay *= 2
    2124                    }
    2125                    if max := 1 * time.Second; tempDelay > max {
    2126                        tempDelay = max
    2127                    }
    2128                    srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
    2129                    time.Sleep(tempDelay)
    2130                    continue
    2131                }
    2132                return e
    2133            }
    2134            tempDelay = 0
    2135            c := srv.newConn(rw)
    2136            c.setState(c.rwc, StateNew) // before Serve can return
    2137            go c.serve()
    2138        }
    2139    }
    

    正如您在第 2137 行中看到的那样,连接是在新的 goroutine 上无条件提供的,因此您对此无能为力。

    限制“ worker ”goroutines

    如果你想限制服务请求的 goroutines 的数量,你仍然可以这样做。

    您可以在多个级别上限制它们。有关听众级别的限制,请参阅 Darigaaz 的回答。要限制处理程序级别,请继续阅读。

    例如,您可以在每个 http.Handler 中插入一个代码。或处理程序函数 ( http.HandlerFunc ) 仅在服务 goroutine 的并发请求数量小于指定限制时才继续。

    这种限制同步代码有多种结构。一个例子可能是:创建一个缓冲 channel ,容量是你想要的限制。每个处理程序应首先在此 channel 上发送一个值,然后再进行工作。当处理程序返回时,它必须从 channel 接收一个值:因此最好在延迟函数中完成(不要忘记“清理”自身)。

    如果缓冲区已满,则尝试在 channel 上发送的新请求将被阻塞:WAITING请求完成其工作。

    请注意,您不必将此限制代码注入(inject)所有处理程序,您可以使用“中间件”模式,一种包装处理程序的新处理程序类型,执行此限制同步工作,并在中间调用包装的处理程序其中。

    在处理程序中进行限制(相对于在监听器中进行限制)的优点在于,在处理程序中我们知道处理程序做了什么,因此我们可以进行选择性限制(例如,我们可以选择限制某些请求,例如数据库操作,而不是限制其他人,如提供静态资源)或者我们可以根据我们的需要任意创建多个不同的限制组(例如,将并发数据库请求限制为 10 个最大值,将静态请求限制为 100 个最大值,将繁重的计算请求限制为 3 个最大值)等。我们也可以轻松实现登录/付费用户无限制(或上限)以及匿名/非付费用户下限等限制。

    另请注意,您甚至可以在一个地方进行速率限制,而无需使用中间件。创建一个“主处理程序”,并将其传递给 http.ListenAndServe() (或 Server.ListenAndServe())。在这个主处理程序中进行速率限制(例如使用上面提到的缓冲 channel ),然后简单地将调用转发到 http.ServeMux 你正在使用。

    这是一个简单的例子,它使用 http.ListenAndServe()http 的默认多路复用器用于演示的包 ( http.DefaultServeMux )。它将并发请求限制为 2 个:
    func fooHandler(w http.ResponseWriter, r *http.Request) {
        log.Println("Foo called...")
        time.Sleep(3 * time.Second)
        w.Write([]byte("I'm Foo"))
        log.Println("Foo ended.")
    }
    
    func barHandler(w http.ResponseWriter, r *http.Request) {
        log.Println("Bar called...")
        time.Sleep(3 * time.Second)
        w.Write([]byte("I'm Bar"))
        log.Println("Bar ended.")
    }
    
    var ch = make(chan struct{}, 2) // 2 concurrent requests
    
    func mainHandler(w http.ResponseWriter, r *http.Request) {
        ch <- struct{}{}
        defer func() {
            <-ch
        }()
    
        http.DefaultServeMux.ServeHTTP(w, r)
    }
    
    func main() {
        http.HandleFunc("/foo", fooHandler)
        http.HandleFunc("/bar", barHandler)
    
        panic(http.ListenAndServe(":8080", http.HandlerFunc(mainHandler)))
    }
    

    部署

    用 Go 编写的 Web 应用程序不需要外部服务器来控制进程,因为 Go 网络服务器本身并发处理请求。

    因此,您可以按原样启动用 Go 编写的网络服务器:Go 网络服务器已做好生产准备。

    如果您愿意,您当然可以使用其他服务器来执行其他任务(例如 HTTPS 处理、身份验证/授权、路由、多个服务器之间的负载平衡)。

    关于http - Go Web 服务器的进程管理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55934392/

    相关文章:

    iphone - UIButton 触摸时缩放?

    amazon-web-services - 从响应中删除 AWS/EC2 状态文本

    具有多个范围的 http 请求返回 HTTP/1.1 200 OK

    json - 如何解码转义的 JSON 字符串

    json - Go 处理 JSON 响应和请求

    群体尺度算法

    java - 带有 Signpost 和 Apache Commons HTTP 的 OAuth

    android - 如何在android中连接服务器数据库而不会丢失/失败?

    user-interface - 如何在 Go walk 中设置窗口位置并使其不可调整大小

    css - 图像缩放和动画改进