衔接上文,继续开发。这一章的主要目的是热更新服务。
实现目标
- 不关闭现有的服务连接
- 新进程启动替代旧进程
- 新进程负责接管新来的请求连接
- 连接要随时响应用户的请求,当用户仍在访问旧进程时要保持连接。
- 新用户应当请求新进程。
- 不可以出现拒绝请求的情况。
流程
- 替换可执行文件或修改配置文件
- 发送信号量 SIGHUP
- 拒绝新连接请求旧进程,但要保证已有连接正常
- 启动新的子进程
- 新的子进程开始Accept
- 系统将新的请求转交新的子进程
- 旧进程处理完所有旧连接后正常结束
解决方案
有两个解决方案:
- endless包
- http.Server的Shutdown方法(Golang >= 1.8)
endless热更新是采取创建子进程后将原进程退出的方式,这点不符合守护进程的要求。
所以这里我们采用第二种方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package main import ( "context" "fmt" "net/http" "os" "os/signal" "time" "ginBlog/pkg/logging" "ginBlog/pkg/setting" "ginBlog/routers" ) func main() { router := routers.InitRouter() s := &http.Server{ Addr: fmt.Sprintf(":%d", setting.GetServerHTTPPort()), Handler: router, ReadTimeout: setting.GetServerReadTimeOut(), WriteTimeout: setting.GetServerWriteTimeOut(), MaxHeaderBytes: 1 << 20, } go func() { if err := s.ListenAndServe(); err != nil { logging.Info("Listen: ", err) } }() quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) <-quit logging.Info("Shutdown Server ...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := s.Shutdown(ctx); err != nil { logging.Fatal("Server Shutdown:", err) } logging.Info("Server exiting") } |
http.Shutdown 它配合signal.Notify函数监听系统退出信号,完成优雅退出。
Shutdown(ctx) 函数会阻止新的连接进入,并等待活跃连接处理完成后再终止程序。
另外,他同时会监听Context事件,等待Context触发后也会结束进程。
Golang 在HTTP服务方面热更新有不少方案,我们应该依据实际场景选最合适的。
【Go】gin Blog项目(六) 热更新HTTP服务