详解如何热重启golang服务器

服务端代码经常需要升级,对于线上系统的升级常用的做法是,通过前端的负载均衡(如nginx)来保证升级时至少有一个服务可用,依次(灰度)升级。

而另一种更方便的方法是在应用上做热重启,直接升级应用而不停服务。

原理

热重启的原理非常简单,但是涉及到一些系统调用以及父子进程之间文件句柄的传递等等细节比较多。
处理过程分为以下几个步骤:

  • 监听信号(USR2)
  • 收到信号时fork子进程(使用相同的启动命令),将服务监听的socket文件描述符传递给子进程
  • 子进程监听父进程的socket,这个时候父进程和子进程都可以接收请求
  • 子进程启动成功之后,父进程停止接收新的连接,等待旧连接处理完成(或超时)
  • 父进程退出,升级完成

细节

  • 父进程将socket文件描述符传递给子进程可以通过命令行,或者环境变量等
  • 子进程启动时使用和父进程一样的命令行,对于golang来说用更新的可执行程序覆盖旧程序
  • server.Shutdown()优雅关闭方法是go1.8的新特性
  • server.Serve(l)方法在Shutdown时立即返回,Shutdown方法则阻塞至context完成,所以Shutdown的方法要写在主goroutine中

代码

package main
import (
  "context"
  "errors"
  "flag"
  "log"
  "net"
  "net/http"
  "os"
  "os/exec"
  "os/signal"
  "syscall"
  "time"
)

var (
  server  *http.Server
  listener net.Listener
  graceful = flag.Bool("graceful", false, "listen on fd open 3 (internal use only)")
)

func handler(w http.ResponseWriter, r *http.Request) {
  time.Sleep(20 * time.Second)
  w.Write([]byte("hello world233333!!!!"))
}

func main() {
  flag.Parse()

  http.HandleFunc("/hello", handler)
  server = &http.Server{Addr: ":9999"}

  var err error
  if *graceful {
    log.Print("main: Listening to existing file descriptor 3.")
    // cmd.ExtraFiles: If non-nil, entry i becomes file descriptor 3+i.
    // when we put socket FD at the first entry, it will always be 3(0+3)
    f := os.NewFile(3, "")
    listener, err = net.FileListener(f)
  } else {
    log.Print("main: Listening on a new file descriptor.")
    listener, err = net.Listen("tcp", server.Addr)
  }

  if err != nil {
    log.Fatalf("listener error: %v", err)
  }

  go func() {
    // server.Shutdown() stops Serve() immediately, thus server.Serve() should not be in main goroutine
    err = server.Serve(listener)
    log.Printf("server.Serve err: %v\n", err)
  }()
  signalHandler()
  log.Printf("signal end")
}

func reload() error {
  tl, ok := listener.(*net.TCPListener)
  if !ok {
    return errors.New("listener is not tcp listener")
  }

  f, err := tl.File()
  if err != nil {
    return err
  }

  args := []string{"-graceful"}
  cmd := exec.Command(os.Args[0], args...)
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr
  // put socket FD at the first entry
  cmd.ExtraFiles = []*os.File{f}
  return cmd.Start()
}

func signalHandler() {
  ch := make(chan os.Signal, 1)
  signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)
  for {
    sig := <-ch
    log.Printf("signal: %v", sig)

    // timeout context for shutdown
    ctx, _ := context.WithTimeout(context.Background(), 20*time.Second)
    switch sig {
    case syscall.SIGINT, syscall.SIGTERM:
      // stop
      log.Printf("stop")
      signal.Stop(ch)
      server.Shutdown(ctx)
      log.Printf("graceful shutdown")
      return
    case syscall.SIGUSR2:
      // reload
      log.Printf("reload")
      err := reload()
      if err != nil {
        log.Fatalf("graceful restart error: %v", err)
      }
      server.Shutdown(ctx)
      log.Printf("graceful reload")
      return
    }
  }
}

references

Graceful Restart in Golang

