[译]Golang中的优雅重启
原文 Graceful Restart in Golang 作者 grisha 声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加。但由于译者水平有限,所写文字或者代码可能会误导读者,如发现文章有问题,请尽快告知,不胜感激。 前言Update (Apr 2015): Florian von Bock已经根据本文实现了一个叫做endless的Go package 大家知道,当我们用Go写的web服务器需要修改配置或者需要升级代码的时候我们需要重启服务器,如果你(像我一样)已经将优雅的重启视为理所当然,因为使用Golang你需要自己动手来做这些操作,所以你可能会发现这个方式非常方便。 什么是优雅重启本文中的优雅重启表现为两点
1.进程在不关闭其所监听的端口的情况下重启
fork一个子进程有不止一种方法fork一个子进程,但在这种情况下推荐exec.Command,因为Cmd结构提供了一个字段
这句是说,索引位置为i的文件描述符传过去,最终会变为值为i+3的文件描述符。ie: 索引为0的文件描述符565,最终变为文件描述符3 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) } 上面的代码中, 需要注意的是:上面 你可能会想到可以使用命令行参数把该文件描述符的值传递给子进程,但相对来说,我使用的这种方式更为简单 最终, 子进程初始化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) } 通知父进程停止if gracefulChild { parent := syscall.Getppid() log.Printf("main: Killing parent pid: %v",parent) syscall.Kill(parent,syscall.SIGTERM) } server.Serve(l) 父进程停止接收请求并等待当前所处理的所有请求结束为了做到这一点我们需要使用sync.WaitGroup来保证对当前打开的连接的追踪,基本上就是:每当接收一个新的请求时,给wait group做原子性加法,当请求结束时给wait group做原子性减法。也就是说wait group存储了当前正在处理的请求的数量 var httpWg sync.WaitGroup 匆匆一瞥,我发现go中的http标准库并没有为Accept()和Close()提供钩子函数,但这就到了 下面是一个例子,该例子实现了每当执行Accept()的时候会原子性增加wait group。首先我们先继承 type gracefulListener struct { net.Listener stop chan error stopped bool } func (gl *gracefulListener) File() *os.File { tl := gl.Listener.(*net.TCPListener) fl,_ := tl.File() return fl } 接下来我们覆盖Accept方法(暂时先忽略 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 } 我们还需要一个构造函数以及一个Close方法,构造函数中另起一个goroutine关闭,为什么要另起一个goroutine关闭,请看 func newGracefulListener(l net.Listener) (gl *gracefulListener) { gl = &gracefulListener{Listener: l,stop: make(chan error)} // 这里为什么使用go 另起一个goroutine关闭请看文章末尾 go func() { _ = <-gl.stop gl.stopped = true gl.stop <- gl.Listener.Close() }() return } func (gl *gracefulListener) Close() error { if gl.stopped { return syscall.EINVAL } gl.stop <- nil return <-gl.stop } 我们的 接下来,我们还需要一个 type gracefulConn struct { net.Conn } func (w gracefulConn) Close() error { httpWg.Done() return w.Conn.Close() } 为了让我们上面所写的优雅启动方案生效,我们需要替换 netListener = newGracefulListener(l) server.Serve(netListener) 最后补充:我们还需要避免客户端长时间不关闭连接的情况,所以我们创建server的时候可以指定超时时间: server := &http.Server{ Addr: "0.0.0.0:8888",ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 16} 译者总结译者注:
func (gl *gracefulListener) Close() error { // some code gl.Listener.Close() } 作者回复道:
作者自己也较为疑惑,但表示像jokingus所提到的这种方式是行不通的 译者的个人理解:在绝大多数情况下,需要一个goroutine(可以称之为主goroutine)来创建socket,监听该socket,并accept直到有请求到达,当请求到来之后再另起goroutine进行处理。首先因为accept一般处于主goroutine中,且其是一个阻塞操作,如果我们想在accept执行后关闭socket一般来说有两个方法:
另外,也可以参考:Go中如何优雅地关闭net.Listener (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |