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,但这个时间很短。
当我运行Spring MVC应用程序时,我得到了这个异常,服务器无法启动。 请帮我解决这个问题。 异常堆栈跟踪:
本文向大家介绍Go语言实现简单Web服务器的方法,包括了Go语言实现简单Web服务器的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Go语言实现简单Web服务器的方法。分享给大家供大家参考。具体分析如下: 包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求: package http type Handler interface { ServeHTTP
问题内容: 当我运行Spring MVC应用程序时,出现此异常,服务器无法启动。 请帮助我解决此问题。 异常StackTrace: 问题答案: 由于互联网连接不良,jar文件可能已损坏。尝试删除文件夹的内容。然后右键单击您的项目,选择“Maven” ,“ 更新项目” ,然后选中“ 强制更新快照/版本” 。如果您确定只有一个Jar文件有问题,则只需删除其文件夹。
我有两个问题,我找不到任何流行/广泛接受的解决方案: > 使用Java程序启动zookeeper服务器最简单的方法是什么? 而且,是否可以将服务器添加到zookeeper集群,而无需手动转到每台机器并使用新节点的id和ip:端口条目更新其配置文件? 有人能帮忙吗?谢谢。
本文向大家介绍在Apache服务器中运行CGI程序的方法,包括了在Apache服务器中运行CGI程序的方法的使用技巧和注意事项,需要的朋友参考一下 关于apache与CGI在这里就不解释了. 1、apache下载地址:http://www.apache.org,下面以2.0.63为例介绍运行CGI程序的配置。 2、下载Windows下的Perl解释器ActivePerl,官方网站:http://w
本文向大家介绍go语言实现简单http服务的方法,包括了go语言实现简单http服务的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了go语言实现简单http服务的方法。分享给大家供大家参考。具体实现方法如下: 希望本文所述对大家的Go语言程序设计有所帮助。