iris 服务器热重启(Go 语言)

2019-02-28 19:31 #旧文章

提示

我使用的服务器框架是 iris,其他服务器框架可以按照原理类推。

废话少说,直接进入正题。Go 是编译型语言,所以用一个主程序来热加载服务器的想法基本不可行。

热重启标志

想要热重启,我们先得让程序知道自己是人工启动的还是因热重启被启动的。在程序入口中加入一个 flag

isReload := flag.Bool("r", false, "Internal use, please don't use option")

然后传递给启动服务器的函数

func startServer(isReload bool) {
app := iris.New()
// 确定监听方式
var listener net.Listener
var err error
if !isReload { // 正常启动
listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", config.Settings.DomainName, config.Settings.ServerPort))
} else { // 热重启
mylog.Info("Reloading server", "[APP]")
file := os.NewFile(3, "") // 获取重启所需的套接字文件
listener, err = net.FileListener(file)
}
if err != nil {
mylog.Error(err.Error(), "[APP]")
return
}
// 注册热重启钩子
go regSignal(app, &listener)
_ = app.Run(
iris.Listener(listener), // 使用上面得到的Listener
)
}

其中 os.NewFile(3, "")中的 3 是下文启动新进程时赋予的文件编号

热重启信号

在服务器上,我们可以通过发送信号给进程来实现命令下达,我们只需要在程序中监听 SIGUSR1SIGUSR2 就能实现热重启命令的下达。

func regSignal(app *iris.Application, listener *net.Listener) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGUSR2) // 监听SIGUSR2信号
select {
case <-ch:
// 收到信号,开始热重启
mylog.Info("SIGUSR2 received, zdt reloading server.", "[APP]")
l, ok := (*listener).(*net.TCPListener)
if !ok {
mylog.Error("Listener isn't TCPListener.", "[APP]")
return
}
f, err := l.File()
if err != nil {
mylog.Error(err.Error(), "[APP]")
return
}
// 生成启动参数
size := len(os.Args)
args := make([]string, size)
needReloadFlag := true
for i := 1; i < size; i++ {
if os.Args[i] == "-r" {
needReloadFlag = false
}
args[i-1] = os.Args[i]
}
if needReloadFlag {
args[size-1] = "-r"
}
// 启动新进程
cmd := exec.Command(os.Args[0], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = []*os.File{f} // 把监听套接字传给新进程
err = cmd.Start()
// 结束当前服务
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = app.Shutdown(ctx)
return
}
}

现在就可以通过 kill 命令传递信号来实现热重启了

Terminal window
kill -s SIGUSR2 $PID

参考资料