go实践十一 热重启服务

热重启

热重启(Zero Downtime),指新老进程无缝切换,在替换过程中可保持对 client 的服务。

原理

  • 父进程监听重启信号
  • 在收到重启信号后,父进程调用 fork ,同时传递 socket 描述符给子进程
  • 子进程接收并监听父进程传递的 socket 描述符
  • 在子进程启动成功之后,父进程停止接收新连接,同时等待旧连接处理完成(或超时)
  • 父进程退出,热重启完成

创建文件夹和文件  hotupdate/main.go 文件,main.go内容如下

package main

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

/************************** 测试热重启 ***************************/

var (
	server *http.Server
	listener net.Listener = nil

	graceful = flag.Bool("graceful",false,"listen on fd open 3 (internal use only)")
	message = flag.String("message","hello world","message to send")
)

func handler(w http.ResponseWriter,r *http.Request){
	time.Sleep(5 * time.Second)
	w.Write([]byte(*message))
}

func main(){
	var err error

	//解析参数
	flag.Parse()

	//开启服务器
	http.HandleFunc("/test",handler)
	server = &http.Server{Addr: ":3000"}

	//设置监听的对象(新建或已存在的socket描述符)
	if *graceful {
		//子进程监听父进程传递的 socket描述符
		log.Println("listening on the existing file descriptor 3")
		//子进程的 0 1 2 是预留给 标准输入 标准输出 错误输出
		//因此传递的socket 描述符应该放在子进程的 3
		f := os.NewFile(3,"")
		listener,err = net.FileListener(f)
	}else{
		//父进程监听新建的 socket 描述符
		log.Println("listening on a new file descriptor")
		listener,err = net.Listen("tcp",server.Addr)
	}
	if err != nil{
		log.Fatalf("listener error: %v\n",err)
	}
	go func(){
		err = server.Serve(listener)
		log.Printf("server.Serve err: %v\n",err)
	}()
	//监听信号
	handleSignal()
	log.Println("signal end")
}

//处理信号
func handleSignal(){
	//把信号 赋值给 通道
	ch := make(chan os.Signal,1)
	//监听信号
	signal.Notify(ch, syscall.SIGINT,syscall.SIGTERM,syscall.SIGUSR2)
	for{
		//通道 赋值给 sig
		sig := <-ch
		log.Printf("signal receive: %v \n",sig)
		ctx,_ := context.WithTimeout(context.Background(),20*time.Second)
		switch sig{
		case syscall.SIGINT,syscall.SIGTERM:  //终止进程执行
			log.Println("shutdown")
			signal.Stop(ch)
			server.Shutdown(ctx)
			log.Println("graceful shutdown")
			return
		case syscall.SIGUSR2:  //进程热重启
			log.Println("reload")
			err := reload()  //执行热重启
			if err != nil{
				log.Fatalf("graceful reload error: %v\n",err)
			}
			server.Shutdown(ctx)
			log.Println("graceful reload")
			return
		}
	}
}

//热重启
func reload() error{
	tl,ok := listener.(*net.TCPListener)
	if !ok {
		return errors.New("listener is not a tcp listener")
	}
	//获取socket描述符
	f,err  := tl.File()
	if err != nil{
		return err
	}
	//设置传递给子进程的参数(包含 socket描述符)
	args := []string{"-graceful"}
	cmd := exec.Command(os.Args[0],args...)
	cmd.Stdout = os.Stdout  //标准输出
	cmd.Stderr = os.Stderr  //错误输出
	cmd.ExtraFiles = []*os.File{f} //文件描述符
	//新建并执行子进程
	return cmd.Start()
}
/*
我们在父进程执行 cmd.ExtraFiles = []*os.File{f} 来传递 socket 描述符给子进程,子进程通过执行 f := os.NewFile(3, "") 来获取该描述符。值得注意的是,子进程的 0 、1 和 2 分别预留给标准输入、标准输出和错误输出,所以父进程传递的 socket 描述符在子进程的顺序是从 3 开始。
*/

切换到 hotupdate目录中,安装初始化模块

[root@localhost hotupdate]# go mod init hotupdate
go: creating new go.mod: module hotupdate

打包

[root@localhost hotupdate]# go build
[root@localhost hotupdate]# ll
total 7440
-rw-------. 1 root root      26 Jul 18 10:32 go.mod
-rwxr-xr-x. 1 root root 7606372 Jul 18 10:34 hotupdate
-rw-r--r--. 1 root root    3108 Jul 18 10:34 main.go

 

开启服务

[root@localhost hotupdate]# ./hotupdate -message echo
2019/07/18 10:35:49 listening on a new file descriptor

访问服务

[root@localhost ~]# curl http://127.0.0.1:3000/test
echo

打开一个新的终端,输出命令 kill -USR2 pid  , 重启服务

[root@localhost hotupdate]# netstat -tunlp | grep 3000
tcp6       0      0 :::3000                 :::*                    LISTEN      2198/./hotupdate    
[root@localhost hotupdate]# kill -USR2 2198

看到服务重启信息

2019/07/18 10:54:01 signal receive: user defined signal 2 
2019/07/18 10:54:01 reload
2019/07/18 10:54:01 graceful reload
2019/07/18 10:54:01 signal end

输入命令 kill -INT pid ,关闭服务

[root@localhost hotupdate]# netstat -tunlp | grep 3000
tcp6       0      0 :::3000                 :::*                    LISTEN      2639/./hotupdate    
[root@localhost hotupdate]# kill -INT 2639

看到服务关闭信息

2019/07/18 11:11:16 signal receive: interrupt 
2019/07/18 11:11:16 shutdown
2019/07/18 11:11:16 graceful shutdown
2019/07/18 11:11:16 signal end

参考:https://studygolang.com/articles/21952

参考:https://www.cnblogs.com/sunsky303/p/9778466.html

graceful:https://github.com/kuangchanglang/graceful

你可能感兴趣的:(go)