为了账号安全,请及时绑定邮箱和手机立即绑定

Go Web 服务器的进程管理

Go Web 服务器的进程管理

Go
幕布斯6054654 2022-01-17 10:06:26
我是一名新的 Go 程序员,来自 Web 应用程序和服务开发领域。抱歉,这是一个herp de-derp问题,但我在谷歌上搜索答案没有找到任何东西。此外,这是边缘服务器故障领域,但由于我对 API/编程接口更感兴趣,所以我在这里询问。我使用net/http包的内置 Web 服务器编写了一个小型 Go 程序。我正准备部署到生产环境,但我对模型 go Go 的 web-server 的过程以及我应该如何部署有点不清楚。具体来说——在我习惯的环境(PHP、Ruby、Python)中,我们的应用程序前面有一个 Web 服务器(Apache、Nginx 等),我们将这些 Web 服务器配置为使用一定数量的工作进程/线程,并配置每个线程应处理多少个单独的 HTTP(S) 连接。我无法找到有关 Go 的 Web 服务器如何处理此问题的信息,或有关如何为 Go Web 服务器扩展/计划扩展的实用信息。ie -- 如果我有一个简单的程序正在运行,准备好处理一个 HTTP 请求func main() {   http.HandleFunc("/", processRequest)   http.ListenAndServe(":8000", nil)    }HandleFunc一次尝试处理多少个连接?或者它会在连接打开时开始阻塞,并且只有在连接关闭后才提供下一个连接?还是我应该不担心这个并将所有内容都塞进常规程序中?但是如果我这样做了,我如何防止系统被太多的执行线程阻塞?我基本上是在尝试了解go web server的进程模式找到 go 的内置功能来调整它,和/或人们正在使用的任何标准包就像我说的,我很新,所以如果我完全错过了这个情节,请告诉我!
查看完整描述

2 回答

?
哆啦的时光机

TA贡献1779条经验 获得超6个赞

调整/配置 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()

}

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


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


Serve 接受 Listener l 上的传入连接,为每个. 服务 goroutine 读取请求,然后调用 srv.Handler 来回复它们。服务总是返回一个非零错误。


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


查看当前的实现(Go 1.6.2),也没有未记录的方法可以做到这一点。server.go,当前行 #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 的回答。要限制 Handler 级别,请继续阅读。


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


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


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


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


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


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


这是一个简单的示例,它使用包 ( )http.ListenAndServe()的默认多路复用器进行演示。它将并发请求限制为 2:httphttp.DefaultServeMux


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 处理、身份验证/授权、路由、多个服务器之间的负载平衡)。


查看完整回答
反对 回复 2022-01-17
?
慕神8447489

TA贡献1780条经验 获得超1个赞

ListenAndServe使用给定的地址和处理程序启动 HTTP 服务器。处理程序通常为 nil,表示使用DefaultServeMux. 处理HandleFunc并将处理程序添加到DefaultServeMux.


查看http.Server,很多字段都是可选的,并且可以使用默认值正常工作。


现在让我们看看http.ListenAndServe,一点也不难


func ListenAndServe(addr string, handler Handler) error {

    server := &Server{Addr: addr, Handler: handler}

    return server.ListenAndServe()

}

所以默认服务器的创建非常简单。


func (srv *Server) ListenAndServe() error {

    addr := srv.Addr

    if addr == "" {

        addr = ":http"

    }

    ln, err := net.Listen("tcp", addr)

    if err != nil {

        return err

    }

    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})

 }


func (srv *Server) Serve(l net.Listener) error {

    defer l.Close()

    if fn := testHookServerServe; fn != nil {

        fn(srv, l)

    }

    var tempDelay time.Duration // how long to sleep on accept failure

    if err := srv.setupHTTP2(); err != nil {

        return err

    }

    for {

        rw, e := l.Accept()

        if e != nil {

            if ne, ok := e.(net.Error); ok && ne.Temporary() {

                if tempDelay == 0 {

                    tempDelay = 5 * time.Millisecond

                } else {

                    tempDelay *= 2

                }

                if max := 1 * time.Second; tempDelay > max {

                    tempDelay = max

                }

                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)

                time.Sleep(tempDelay)

                continue

            }

            return e

        }

        tempDelay = 0

        c := srv.newConn(rw)

        c.setState(c.rwc, StateNew) // before Serve can return

        go c.serve()

    }

}

它侦听“addr”并接受每个连接,然后生成一个 goroutine 来独立处理每个连接。(HTTP/2.0 有点不同,但大体上是一样的)。

如果您想控制连接,您有 2 个选项:

  1. 使用服务器创建自定义服务器(它的 3 行代码)。ConnState从那里回调和控制客户端连接。(但无论如何它们都会被内核接受)

  2. 使用您自己的net.Listener(like LimitedListener) 实现创建自定义服务器并从那里控制连接,这样您将拥有对连接的最终控制权。

由于默认http.Server无法停止,因此第二种方式是优雅终止侦听器的唯一方法。您可以结合两种方法来实现不同的策略,这已经完成


查看完整回答
反对 回复 2022-01-17
  • 2 回答
  • 0 关注
  • 141 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信