Golang的优雅重启

摘要:
您可能需要重新启动它来升级二进制文件或更改某些配置。在不关闭套接字叉的情况下重新启动继承侦听套接字的新进程。此成员指定要由新进程继承的打开文件(stdin/err/out除外)=netListener.File()//thisrurnsaDup()路径:=[]string{“-magnment”}cmd:

更新(2015年4月):Florian von Bock已将本文中描述的内容转换为一个名为endless的优秀Go包 。

如果您有Golang HTTP服务,可能需要重新启动它以升级二进制文件或更改某些配置。如果你(像我一样)因为网络服务器处理它而优雅地重新启动是理所当然的,你可能会发现这个配方非常方便,因为使用Golang你需要自己动手。

实际上这里有两个问题需要解决。首先是正常重启的UNIX方面,即进程可以在不关闭侦听套接字的情况下自行重启的机制。第二个问题是确保所有正在进行的请求正确完成或超时。

重新启动而不关闭套接字

  • fork一个继承侦听套接字的新进程。
  • 子进程初始化并开始接受套接字上的连接。
  • 紧接着,孩子向父母发送信号,导致父母停止接受连接并终止。

分叉一个新的过程

使用Golang lib分支进程的方法不止一种,但对于这种特殊情况, exec.Command可行的方法。这是因为此函数返回Cmd结构具有此ExtraFiles成员,成员指定要由新进程继承的打开文件(除了stdin / err / out)。

这是这样的:

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)
}

在上面的代码中netListener是一个指向net.Listener的指针, 用于监听HTTP请求。path如果要升级变量应该包含新可执行文件的路径(可能与当前运行的路径相同)。

上面代码中的一个重点是netListener.File() 返回 文件描述符的 dup(2)重复的文件描述符不会设置FD_CLOEXEC标志,这会导致文件在子节点中关闭(不是我们想要的)。

您可能会遇到通过命令行参数将继承的文件描述符编号传递给子项的示例,但ExtraFiles实现的方式 使其不必要。文档指出“如果非零,则条目i变为文件描述符3 + i。”这意味着在上面的代码片段中,子代中的继承文件描述符将始终为3,因此不需要明确地传递它。

最后,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 对这篇文章的评价)。

下面是一个侦听器示例,它在每个Accept()上递增一个等待组。首先,我们“子类” net.Listener(你会明白我们为什么需要stopstopped以下):

1 
2 
3 
4 
5
type gracefulListener struct {
    net.Listener
    stop    chan error
    stopped bool
}

接下来,我们“覆盖”Accept方法。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
}

上面的函数启动goroutine的原因是因为它不能在我们Accept()上面完成,因为它会阻塞 gl.Listener.Accept()goroutine将通过关闭文件描述符来解锁它。

我们的Close()方法只是发送一个nil停止通道,以便上面的goroutine完成其余的工作。

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.Conn减少等待组的变体 Close()

1 
2 
3 
4 
5 
6 
7 
8
type gracefulConn struct {
    net.Conn
}

func (w gracefulConn) Close() error {
    httpWg.Done()
    return w.Conn.Close()
}

要开始使用上面优雅的Listener版本,我们只需要将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}

免责声明:文章转载自《Golang的优雅重启》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇用javascript的classList代替jquery的class操作并发管理下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

css hover伪类选择器与JQuery hover()方法

css hover伪类选择器 它属于anchor伪类 在支持 CSS 的浏览器中,<a>标签链接的不同状态都可以以不同的方式显示,常常用来改链接的颜色效果 实例 a:link{color:#FF0000;}/* 未访问的链接 */ a:visited{color:#00FF00;}/* 已访问的链接 */ a:hover{color:#FF...

Swing之JList的使用

大家在使用Swing的JList的时候一定会关心几个问题常见问题: 1:怎么把JList弄到界面上去 2:怎么往JList上放数据 3:怎么监听JList里的项的双击事件,然后取出数据 4:数据太多,怎么添加滚动条。 这是几个常用功能,今天在这里做个总结。 ==================================================...

Filebeat命令参考

 Filebeat命令参考: Filebeat提供了一个命令行界面,用于启动Filebeat并执行常见任务,例如测试配置文件和加载仪表板。命令行还支持用于控制全局行为的全局标志。 命令: export 将配置或索引模板导出到stdout。 help 显示任何命令的帮助。 keystore 管...

windows开机显示无法登陆你的账号,你的所有操作将不会被保存!变相系统还原?

■  如果你的电脑开机的时候出现这种提示 ■  别急,这可能只是一些爱倒腾的同学不小心删除了系统账户文件导致的,我们要吧这些系统账户文件再找回来 ■  计算机——》属性——》高级系统设置——》用户配置文件设置——》默认配置文件更改——》目标目录C:UsersAdministrator 如下图,做完更改重启即可。 ■  如果说因为丢失了系统用户管理...

Android手机里的垃圾文件和文件夹清理

SD卡中各个文件夹功能的最详尽分析SD卡用久了会有好多文件夹出现,大家看看都是干什么用~1、.android_secure  是官方app2sd的产物,删了之后装到sd卡中的软件就无法使用了。2、.Bluetooth  顾名思义,用蓝牙之后就会有这个。3、.mobo  顾名思义,Moboplayer的缓存文件。4、.QQ   顾名思义,QQ的缓存文件。5、...

linux新装系统初始化

linux新装系统初始化 系统新装的时候建议进行的初始化,以及以后每个主机安装系统的模版设置,比如最小化安装及启动必要的服务,统一的sudo配置,比如利用/etc/rc.local当作系统开机启动的档案管理等等。 linux新装系统初始化 1. 登录安全 2. 更改主机名: 3. 安装时最小化安装,及启动必要的服务 4. 最好把/etc/rc.lo...