详解如何热重启golang服务器
服务端代码经常需要升级,对于线上系统的升级常用的做法是,通过前端的负载均衡(如nginx)来保证升级时至少有一个服务可用,依次(灰度)升级。
而另一种更方便的方法是在应用上做热重启,直接升级应用而不停服务。
原理
热重启的原理非常简单,但是涉及到一些系统调用以及父子进程之间文件句柄的传递等等细节比较多。
处理过程分为以下几个步骤:
- 监听信号(USR2)
- 收到信号时fork子进程(使用相同的启动命令),将服务监听的socket文件描述符传递给子进程
- 子进程监听父进程的socket,这个时候父进程和子进程都可以接收请求
- 子进程启动成功之后,父进程停止接收新的连接,等待旧连接处理完成(或超时)
- 父进程退出,升级完成
细节
- 父进程将socket文件描述符传递给子进程可以通过命令行,或者环境变量等
- 子进程启动时使用和父进程一样的命令行,对于golang来说用更新的可执行程序覆盖旧程序
- server.Shutdown()优雅关闭方法是go1.8的新特性
- server.Serve(l)方法在Shutdown时立即返回,Shutdown方法则阻塞至context完成,所以Shutdown的方法要写在主goroutine中
代码
packagemain
import(
"context"
"errors"
"flag"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
var(
server*http.Server
listenernet.Listener
graceful=flag.Bool("graceful",false,"listenonfdopen3(internaluseonly)")
)
funchandler(whttp.ResponseWriter,r*http.Request){
time.Sleep(20*time.Second)
w.Write([]byte("helloworld233333!!!!"))
}
funcmain(){
flag.Parse()
http.HandleFunc("/hello",handler)
server=&http.Server{Addr:":9999"}
varerrerror
if*graceful{
log.Print("main:Listeningtoexistingfiledescriptor3.")
//cmd.ExtraFiles:Ifnon-nil,entryibecomesfiledescriptor3+i.
//whenweputsocketFDatthefirstentry,itwillalwaysbe3(0+3)
f:=os.NewFile(3,"")
listener,err=net.FileListener(f)
}else{
log.Print("main:Listeningonanewfiledescriptor.")
listener,err=net.Listen("tcp",server.Addr)
}
iferr!=nil{
log.Fatalf("listenererror:%v",err)
}
gofunc(){
//server.Shutdown()stopsServe()immediately,thusserver.Serve()shouldnotbeinmaingoroutine
err=server.Serve(listener)
log.Printf("server.Serveerr:%v\n",err)
}()
signalHandler()
log.Printf("signalend")
}
funcreload()error{
tl,ok:=listener.(*net.TCPListener)
if!ok{
returnerrors.New("listenerisnottcplistener")
}
f,err:=tl.File()
iferr!=nil{
returnerr
}
args:=[]string{"-graceful"}
cmd:=exec.Command(os.Args[0],args...)
cmd.Stdout=os.Stdout
cmd.Stderr=os.Stderr
//putsocketFDatthefirstentry
cmd.ExtraFiles=[]*os.File{f}
returncmd.Start()
}
funcsignalHandler(){
ch:=make(chanos.Signal,1)
signal.Notify(ch,syscall.SIGINT,syscall.SIGTERM,syscall.SIGUSR2)
for{
sig:=<-ch
log.Printf("signal:%v",sig)
//timeoutcontextforshutdown
ctx,_:=context.WithTimeout(context.Background(),20*time.Second)
switchsig{
casesyscall.SIGINT,syscall.SIGTERM:
//stop
log.Printf("stop")
signal.Stop(ch)
server.Shutdown(ctx)
log.Printf("gracefulshutdown")
return
casesyscall.SIGUSR2:
//reload
log.Printf("reload")
err:=reload()
iferr!=nil{
log.Fatalf("gracefulrestarterror:%v",err)
}
server.Shutdown(ctx)
log.Printf("gracefulreload")
return
}
}
}
references
GracefulRestartinGolang
facebookgo/grace
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。