调整/配置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接受监听器l上的传入连接,为每个连接创建一个新的服务goroutine。服务goroutine读取请求,然后调用srv.Handler进行回复。Serve始终返回非nil错误。
这意味着每个传入的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
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)
2137 go c.serve()
2138 }
2139 }
如您在第2137行所见,连接会在一个新的 goroutine 上被无条件地服务,因此你无法对其进行任何操作。
限定“worker” goroutines
如果您想要限制处理请求的goroutine数量,您仍然可以这样做。
您可以在多个级别上进行限制。要在监听器级别上进行限制,请参阅Darigaaz的答案。 要在处理程序级别上进行限制,请继续阅读。
例如,您可以将代码插入到每个http.Handler
或处理函数(http.HandlerFunc
),该代码仅在并发请求处理的goroutine数小于指定限制时才继续执行。
有许多构造用于这种限制同步代码。 一个例子是:创建具有所需限制容量的缓冲通道。每个处理程序应首先在该通道上发送值然后执行工作。当处理程序返回时,它必须从通道接收一个值:因此最好使用延迟函数(不要忘记“清理”自己)。
如果缓冲区已满,则试图在通道上发送请求的新请求将被阻塞:等待请求完成其工作。
请注意,您不必将此限制代码注入到所有处理程序中,您可以使用“中间件”模式,这是一种新的处理程序类型,它包裹了您的处理程序,执行此限制同步任务,并在其中间调用包装后的处理程序。
在处理程序中进行限制(而不是在监听器中进行限制)的优点是,在处理程序中我们知道处理程序在做什么,所以我们可以进行有选择性的限制(例如,我们可以选择限制某些请求,如数据库操作,而不是限制其他请求,如提供静态资源),或者我们可以根据需要创建多个不同的限制组(例如,最大限度地限制并发db请求为10,最大限制静态请求为100,最大限制重计算请求为3等等)。 我们还可以轻松地实现像登录/付费用户无限制(或高限制)和匿名/非付费用户低限制等限制。
还要注意,您甚至可以在单个位置进行速率限制,而无需使用中间件。创建一个“主处理程序”,并将其传递给http.ListenAndServe()
(或Server.ListenAndServe()
)。在此主处理程序中,执行速率限制(例如,使用上述缓冲通道),然后简单地将调用转发到您正在使用的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)
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 Web服务器本身可以并发处理请求。
因此,您可以按原样启动以Go编写的Web服务器:Go Web服务器已经准备就绪。
当然,如果您愿意,可以使用其他服务器执行其他任务(例如处理HTTPS、身份验证/授权、路由、在多个服务器之间进行负载平衡等)。
http.Server
(详见其文档)来调整/定制HTTP服务器。net/http
包文档中包含了一个示例。此外,每个传入的HTTP请求都会并发处理,通过为每个请求创建一个新的服务goroutine,如Server.Serve()
所述。 - icza