go-SDK内提供了net模块,让我们可以方便的搭建http服务,我们知道服务一旦启动,调用协程阻塞就等待了,然后等待接受客户端发来的请求。那么当我们想要停止服务时,如何优雅的关闭已经启动的服务那?
二、优雅停服
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Println("path", r.URL.Path)
fmt.Fprintln(w, "hello world")
}
func main() {
//1. 创建http请求处理器,并注册path与处理器
handler := http.NewServeMux()
handler.HandleFunc("/hello", sayHello)
//2. 创建httpserver
server := &http.Server{Addr: ":9090", Handler: handler}
// 用于实现通知等待模型
ShutdownFlag := make(chan int)
//3. 异步协程,模拟优雅停服
go func() {
//3.1 休眠1分钟,让服务器有1分钟时间可以处理请求
time.Sleep(time.Minute)
fmt.Println("call shutdown")
//3.2如果1分钟还没关闭完毕,则报错
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
//3.3优雅关闭
err1 := server.Shutdown(ctx)
if err1 != nil {
fmt.Printfln(Err1)
}
//3.3通知main协程,关闭完毕,可以退出了
close(shutdownFlag)
}()
//4. 启动http服务
err := server.ListenAndServe()
if err != http.ErrServerClosed {
panic(err)
}
//5. 等待优雅关闭结束
<-shutdownFlag
fmt.Println("shutdown ok ")
}
- 代码1 注册path与处理器,代码2创建httpserver,在端口9090监听服务请求
- 代码4 启动http服务,然后main函数所在协程就会阻塞了。
- 代码3 异步协程模拟优雅停服。这里先休眠1分钟,是为了在1分钟让服务器可以正常接受请求处理。1分钟后,调用了server的shutdown方法,执行优雅停服,停服成功后,代码4会返回ErrServerClosed错误。
三、shutdown内部逻辑
- 首先关闭所有开启的监听器,然后关闭所有闲置连接,最后等待活跃的连接均闲置了才终止服务。
- 如果传入的 context 在服务完成终止前已超时了,那么 Shutdown 方法返回 context 的错误,否则返回任何由关闭服务监听器所引起的错误。
- 另外当 Shutdown 方法被调用时,Serve、ListenAndServe 及 ListenAndServeTLS 方法会立刻返回 ErrServerClosed 错误。所以需要确保 Shutdown 未返回时,main函数所在的协程不要退出。我们示例中代码代码3.3和代码5保证了这个
- 对 WebSocket 等的长连接,Shutdown 不会尝试关闭也不会等待这些连接关闭。若需要关闭,需调用者分开额外处理(诸如通知诸长连接或等待它们关闭,使用 RegisterOnShutdown 注册终止通知函数)。
- 一旦对 server 调用了 Shutdown,其就不可再使用了(会报 ErrServerClosed 错误)。
使用shutdown方法可以优雅停服,但是需要保证main函数所在协程不能在停服完毕前就退出了,因为go中main函数所在协程退出意味着整个进程就退出了,而这时优雅停服可能还没完成。