如果您有Golang HTTP服务,可能需要重新启动它以升级二进制文件或更改某些配置。如果你(像我一样)因为网络服务器处理它而优雅地重新启动是理所当然的,你可能会发现这个配方非常方便,因为使用Golang你需要自己动手。
实际上这里有两个问题需要解决。首先是正常重启的UNIX方面,即进程可以在不关闭侦听套接字的情况下自行重启的机制。第二个问题是确保所有正在进行的请求正确完成或超时。
重新启动而不关闭套接字
- fork一个继承侦听套接字的新进程。
- 子进程初始化并开始接受套接字上的连接。
- 紧接着,孩子向父母发送信号,导致父母停止接受连接并终止。
分叉一个新的过程
ExtraFiles
这是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
file := netListener.File() // this returns a Dup() path := "/path/to/executable" args := []string{ "-graceful"} cmd := exec.Command(path, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.ExtraFiles = []*os.File{file} err := cmd.Start() if err != nil { log.Fatalf("gracefulRestart: Failed to launch, error: %v", err) }
netListenerpath
netListener.File()FD_CLOEXEC
ExtraFiles
args-graceful
子初始化
这是程序启动序列的一部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
server := &http.Server{Addr: "0.0.0.0:8888"} var gracefulChild bool var l net.Listever var err error flag.BoolVar(&gracefulChild, "graceful", false, "listen on fd open 3 (internal use only)") if gracefulChild { log.Print("main: Listening to existing file descriptor 3.") f := os.NewFile(3, "") l, err = net.FileListener(f) } else { log.Print("main: Listening on a new file descriptor.") l, err = net.Listen("tcp", server.Addr) }
信号父母停止
此时我们已准备好接受请求,但就在我们这样做之前,我们需要告诉我们的父母停止接受请求并退出,这可能是这样的:
1 2 3 4 5 6 7
if gracefulChild {
parent := syscall.Getppid() log.Printf("main: Killing parent pid: %v", parent) syscall.Kill(parent, syscall.SIGTERM) } server.Serve(l)
正在进行的请求完成/超时
为此,我们需要使用sync.WaitGroup跟踪打开的连接 。我们需要在每个接受的连接上递增等待组,并在每个连接关闭时递减它。
1
var httpWg sync.WaitGroup
乍一看,Golang标准的http包不提供任何钩子来对Accept()或Close()采取行动,但这就是界面魔法拯救的地方。(非常感谢Jeff R. Allen 对这篇文章的评价)。
net.Listenerstopstopped
1 2 3 4 5
type gracefulListener struct { net.Listener stop chan error stopped bool }
gracefulConn
1 2 3 4 5 6 7 8 9 10 11
func (gl *gracefulListener) Accept() (c net.Conn, err error) { c, err = gl.Listener.Accept() if err != nil { return } c = gracefulConn{Conn: c} httpWg.Add(1) return }
我们还需要一个“构造函数”:
1 2 3 4 5 6 7 8 9
func newGracefulListener(l net.Listener) (gl *gracefulListener) { gl = &gracefulListener{Listener: l, stop: make(chan error)} go func() { _ = <-gl.stop gl.stopped = true gl.stop <- gl.Listener.Close() }() return }
Accept()gl.Listener.Accept()
Close()nil
1 2 3 4 5 6 7
func (gl *gracefulListener) Close() error { if gl.stopped { return syscall.EINVAL } gl.stop <- nil return <-gl.stop }
net.TCPListener
1 2 3 4 5
func (gl *gracefulListener) File() *os.File { tl := gl.Listener.(*net.TCPListener) fl, _ := tl.File() return fl }
net.ConnClose()
1 2 3 4 5 6 7 8
type gracefulConn struct { net.Conn } func (w gracefulConn) Close() error { httpWg.Done() return w.Conn.Close() }
server.Serve(l)
1 2
netListener = newGracefulListener(l) server.Serve(netListener)
还有一件事。您应该避免挂断客户端无意关闭的连接(或不是本周)。最好按如下方式创建服务器:
1 2 3 4 5
server := &http.Server{ Addr: "0.0.0.0:8888", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 16}