使用 Go 语言实现优雅的服务器重启

使用 Go 语言实现优雅的服务器重启

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

image

目标:

  • 不关闭现有连接:例如我们不希望关掉已部署的运行中的程序。但又想不受限制地随时升级服务。
  • socket连接要随时响应用户请求:任何时刻socket的关闭可能使用户返回'连接被拒绝'的消息,而这是不可取的。
  • 新的进程要能够启动并替换掉旧的。

原理

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

  • SIGTERM: 优雅地停止进程
  • SIGHUP: 重启/重新加载进程 (例如: nginx, sshd, apache)

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

  1. 服务器要拒绝新的连接请求,但要保持已有的连接。
  2. 启用新版本的进程
  3. 将socket“交给”新进程,新进程开始接受新连接请求
  4. 旧进程处理完毕后立即停止。

停止接受连接请求

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


  1. for {
  2. conn, err := listener.Accept()
  3. // Handle connection
  4. }

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


  1. for {
  2. conn, err := listener.Accept()
  3. if err != nil {
  4. if nerr, ok := err.(net.Err); ok && nerr.Timeout() {
  5. fmt.Println(“Stop accepting connections”)
  6. return
  7. }
  8. }
  9. }

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

启动新进程

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


  1. 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而不是新建一个(有些兴许已经在使用了,因为我们还没断开已有的监听)。你可以按任何你希望的方法来,最常见的是通过环境变量或命令行标志。


  1. listenerFile, err := listener.File()
  2. if err != nil {
  3. log.Fatalln("Fail to get socket file descriptor:", err)
  4. }
  5. listenerFd := listenerFile.Fd()
  6.  
  7. // Set a flag for the new process start process
  8. os.Setenv("_GRACEFUL_RESTART", "true")
  9.  
  10. execSpec := &syscall.ProcAttr{
  11. Env: os.Environ(),
  12. Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd},
  13. }
  14. // Fork exec the new version of your server
  15. fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec)

 然后在程序的开始处:


  1. var listener *net.TCPListener
  2. if os.Getenv("_GRACEFUL_RESTART") == "true" {
  3. // The second argument should be the filename of the file descriptor
  4. // however, a socker is not a named file but we should fit the interface
  5. // of the os.NewFile function.
  6. file := os.NewFile(3, "")
  7. listener, err := net.FileListener(file)
  8. if err != nil {
  9. // handle
  10. }
  11. var bool ok
  12. listener, ok = listener.(*net.TCPListener)
  13. if !ok {
  14. // handle
  15. }
  16. } else {
  17. listener, err = newListenerWithPort(12345)
  18. }

文件描述没有被随机的选择为3,这是因为uintptr的切片已经发送了fork,监听获取了索引3。留意隐式声明问题

最后一步,等待旧服务连接停止

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

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


  1. for {
  2. conn, err := listener.Accept()
  3.  
  4. wg.Add(1)
  5. go func() {
  6. handle(conn)
  7. wg.Done()
  8. }()
  9. }

至于等待连接的结束,你仅需要wg.Wait(),因为没有新的连接,我们等待wg.Done()已经被所有正在运行的handler调用。

Bonus: 不要无限制等待,给定限量的时间


  1. timeout := time.NewTimer(time.Minute)
  2. wait := make(chan struct{})
  3. go func() {
  4. wg.Wait()
  5. wait <- struct{}{}
  6. }()
  7.  
  8. select {
  9. case <-timeout.C:
  10. return WaitTimeoutError
  11. case <-wait:
  12. return nil
  13. }

完整的示例

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

结论

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

原文发布时间:2014-12-26

本文来自云栖合作伙伴“linux中国”

时间: 2024-10-28 09:56:05

使用 Go 语言实现优雅的服务器重启的相关文章

Go语言实现简单Web服务器的方法_Golang

本文实例讲述了Go语言实现简单Web服务器的方法.分享给大家供大家参考.具体分析如下: 包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求: package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) } 在这个例子中,类型 Hello 实现了 http.Handler. 注意: 这个例子无法在基于 web 的指南用户界面运行.为了尝试编写 web 服务器,可能需要安装

linux服务器重启后ssh端口总是密码错

问题描述 linux服务器重启后ssh端口总是密码错 putty通过ssh连接azure上的linux, 刚开始都是好的,但是在linux中用shutdown -r 0 重启后,再连上去首先提示"连接证书"改变了,然后就是登录总是说登录失败. 解决方案 找下服务商解决,另外可能需要排除一下是不是被黑了.: 解决方案二: 是不是重启后,进入其他用户了,证书是你当前用户吗 解决方案三: Hi, 我尝试了下你所描述的过程,在使用shutdown -r 0 并重启后还是能够成功的连接上虚拟机,