facebookgo/grace

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Golang实现http重定向https的方式

    以前写代码时,都是直接将程序绑定到唯一端口提供http/https服务,在外层通过反向代理(nginx/caddy)来实现http和https的切换.随着上线后的服务越来越多,有一些服务无法直接通过反向代理来提供这种重定向,只能依靠代码自己实现.所以简要记录一下如何在代码中实现http到https的重定向. 分析 无论是反向代理还是代码自己实现,问题的本质都是判断请求是否是https请求. 如果是则直接处理,如果不是,则修改请求中的url地址,同时返回客户端一个重定向状态码(301/302/30

  • Golang中定时器的陷阱详解

    前言 在业务中,我们经常需要基于定时任务来触发来实现各种功能.比如TTL会话管理.锁.定时任务(闹钟)或更复杂的状态切换等等.百纳网主要给大家介绍了关于Golang定时器陷阱的相关内容,所谓陷阱,就是它不是你认为的那样,这种认知误差可能让你的软件留下隐藏Bug.刚好Timer就有3个陷阱,我们会讲 1)Reset的陷阱和 2)通道的陷阱, 3)Stop的陷阱与Reset的陷阱类似,自己探索吧. 下面话不多说了,来一起看看详细的介绍吧 Reset的陷阱在哪 Timer.Reset()函数的返回值是

  • golang sql连接池的实现方法详解

    前言 golang的"database/sql"是操作数据库时常用的包,这个包定义了一些sql操作的接口,具体的实现还需要不同数据库的实现,mysql比较优秀的一个驱动是:github.com/go-sql-driver/mysql,在接口.驱动的设计上"database/sql"的实现非常优秀,对于类似设计有很多值得我们借鉴的地方,比如beego框架cache的实现模式就是借鉴了这个包的实现:"database/sql"除了定义接口外还有一个重

  • golang设置http response响应头与填坑记录

    1. 设置WriteHeader的顺序问题 之前遇到个问题,在一段代码中这样设置WriteHeader,最后在header中取Name时怎么也取不到. w.WriteHeader(201) w.Header().Set("Name", "my name is smallsoup") 用 golang 写 http server 时,可以很方便可通过 w.Header.Set(k, v) 来设置 http response 中 header 的内容.但是需要特别注意的

  • golang flag简单用法

    通过一个简单的实例,来让大家了解一下golang flag包的一个简单的用法 package main import ( "flag" "strings" "os" "fmt" ) var ARGS string func main() { var uptime *bool = new(bool) flag.BoolVar(uptime,"u", false, "print system upti

  • Golang如何交叉编译各个平台的二进制文件详解

    Golang交叉编译平台的二进制文件 熟悉golang的人都知道,golang交叉编译很简单的,只要设置几个环境变量就可以了 # mac上编译linux和windows二进制 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build # linux上编译mac和windows二进制 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go

  • golang中命令行库cobra的使用方法示例

    简介 Cobra既是一个用来创建强大的现代CLI命令行的golang库,也是一个生成程序应用和命令行文件的程序.下面是Cobra使用的一个演示: Cobra提供的功能 简易的子命令行模式,如 app server, app fetch等等 完全兼容posix命令行模式 嵌套子命令subcommand 支持全局,局部,串联flags 使用Cobra很容易的生成应用程序和命令,使用cobra create appname和cobra add cmdname 如果命令输入错误,将提供智能建议,如 ap

  • golang利用不到20行代码实现路由调度详解

    前言 本文主要介绍了关于golang实现路由调度的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 项目地址 github (本地下载) 本项目依赖 使用标准库实现,无额外依赖 为什么需要路由调度层 golang http标准库只能精确匹配请求的URI,然后执行handler.现在一般web项目都至少有个Controller层,以struct实现,根据不同的请求路径派发到不同的方法中去. 路由调度器定义 由于golang暂时还不可以动态创建对象(比如java的Class.

  • 详解如何热重启golang服务器

    服务端代码经常需要升级,对于线上系统的升级常用的做法是,通过前端的负载均衡(如nginx)来保证升级时至少有一个服务可用,依次(灰度)升级. 而另一种更方便的方法是在应用上做热重启,直接升级应用而不停服务. 原理 热重启的原理非常简单,但是涉及到一些系统调用以及父子进程之间文件句柄的传递等等细节比较多. 处理过程分为以下几个步骤: 监听信号(USR2) 收到信号时fork子进程(使用相同的启动命令),将服务监听的socket文件描述符传递给子进程 子进程监听父进程的socket,这个时候父进程和

  • 详解如何热更新线上的Java服务器代码

    一.前言 1.热更新代码的场景 (1)当线上服务器出现问题时,有些时候现有的手段不足以发现问题所在,可能需要追加打印日志或者增加一些调试代码,如果我们去改代码重新部署,会破坏问题现场,可以通过热部署的手段来增加调试代码 (2)线上出现紧急bug,通过Review代码找到问题,修改好后打包部署的流程可能比较久,可以通过热部署代码及时解决问题 二.Arthas的使用 使用阿里巴巴开源的Java诊断工具---Arthas,他可以附着在我们的Java服务器进程上面,查看服务器状态,jvm状态等各种参数指

  • 详解tomcat热部署和热加载的方法

    详解tomcat热部署和热加载的方法 我在项目开发过程中,经常要改动Java/JSP 文件,但是又不想从新启动服务器(服务器从新启动花时间),想直接获得(debug)结果.有两种方式热部署 和热加载: 1.热加载:在server.xml -> context 属性中 设置 reloadable="true" <Context docBase="xxx" path="/xxx" reloadable="true"/&

  • 详解samba + OPENldap 搭建文件共享服务器问题

    这里我使用的是 samba(文件共享服务) v4.9.1 + OPENldap(后端数据库软件) v2.4.44 + smbldap-tools(后端数据库管理软件) v0.9.11 + CentOS7. 如果有不同,可能会有部分问题. 注: samba 的功能不只有文件共享,还可以作为一台Windows域成员,甚至Windows域控制器.千万不要认为samba只是一个文件共享服务. 由于我们使用了samba的文件共享功能,与文件权限有直接的联系,所以samba中的使用的用户必须是Linux中能

  • 详解用Nginx搭建CDN服务器方法(图文)

    利用Nginx的proxy_cache搭建缓存服务器一:编译ngx_cache_purge 1.Nginx的Proxy_cache是根据Key值md5哈希存储缓存,支持任意的Key,例如你可以根据"域名.URI.参数"组合成key,也支持非200状态码,如404/302等. 2.要利用Nginx的Proxy_cache,你需要在Nginx编译进ngx_cache_purge 模块,执行:nginx -V,查看有没有ngx_cache_purge 字样,没有的话需要自己手动编译. Ngi

  • 详解Nginx如何配置Web服务器的示例代码

    概述 今天主要分享怎么将NGINX配置作为Web服务器,并包括以下部分: 设置虚拟服务器 配置位置 使用变量 返回特定状态码 重写HTTP响应 在高层次上,将NGINX配置作为Web服务器有一些问题需要了解,定义它处理哪些URL以及如何处理这些URL上的资源的HTTP请求. 在较低层次上,配置定义了一组控制对特定域或IP地址的请求的处理的虚拟服务器. 用于HTTP流量的每个虚拟服务器定义了称为位置的特殊配置实例,它们控制特定URI集合的处理. 每个位置定义了自己的映射到此位置的请求发生的情况.

  • 详解如何在阿里云服务器部署程序并用域名直接访问

    闲来无事,买了一个最便宜的阿里云服务器来学习,一年三百多,适合新手了解程序等. 一般买服务器只有公网的IP地址,也就是类似10.205.25.32这种形式的.如何想用域名(例如www.baidu.com)直接访问的你网站,可以在阿里云直接再买个域名,将域名解析绑定ip地址. 有人想知道怎么解析域名,我这里补充一下域名相关内容 1.域名: 域名分为一级,二级,三级域名,如www.baidu.com,baidu为一级域名,www为二级域名,此网址没有三级域名 而比如mail.www.baidu.co

  • 详解vscode实现远程linux服务器上Python开发

    最近需要训练一个生成对抗网络模型,然后开发接口,不得不在一台有显卡的远程linux服务器上进行,所以,趁着这个机会研究了下怎么使用vscode来进行远程开发. (1)在windows系统命令行下运行命令:ssh-keygen, 一路回车,将会在C:\Users\用户名.ssh目录下生成两个文件:id_rsa和id_rsa.pub. 前者是私钥,后者是公钥.如下所示: (2)将公钥文件的内容拷贝到远程linux服务器需要免密登录的用户家目录内的.ssh目录内,重命名为authorized_keys

  • 详解Nodejs get获取远程服务器接口数据

    本文实例为大家分享了Nodejs get获取远程服务器接口数据的具体代码,供大家参考,具体内容如下 1.GET模块:_get.js /** * Created by jinx on 7/7/17. */ var http = require('http'); module.exports = { /** * 测试获取所有的区域 * / locations: function (cb) { http.get('http://wx.xx.com/locations', function (res)

  • 详解Liunx下配置DNS服务器

    当Ping 主机名时可以映射出该主机的IP地址,反之亦然.配置并指定DNS服务器可以快速部署集群,不需要每台主机都去修改HOSTS文件即可实现IP与主机名的相互解析.而在Linux下的DNS是用bind来实现的. 环境: 机器IP               机器名称            用途 10.190.60.5    hadoop01.ftgov   -- DNS主机 10.190.60.6    hadoop02.ftgov   -- DNS客户机 10.190.60.7    had

随机推荐