go程序不停机重启

让我们给http服务写一个版本更新接口,让它自动更新版本并重启服务吧。

初步例子

  注:为了精简,文中代码都去除了err处理  

main.go

var Version = "1.0"

/* 打印版本 */
func version(w http.ResponseWriter, r *http.Request) {
	msg := fmt.Sprintf("version %v\n", Version)
	w.Write([]byte(msg))
}

/* 版本升级 */
func upgrade(w http.ResponseWriter, r *http.Request) {
	// 1. 把新版本文件放置到服务主目录(简化)
	os.Remove("test_restart")
	os.Rename("new_test_restart", "test_restart")

	// 2. go的可执行文件加权限(省略)

	// 3. 重启服务
	cmd := exec.Command("/bin/bash", "-c", "./restart.sh")
	cmd.Output()

	w.Write([]byte("restart ok\n"))
}

func main() {
	// 记录pid
	f,_ := os.Create("s.pid")
	pid := os.Getpid()
	f.WriteString(fmt.Sprintf("%v", pid))
	fmt.Printf("System running:%v\n", pid)

	// 监听连接
	mux := http.NewServeMux()
	mux.HandleFunc("/version", version)
	mux.HandleFunc("/upgrade", upgrade)
	http.ListenAndServe("127.0.0.1:9527", mux)
}

restart.sh(重启脚本)

kill -9 $(cat s.pid)
nohup ./test_restart > nohup.log 2>&1 &

 测试

    1. 编译后开始运行

     

    2. 请求一下版本信息接口

   

    3. 将代码中Version改为“1.1”, 生成一个新文件“new_test_restart”

    4. 请求版本更新接口

   

   

发现问题

    重启脚本是stop后start的,stop时直接杀死了进程,程序直接中断了当前所有的连接,此时接口函数还未return,导致调用方接收不到响应。

使用Endless

    看来得不中断已有连接的情况下进行重启才行,不能简单的stop后start,得平滑重启。大致就是让父进程启动一个子进程去监听新的连接,自己不再监听新的连接,而是在处理完已有连接后终止,之后子进程独挑大梁。

    随后发现github上的endless挺满足需求,它是一个不停机重启的服务器实现,实现流程为:

  1. 监听 SIGHUP 信号
  2. 收到信号后 fork 子进程(使用相同的启动命令),将服务监听的 socket 文件描述符传递给子进程
  3. 子进程启动成功后开始监听新的连接,并发送 SIGTERM 信号给父进程
  4. 父进程收到 SIGTERM 信号后停止接收新的连接,等待旧连接处理完成后终止
  5. 父进程终止,重启完成

    关于 SIGHUP 信号,我们可以用“kill -1”命令发送给endless。

    使用endless改动很小,在main函数中只需要把 http.ListenAndServe 修改为 endless.ListenAndServe即可: 

main.go

func main() {
	// 记录pid(省略)

	// 监听连接
	mux := http.NewServeMux()
	mux.HandleFunc("/version", version)
	mux.HandleFunc("/upgrade", upgrade)
        // http.ListenAndServe("127.0.0.1:9527", mux)
	endless.ListenAndServe("127.0.0.1:9527", mux)
}

restart.sh

kill -1 $(cat s.pid)

再测试

   1. 编译后开始运行(带时间前缀的是endless打印的日志)

   

    2. 请求一下版本信息接口

   

    3. 将代码中Version改为“1.1”, 生成一个新文件“new_test_restart”

    4. 请求版本更新接口

   

   

        请求没有被中断,成功接收了响应。

        endless的日志比较清晰,105300的父进程接收 SIGHUP 后,fork了子进程105335,接收到子进程传递的 SIGHUP 后,等待已有连接处理完成后终止,完全符合上述介绍的流程。

    5. 验证版本

   

    至此不停机版本更新成功 ٩(◕‿◕。)۶

原文地址:https://www.cnblogs.com/MilletChili/p/15593756.html