网站-用JAVA语言如何做一个服务器

问题描述 用JAVA语言如何做一个服务器 我要做一个网站,接收终端机发来的信息并且处理,再发数据给终端机,这样该怎么做 解决方案 java开发网站后台服务器端,看你的规模了. 如果网页简单你就直接使用这个Servlet进行开发就可以了. 很方便就可以实现了. 如果比较复杂,那你就考虑使用框架吧. 使用SSH或者是SSI框架组合进行开发. 假定你已经想好了你的网站的访问Url,业务处理方法,以及你存储数据的数据库的设计都OK,制作流程大致如下 (如果没有,你先从纸面分析设计下你的系统都有什么功能(

网站-服务器重启后出现502 bad gateway 的错误 后来直接网络连接错误105了

问题描述 服务器重启后出现502 bad gateway 的错误 后来直接网络连接错误105了 把linux服务器重启之后,挂载在改服务器上的网站就进不去了,刚开始是报502 bad gateway的错误,后来直接进不去,报网络解析错误105,使用别人的电脑进该网站的时候却报的是连接错误102,好捉急 解决方案 http://my.oschina.net/junn/blog/147923,希望这个对您有帮助! 解决方案二: http://my.oschina.net/junn/blog/1479

Windows服务器重启导致filebeat无法启动

早上6点钟, 收到zabbix的告警, 说一台服务器重启了, 回到公司马上查看系统日志,发现只有这些记录: 这不是坑爹么! 肯定是意外关闭啊, 但是为什么会是意外关闭呀? 这个问题后续需要再跟进, 现在暂不讨论, 因为有个更加急迫的故障需要处理: filebeat无法启动. 既然系统无法启动, 咱们去服务管理那边试下: 运行 - 输入services.msc 找到filebeat的服务后, 手动启动失败, 得到错误: 在谷歌上搜索一番之后, 找到一个解决办法: 我的电脑-->右键-->管理--

服务器重启后SQL Server Agent由于&quot;The EventLog service has not been started&quot; 启动失败

案例环境:     操作系统   : Microsoft Windows Server 2003 Standard Edtion SP2     数据库版本 : SQL Server 2005 Standard Edition SP4   案例描述:     服务器重启过后,MSSQLSERVER服务自动重启了,但是SQLSERVERAGENT服务启动失败(当然SQL Agent服务的启动类型为自动启动(Automatic)),在这台服务器第二次遇到这种情况,第一次遇到时没太注意,以为只是特殊案

java-log4j动态设定文件名,并且按每分钟产生一个日志文件,为什么只有在服务器重启的第一次成功

问题描述 log4j动态设定文件名,并且按每分钟产生一个日志文件,为什么只有在服务器重启的第一次成功 log4j动态设定文件名,并且按每分钟产生一个日志文件,为什么只有在服务器重启的第一次成功,以后的日志都不会根据时间产生新文件 配置文件 log4j.logger.test1 = DEBUG,test1 log4j.additivity.test1 = false log4j.appender.test1 = org.apache.log4j.DailyRollingFileAppender l

阿里云LNMP 云服务器重启后网站打不开解决方法_Linux

今天升级了一下系统架构,把MySQL升级到5.5.30,Nginx升级到1.4.1,php升级到5.5.25,但是升级之后服务器挂掉了,于是我就用了回滚快照,让系统恢复到原来的状态(阿里云的回滚快照功能需要重启服务器),服务器回滚之后,数据都在,但是就是无法提供web服务,尝试了 iptables -F清除防火墙规则,但是网站仍旧打不开.通过ps -aux命令发现nginx没有启动,于是执行了 复制代码 代码如下: /usr/local/nginx/sbin/nginx 之后,服务器web服务果

服务器重启前请做完你该做的工作

对于系统管理员来说如何管理自己的服务器已经是再简单不过,但是如何管理好服务器却不是一个简单的事情.对于管理员来说重启服务器可不是一件闹着玩的事情.对于Windows服务器管理员来说经常性重启Windows设备已经成为一种生活常态,但在Unix系统中这种办法却难以奏效--在默认情况下重新启动不会带来任何形式的改善. 我打算借此机会跟大家详细聊聊重启的问题.对于每一位服务器管理员来说这都算得上热门话题,但在Unix极客们眼中它则属于一种层次更深的课题--可能因为Windows管理员们往往把重启当成故