• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    在Go程序中实现服务器重启的方法

    Go被设计为一种后台语言,它通常也被用于后端程序中。服务端程序是GO语言最常见的软件产品。在这我要解决的问题是:如何干净利落地升级正在运行的服务端程序。
    目标:

    原理

    在基于Unix的操作系统中,signal(信号)是与长时间运行的进程交互的常用方法.

    如果收到SIGHUP信号,优雅地重启进程需要以下几个步骤:

    停止接受连接请求

    服务器程序的共同点:持有一个死循环来接受连接请求:

       

    复制代码 代码如下:
    for {
          conn, err := listener.Accept()
          // Handle connection
        }

    跳出这个循环的最简单方式是在socket监听器上设置一个超时,当调用listener.SetTimeout(time.Now())后,listener.Accept()会立即返回一个timeout err,你可以捕获并处理:

       

    复制代码 代码如下:
    for {
          conn, err := listener.Accept()
          if err != nil {
            if nerr, ok := err.(net.Err); ok nerr.Timeout() {
               fmt.Println(“Stop accepting connections”)
               return
            }
          }
        }

    注意这个操作与关闭listener有所不同。这样进程仍在监听服务器端口,但连接请求会被操作系统的网络栈排队,等待一个进程接受它们。
    启动新进程

    Go提供了一个原始类型ForkExec来产生新进程.你可以与这个新进程共享某些消息,例如文件描述符或环境参数。

       

    复制代码 代码如下:
    execSpec := syscall.ProcAttr{
          Env:   os.Environ(),
          Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
        }
        fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec)
        […]

     你会发现这个进程使用完全相同的参数os.Args启动了一个新进程。
    发送socket到子进程并恢复它

    正如你先前看到的,你可以将文件描述符传递到新进程,这需要一些UNIX魔法(一切都是文件),我们可以把socket发送到新进程中,这样新进程就能够使用它并接收及等待新的连接。

    但fork-execed进程需要知道它必须从文件中得到socket而不是新建一个(有些兴许已经在使用了,因为我们还没断开已有的监听)。你可以按任何你希望的方法来,最常见的是通过环境变量或命令行标志。

       

    复制代码 代码如下:
    listenerFile, err := listener.File()
        if err != nil {
          log.Fatalln("Fail to get socket file descriptor:", err)
        }
        listenerFd := listenerFile.Fd()
        
        // Set a flag for the new process start process
        os.Setenv("_GRACEFUL_RESTART", "true")
        
        execSpec := syscall.ProcAttr{
          Env:   os.Environ(),
          Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd},
        }
        // Fork exec the new version of your server
        fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec)

     然后在程序的开始处:

       

    复制代码 代码如下:
    var listener *net.TCPListener
        if os.Getenv("_GRACEFUL_RESTART") == "true" {
          // The second argument should be the filename of the file descriptor
          // however, a socker is not a named file but we should fit the interface
          // of the os.NewFile function.
          file := os.NewFile(3, "")
          listener, err := net.FileListener(file)
          if err != nil {
            // handle
          }
          var bool ok
          listener, ok = listener.(*net.TCPListener)
          if !ok {
            // handle
          }
        } else {
          listener, err = newListenerWithPort(12345)
        }

    文件描述没有被随机的选择为3,这是因为uintptr的切片已经发送了fork,监听获取了索引3。留意隐式声明问题。
    最后一步,等待旧服务连接停止

    到此为止,就这样,我们已经将其传到另一个正在正确运行的进程,对于旧服务器的最后操作是等其连接关闭。由于标准库里提供了sync.WaitGroup结构体,用go实现这个功能很简单。

    每次接收一个连接,在WaitGroup上加1,然后,我们在它完成时将计数器减一:

       

    复制代码 代码如下:
    for {
          conn, err := listener.Accept()
        
          wg.Add(1)
          go func() {
            handle(conn)
            wg.Done()
          }()
        }

    至于等待连接的结束,你仅需要wg.Wait(),因为没有新的连接,我们等待wg.Done()已经被所有正在运行的handler调用。
    Bonus: 不要无限制等待,给定限量的时间

       

    复制代码 代码如下:
    timeout := time.NewTimer(time.Minute)
        wait := make(chan struct{})
        go func() {
          wg.Wait()
          wait - struct{}{}
        }()
        
        select {
        case -timeout.C:
          return WaitTimeoutError
        case -wait:
          return nil
        }

    完整的示例

    这篇文章中的代码片段都是从这个完整的示例中提取的:https://github.com/Scalingo/go-graceful-restart-example
    结论

    socket传递配合ForkExec使用确实是一种无干扰更新进程的有效方式,在最大时间上,新的连接会等待几毫秒——用于服务的启动和恢复socket,但这个时间很短。

    您可能感兴趣的文章:
    • Go语言基于Socket编写服务器端与客户端通信的实例
    • Go语言实现socket实例
    • 服务器端Go程序对长短链接的处理及运行参数的保存
    • Centos5.4+Nginx-0.8.50+UWSGI-0.9.6.2+Django-1.2.3搭建高性能WEB服务器
    • 在Apache服务器上同时运行多个Django程序的方法
    • 编写Go程序对Nginx服务器进行性能测试的方法
    • C++、python和go语言实现的简单客户端服务器代码示例
    • go语言实现一个最简单的http文件服务器实例
    • Go语言Echo服务器的方法
    • Go语言实现简单Web服务器的方法
    • Go语言服务器开发实现最简单HTTP的GET与POST接口
    • Go语言服务器开发之客户端向服务器发送数据并接收返回数据的方法
    • 剖析Go编写的Socket服务器模块解耦及基础模块的设计
    上一篇:go语言中的interface使用实例
    下一篇:在Linux系统中安装Go语言的详细教程
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    在Go程序中实现服务器重启的方法 在,程序,中,实现,服务器,