乐趣区

关于golang:Go-平滑重启优雅重启

问题背景

生产环境重要且简单,许多的操作须要在任何场景都要保障失常运行。

如果咱们对线上服务进行更新的步骤如下:

  1. kill -9服务
  2. 再启动服务

那么将不可避免的呈现以下两个问题:

  • 未解决完的申请,被迫中断,数据一致性被毁坏
  • 新服务启动期间,申请无奈进来,导致一段时间的服务不可用景象

个别有三种计划解决以上问题:

  1. 生产环境会通过四层(lb)-> 七层(gateway)-> 服务,那么能够通过流量调度的形式实现平滑重启
  2. k8s 治理
  3. 程序本身实现平滑重启(本章介绍)

什么事平滑重启

过程在不敞开其所监听端口的状况下进行重启,并且重启的整个过程保障所有申请都能被 正确 解决。

次要步骤:

  1. 原过程(父过程)先 fork 一个子过程,同时让 fork 进去的子过程继承父过程所监听的socket
  2. 子过程实现初始化后,开始接管 socket 的申请。
  3. 父过程进行接管新的申请,并将当下的申请解决完,期待连贯闲暇后,平滑退出。

信号(Signal)

服务的平滑重启,次要依赖过程接管的信号(实现过程间通信),这里简略的介绍 Golang 中信号的解决:

发送信号

  • kill命令 容许用户发送一个特定的信号给过程
  • raise库函数 能够发送特定的信号给以后过程

在 Linux 下运行 man kill 能够查看此命令的介绍和用法。

kill — terminate or signal a process
The kill utility sends a signal to the processes specified by the pid operands.
Only the super-user may send signals to other users’ processes.

罕用信号类型

信号的默认行为:

  • term:信号终止过程
  • core:产生外围转储文件并退出
  • ignore:疏忽信号
  • stop:信号进行过程
  • cont:信号复原一个已进行的过程
信号 默认动作 阐明
SIGHUP 1 Term HUP (hang up):终端管制过程完结(终端连贯断开)
SIGINT 2 Term INT (interrupt):用户发送 INTR 字符 (Ctrl+C) 触发(强制过程完结)
SIGQUIT 3 Core QUIT (quit):用户发送 QUIT 字符 (Ctrl+/) 触发(过程完结)
SIGKILL 9 Term KILL (non-catchable, non-ignorable kill):无条件完结程序(不能被捕捉、阻塞或疏忽)
SIGUSR1 30,10,16 Term 用户自定义信号 1
SIGUSR2 31,12,17 Term 用户自定义信号 2
SIGKILL 15 KILL (non-catchable, non-ignorable kill) TERM (software termination signal):程序终止信号

信号接管测试

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
)

func main() {sigs := make(chan os.Signal)
    signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)

    // 监听所有信号
    log.Println("listen sig")
    signal.Notify(sigs)

    // 打印过程 id
    log.Println("PID:", os.Getppid())


    s := <-sigs
    log.Println("退出信号", s)
}
go run main.go
## --> listen sig
## --> PID: 4604

kill -s HUP 4604
# --> Hangup: 1

实现案例

demo:

func main() {sigs := make(chan os.Signal)
   signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)

   // 监听所有信号
   log.Println("listen sig")
   signal.Notify(sigs)


   // 打印过程 id
   log.Println("PID:", os.Getppid())
   go func() {
      for s := range sigs {
         switch s {
         case syscall.SIGHUP:
            log.Println("startNewProcess...")
            startNewProcess()
            log.Println("shutdownParentProcess...")
            shutdownParentProcess()
         case syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
            log.PrintLn("Program Exit...", s)
         case syscall.SIGUSR1:
            log.Println("usr1 signal", s)
         case syscall.SIGUSR2:
            log.Println("usr2 signal", s)
         default:
            log.Println("other signal", s)
         }
      }
   }()

   <-sigs
}

举荐组件

Facebookarchive/grace

shutdown 优雅退出

go 1.8.x 后,golang 在 http 里退出了 shutdown 办法,用来管制优雅退出。

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {s := http.NewServeMux()
    s.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {time.Sleep(3 * time.Second)
        log.Println(w, "Hello world!")
    })
    server := &http.Server{
        Addr:    ":8090",
        Handler: s,
    }
    go server.ListenAndServe()

    listenSignal(context.Background(), server)
}

func listenSignal(ctx context.Context, httpSrv *http.Server) {sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)

    select {
    case <-sigs:
        log.Println("notify sigs")
        httpSrv.Shutdown(ctx)
        log.Println("http shutdown")
    }
}

小结

在平时的生产环境中,优雅的重启是一个不可短少的环节,无论是在 go 过程层间,或者下层的服务流量调度层面,都有许多的计划,抉择最适宜团队,保障我的项目稳固才是最重要的。

参考文章

https://github.com/facebookar…

https://mp.weixin.qq.com/s/UV…

退出移动版