优雅的重启服务

知识点

  • 信号量的了解。
  • 应用热更新。

本文目标

ctrl+cctrl+c
ctrl+cGinHTTP

ctrl + c

SIGPIPE

在终端执行特定的组合键可以使系统发送特定的信号给此进程,完成一系列的动作

dump core
ctrl + cgin
kill -9 pidSIGKILL

信号

本段中反复出现信号是什么呢?

UnixUnixPOSIX

它是一种异步的通知机制,用来提醒进程一个事件(硬件异常、程序执行异常、外部发出信号)已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程。此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数

所有信号

  1. $ kill -l
  2. 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
  3. 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
  4. 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
  5. 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
  6. 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
  7. 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
  8. 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
  9. 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
  10. 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
  11. 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
  12. 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
  13. 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
  14. 63) SIGRTMAX-1 64) SIGRTMAX

怎样算优雅

目的

  • 不关闭现有连接(正在运行中的程序)
  • 新的进程启动并替代旧进程
  • 新的进程接管新的连接
  • 连接要随时响应用户的请求,当用户仍在请求旧进程时要保持连接,新用户应请求新进程,不可以出现拒绝请求的情况

流程

1、替换可执行文件或修改配置文件

SIGHUP

3、拒绝新连接请求旧进程,但要保证已有连接正常

4、启动新的子进程

Accet

6、系统将新的请求转交新的子进程

7、旧进程处理完所有旧连接后正常结束

实现优雅重启

endless

Zero downtime restarts for golang HTTP and HTTPS servers. (for golang 1.3+)

Golang HTTP/HTTPS
endless server
forkhammerTime
endless

安装

  1. go get -u github.com/fvbock/endless

编写

main.go
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "syscall"
  6. "github.com/fvbock/endless"
  7. "gin-blog/routers"
  8. "gin-blog/pkg/setting"
  9. )
  10. func main() {
  11. endless.DefaultReadTimeOut = setting.ReadTimeout
  12. endless.DefaultWriteTimeOut = setting.WriteTimeout
  13. endless.DefaultMaxHeaderBytes = 1 << 20
  14. endPoint := fmt.Sprintf(":%d", setting.HTTPPort)
  15. server := endless.NewServer(endPoint, routers.InitRouter())
  16. server.BeforeBegin = func(add string) {
  17. log.Printf("Actual pid is %d", syscall.Getpid())
  18. }
  19. err := server.ListenAndServe()
  20. if err != nil {
  21. log.Printf("Server err: %v", err)
  22. }
  23. }
endless.NewServerendlessServerBeforeBeginpidListenAndServe

验证

编译
  1. $ go build main.go
执行
  1. $ ./main
  2. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
  3. ...
  4. Actual pid is 48601
pidkill -1 48601
  1. [root@localhost go-gin-example]# ./main
  2. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
  3. - using env: export GIN_MODE=release
  4. - using code: gin.SetMode(gin.ReleaseMode)
  5. [GIN-debug] GET /auth --> ...
  6. [GIN-debug] GET /api/v1/tags --> ...
  7. ...
  8. Actual pid is 48601
  9. ...
  10. Actual pid is 48755
  11. 48601 Received SIGTERM.
  12. 48601 [::]:8000 Listener closed.
  13. 48601 Waiting for connections to finish...
  14. 48601 Serve() returning...
  15. Server err: accept tcp [::]:8000: use of closed network connection
forkpid48755
  1. 48601 Received SIGTERM.
  2. 48601 [::]:8000 Listener closed.
  3. 48601 Waiting for connections to finish...
  4. 48601 Serve() returning...
  5. Server err: accept tcp [::]:8000: use of closed network connection
pidSIGTERM
唤醒
postman
  1. Actual pid is 48755
  2. 48601 Received SIGTERM.
  3. 48601 [::]:8000 Listener closed.
  4. 48601 Waiting for connections to finish...
  5. 48601 Serve() returning...
  6. Server err: accept tcp [::]:8000: use of closed network connection
  7. $ [GIN] 2018/03/15 - 13:00:16 | 200 | 188.096µs | 192.168.111.1 | GET /api/v1/tags...

这就完成了一次正向的流转了

你想想,每次更新发布、或者修改配置文件等,只需要给该进程发送SIGTERM 信号,而不需要强制结束应用,是多么便捷又安全的事!

问题

endless

http.Server - Shutdown()

Golang >= 1.8http.Server
  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "context"
  6. "log"
  7. "os"
  8. "os/signal"
  9. "time"
  10. "gin-blog/routers"
  11. "gin-blog/pkg/setting"
  12. )
  13. func main() {
  14. router := routers.InitRouter()
  15. s := &http.Server{
  16. Addr: fmt.Sprintf(":%d", setting.HTTPPort),
  17. Handler: router,
  18. ReadTimeout: setting.ReadTimeout,
  19. WriteTimeout: setting.WriteTimeout,
  20. MaxHeaderBytes: 1 << 20,
  21. }
  22. go func() {
  23. if err := s.ListenAndServe(); err != nil {
  24. log.Printf("Listen: %s\n", err)
  25. }
  26. }()
  27. quit := make(chan os.Signal)
  28. signal.Notify(quit, os.Interrupt)
  29. <- quit
  30. log.Println("Shutdown Server ...")
  31. ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
  32. defer cancel()
  33. if err := s.Shutdown(ctx); err != nil {
  34. log.Fatal("Server Shutdown:", err)
  35. }
  36. log.Println("Server exiting")
  37. }

小结

GolangHTTP

参考

本系列示例代码

拓展阅读

关于

修改记录

  • 第一版:2018 年 02 月 16 日发布文章
  • 第二版:2019 年 10 月 01 日修改文章

如果有任何疑问或错误,欢迎在 issues 进行提问或给予修正意见,如果喜欢或对你有所帮助,欢迎 Star,对作者是一种鼓励和推进。

我的公众号

image