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

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

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

重新启动而不关闭套接字

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

分叉一个新的过程

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

这是这样的:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  1. file := netListener.File() // this returns a Dup()
  2. path := "/path/to/executable"
  3. args := []string{
  4. "-graceful"}
  5. cmd := exec.Command(path, args...)
  6. cmd.Stdout = os.Stdout
  7. cmd.Stderr = os.Stderr
  8. cmd.ExtraFiles = []*os.File{file}
  9. err := cmd.Start()
  10. if err != nil {
  11. log.Fatalf("gracefulRestart: Failed to launch, error: %v", err)
  12. }

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

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

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

最后,args数组包含一个-graceful选项:你的程序需要某种方式通知孩子这是一个正常重启的一部分,孩子应该重新使用套接字而不是尝试打开一个新套接字。另一种方法可能是通过环境变量。

子初始化

这是程序启动序列的一部分

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  1. server := &http.Server{Addr: "0.0.0.0:8888"}
  2. var gracefulChild bool
  3. var l net.Listever
  4. var err error
  5. flag.BoolVar(&gracefulChild, "graceful", false, "listen on fd open 3 (internal use only)")
  6. if gracefulChild {
  7. log.Print("main: Listening to existing file descriptor 3.")
  8. f := os.NewFile(3, "")
  9. l, err = net.FileListener(f)
  10. } else {
  11. log.Print("main: Listening on a new file descriptor.")
  12. l, err = net.Listen("tcp", server.Addr)
  13. }

信号父母停止

此时我们已准备好接受请求,但就在我们这样做之前,我们需要告诉我们的父母停止接受请求并退出,这可能是这样的:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  1. if gracefulChild {
  2. parent := syscall.Getppid()
  3. log.Printf("main: Killing parent pid: %v", parent)
  4. syscall.Kill(parent, syscall.SIGTERM)
  5. }
  6. server.Serve(l)

正在进行的请求完成/超时

为此,我们需要使用sync.WaitGroup跟踪打开的连接 。我们需要在每个接受的连接上递增等待组,并在每个连接关闭时递减它。

  1. 1
  1. var httpWg sync.WaitGroup

乍一看,Golang标准的http包不提供任何钩子来对Accept()或Close()采取行动,但这就是界面魔法拯救的地方。(非常感谢Jeff R. Allen 对这篇文章的评价)。

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

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

接下来,我们“覆盖”Accept方法。(gracefulConn暂时没关系,稍后会介绍)。

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  1. func (gl *gracefulListener) Accept() (c net.Conn, err error) {
  2. c, err = gl.Listener.Accept()
  3. if err != nil {
  4. return
  5. }
  6. c = gracefulConn{Conn: c}
  7. httpWg.Add(1)
  8. return
  9. }

我们还需要一个“构造函数”:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  1. func newGracefulListener(l net.Listener) (gl *gracefulListener) {
  2. gl = &gracefulListener{Listener: l, stop: make(chan error)}
  3. go func() {
  4. _ = <-gl.stop
  5. gl.stopped = true
  6. gl.stop <- gl.Listener.Close()
  7. }()
  8. return
  9. }

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

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

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  1. func (gl *gracefulListener) Close() error {
  2. if gl.stopped {
  3. return syscall.EINVAL
  4. }
  5. gl.stop <- nil
  6. return <-gl.stop
  7. }

最后,这个小方便方法从中提取文件描述符net.TCPListener

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  1. func (gl *gracefulListener) File() *os.File {
  2. tl := gl.Listener.(*net.TCPListener)
  3. fl, _ := tl.File()
  4. return fl
  5. }

当然,我们还需要一个net.Conn减少等待组的变体 Close()

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  1. type gracefulConn struct {
  2. net.Conn
  3. }
  4. func (w gracefulConn) Close() error {
  5. httpWg.Done()
  6. return w.Conn.Close()
  7. }

要开始使用上面优雅的Listener版本,我们只需要将server.Serve(l)行更改为:

  1. 1
  2. 2
  1. netListener = newGracefulListener(l)
  2. server.Serve(netListener)

还有一件事。您应该避免挂断客户端无意关闭的连接(或不是本周)。最好按如下方式创建服务器:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  1. server := &http.Server{
  2. Addr: "0.0.0.0:8888",
  3. ReadTimeout: 10 * time.Second,
  4. WriteTimeout: 10 * time.Second,
  5. MaxHeaderBytes: 1 << 16}

