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 处理、身份验证/授权、路由、多个服务器之间的负载平衡)。
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 个选项:
使用服务器创建自定义服务器(它的 3 行代码)。ConnState从那里回调和控制客户端连接。(但无论如何它们都会被内核接受)
使用您自己的
net.Listener
(likeLimitedListener
) 实现创建自定义服务器并从那里控制连接,这样您将拥有对连接的最终控制权。
由于默认http.Server
无法停止,因此第二种方式是优雅终止侦听器的唯一方法。您可以结合两种方法来实现不同的策略,这已经完成了。
- 2 回答
- 0 关注
- 141 浏览
添加回答
举报