Golang热重启
什么是热重启:新老程序(进程)无缝替换,同时可以保持对client的服务。让client端感觉不到你的服务挂掉了。 比如重新加载配置文件,需要重启一下,替换老程序需要重启一下,就需要用到热重启。但使用Golang的场景,其实直接在http proxy层面做切流量更方便. 原理通过发送signal(信号)与进程间交互。 信号可以自己定义,指定拦截系统的信号,改变系统默认行为来自定义操作。 如果收到重启信号,fork新版本的进程 将socket句柄交给新进程,新进程开始接受新连接请求 旧版本服务器停止接受连接,要保持已有的连接,处理完毕后立即停止旧版本服务器,关掉监听(os.Exit(1))。 简化版的重启过程我这里的代码就不处理原有的连接,方便理解关键点,可以参考其他文章用sync.WaitGroup.wait()来处理这个问题。 生成一个server监听8888端口,然后拦截系统信号,获取监听器获取监听器,这里用一个函数来获取,因为父进程和子进程获取到的监听器不一样,父进程获取到的是原来的tcp socket文件,而子进程获取到的只是这个 socket文件的copy。那么,要是你两次都获取tcp socket文件,相当于2次监听同一个端口,会报这个错误 func main() {
server = http.Server{
Addr: ":8888",Handler: &MyHandle{},ReadTimeout: 6 * time.Second,} go handleSignals() log.Printf("Actual pid is %dn",syscall.Getpid()) var err2 error listener,err2 = getListener(server.Addr) if err2 != nil { log.Println(err2) } "isChild : %v,listener: %vn",isChild,listener) err := server.Serve(listener) if err != nil { log.Println(err) } }
获取socket监听器和继承socket监听器isChild就是子进程的标志,假如是子进程那么 func getListener(laddr string) (l net.Listener,err error) {
if isChild {
runningServerReg.RLock()
defer runningServerReg.RUnlock()
f := os.NewFile(3,"")
l,err = net.FileListener(f)
if err != nil {
"net.FileListener error:",err)
return
}
"laddr : %v,listener: %v n",laddr,l)
syscall.Kill(syscall.Getppid(),syscall.SIGTSTP) //干掉父进程
} else {
l,err = net.Listen("tcp",laddr)
"net.Listen error: %v",114)">return
}
}
return
}
信号处理用signal包的signal.Notify过滤掉自己要定义的信号,给这些信号放行。SIGHUP用来fork子进程,SIGTSTP用来干掉父进程。SIGINT就用来自己CTR+C用了,方便中断测试。 func handleSignals() {
var sig os.Signal
signal.Notify(
sigChan,hookableSignals...,)
pid := syscall.Getpid()
for {
sig = <-sigChan
log.Println(pid,116)">"Received SIG.",sig)
switch sig {
case syscall.SIGHUP:
"Received SIGHUP. forking.")
err := fork()
log.Println("Fork err:",err)
}
case syscall.SIGTSTP:
"Received SIGTSTP.")
shutdown()
default:
"Received fork子进程
|