Golang的优雅重启的更多相关文章

  1. [译]Golang中的优雅重启

    原文 Graceful Restart in Golang 作者 grisha 声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加. ...

  2. Golang开发支持平滑升级(优雅重启)的HTTP服务

    Golang开发支持平滑升级(优雅重启)的HTTP服务 - tabalt的博客 http://tabalt.net/blog/graceful-http-server-for-golang/ http ...

  3. Apache 优雅重启 Xampp开机自启 - 【环境变量】用DOS命令在任意目录下启动服务

    D:\xampp\apache\bin\httpd.exe" -k runservice Apache 优雅重启 :httpd -k graceful Xampp开机自启动  参考文献:ht ...

  4. Spring Boot 1.X和2.X优雅重启实战

    纯洁的微笑 今天 项目在重新发布的过程中,如果有的请求时间比较长,还没执行完成,此时重启的话就会导致请求中断,影响业务功能,优雅重启可以保证在停止的时候,不接收外部的新的请求,等待未完成的请求执行完成 ...

  5. apache2 重启、停止、优雅重启、优雅停止

    停止或者重新启动Apache有两种发送信号的方法 第一种方法: 直接使用linux的kill命令向运行中的进程发送信号.你也许你会注意到你的系统里运行着很多httpd进程.但你不应该直接对它们中的任何 ...

  6. golang 服务平滑重启小结

    背景 golang 程序平滑重启框架 supervisor 出现 defunct 原因 使用 master/worker 模式 背景 在业务快速增长中,前期只是验证模式是否可行,初期忽略程序发布重启带 ...

  7. Golang服务器热重启、热升级、热更新(safe and graceful hot-restart/reload http server)详解

    服务端代码经常需要升级,对于线上系统的升级常用的做法是,通过前端的负载均衡(如nginx)来保证升级时至少有一个服务可用,依次(灰度)升级. 而另一种更方便的方法是在应用上做热重启,直接更新源码.配置 ...

  8. Golang学习--平滑重启

    在上一篇博客介绍TOML配置的时候,讲到了通过信号通知重载配置.我们在这一篇中介绍下如何的平滑重启server. 与重载配置相同的是我们也需要通过信号来通知server重启,但关键在于平滑重启,如果只 ...

  9. iota: Golang 中优雅的常量

    阅读约 11 分钟 注:该文作者是 Katrina Owen,原文地址是 iota: Elegant Constants in Golang 有些概念有名字,并且有时候我们关注这些名字,甚至(特别)是 ...

随机推荐

  1. Mac pro 装双系统 参考

    15岁觉得游泳难,放弃游泳,到18岁遇到一个你喜欢的人约你去游泳,你只好说“我不会耶”.18岁觉得英文难,放弃英文,28岁出现一个很棒但要会英文的工作,你只好说“我不会耶”.人生前期越嫌麻烦,越懒得学 ...

  2. Immunity Debugger学习

    1.Immunity Debugger简介 Immunity Debugger软件专门用于加速漏洞利用程序的开发,辅助漏洞挖掘以及恶意软件分析.它具备一个完整的图形用户界面,同时还配备了迄今为止最为强 ...

  3. Java中的按位运算

    博客大搬家. 一.位运算符简介: 1.按位与&.如果两个整形数据 a.b 对应位都是1,则结果位才为1,否则为0,(int 最大值0x7fffffff ): int a = 0x7ffffff ...

  4. 【tmos】字段update_time如何动态的更新

    1.数据库设置 2.数据库不设置,用Jpa的注解来完成 @EnableJpaAuditing注解 @SpringBootApplication @EnableJpaAuditing public cl ...

  5. mysql原理~创建用户的那些事情

    一 简介:mysql是如何创建用户的二 基本语法:  1 grant 权限 on db.table to 'user'@'ip' identified by 'password'     目的 创建用 ...

  6. ubuntu16.04配置anaconda环境

    0 - 下载安装包 推荐到清华镜像下载.我选择的是Anaconda3-5.1.0-Linux-x86_64.sh. 1 - 安装Anaconda 然后切换到安装包目录,执行下面命令,期间一直按回车或者 ...

  7. 前端必备——js中前端与后台的数据交互全解

    只要编程语言能够支持网卡端口的监听和发送,理论上都是可以实现服务器后台设计的.也因此造成了实现后台的语言偏多,而web前端语言以html/css/js为主.所以在这里我们不涉及后台的设计,只介绍在we ...

  8. linux系统 户和账号操作

    1,基本操作要求 实现用户账号的管理,要完成的工作主要有如下几个方面: ·       用户账号的添加.删除与修改.·       用户口令的管理.·       用户组的管理. 2,用户账户添加删除 ...

  9. SQLServer常用分页方式

    mysql的分页是基于limit关键字,oracle的分页是基于rownum行号,SQLserver的分页在下面进行研究,是基于SQLServer2012进行的测试. 0.原来的SQL的所有数据 下面 ...

  10. Django-路由层

    URL配置(URLconf)就像Django所支撑网站的记录.它的本质是URL要为该URL调用的视图函数之间的映射表:你就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码对应 ...