在开发完成应用程序后,即可将其部署到测试、预发布或生产环境中,这时又涉及一个问题,即持续集成。简单来说,开发人员需要关注的是这个应用程序是不断地进行更新和发布的,也就是说,这个应用程序在发布时,很可能某客户正在使用这个应用。如果直接硬发布,就会造成客户的行为被中断。

遇到问题

为了避免这种情况的发生,我们希望在应用更新或发布时,现有正在处理既有连接的应用不要中断,要先处理完既有连接后再退出。而新发布的应用在部署上去后再开始接收新的请求并进行处理,这样即可避免原来正在处理的连接被中断的问题。

解决方法

想要解决这个问题,目前最经典的方案就是通过信号量的方式来解决。

信号定义

信号是UNIX、类UNIX,以及其他POSIX兼容的操作系统中进程间通信的一种有限制的方式。

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

支持的信号

我们可以通过 kill-l 查看系统中所支持的所有信号,如下:

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

实现

目的

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

流程

  • 替换可执行文件或修改配置文件
  • 发送信号量SIGHUP
  • 拒绝新连接请求旧进程,保证正在处理的连接正常
  • 启动新的子进程
  • 新的子进程开始Accept
  • 系统将新的请求转交新的子进程
  • 旧进程处理完所有旧连接后正常退出

实现

回到项目的main.go文件,对该项目进行改造,使其支持优雅重启和停止,修改代码如下:

  1. func main() {
  2. gin.SetMode(global.ServerSetting.RunMode)
  3. router := routers.NewRouter()
  4. //log.Fatal("开始启动")
  5. s := &http.Server{
  6. Addr: ":" + global.ServerSetting.HttpPort,
  7. Handler: router,
  8. TLSConfig: nil,
  9. ReadTimeout: global.ServerSetting.ReadTimeout,
  10. ReadHeaderTimeout: 0,
  11. WriteTimeout: global.ServerSetting.WriteTimeout,
  12. IdleTimeout: 0,
  13. MaxHeaderBytes: 1 << 20,
  14. TLSNextProto: nil,
  15. ConnState: nil,
  16. ErrorLog: nil,
  17. BaseContext: nil,
  18. ConnContext: nil,
  19. }
  20. go func() {
  21. err := s.ListenAndServe()
  22. if err !=nil && err !=http.ErrServerClosed {
  23. log.Fatalf("s.ListenAndServer err: %v",err)
  24. }
  25. }()
  26. // 等待中断信号
  27. quit := make(chan os.Signal)
  28. // 接收syscall.SIGINT和syscall.SIGTERM信号
  29. signal.Notify(quit,syscall.SIGINT,syscall.SIGTERM)
  30. <-quit
  31. log.Fatalf("Shuting down server ......")
  32. // 最大时间控制,用于通知该服务端它有5秒的时间来处理原请求
  33. ctx,cancel := context.WithTimeout(context.Background(),5*time.Second)
  34. defer cancel()
  35. if err := s.Shutdown(ctx);err !=nil{
  36. log.Fatalf("Server force to shutdown: %v",err)
  37. }
  38. log.Fatalf("Server existing ......")
  39. }