上篇文章中提到的Golang热更新,本文将详细讨论。1、什么是热更新网上有这样一个例子来描述热更新,我觉得很形象很贴切:一辆高速行驶的大货车行驶中突然爆胎,热更新要求不停在补胎的情况下,卡车在补胎过程中需要保持正常行驶。软件的热更新是指在保持系统正常运行的同时对系统进行更新升级。常见的情况包括:系统服务升级、已有逻辑修复、服务配置更新等。2、热更新原理我们先来看看Nginx热更新是怎么做的?Nginx支持在运行过程中接收信号,方便开发者控制进程。1)先备份原来的Nginx二进制文件,用新编译的Nginx二进制文件替换旧的2)然后发送USR2信号给master进程。此时Nginx进程会启动新版本的Nginx,新版本的Nginx进程会启动新的master进程和work进程。即此时会有两个Nginx实例在运行,共同处理新的请求。3)然后向原master进程发送WINCH信号,它会逐渐关闭相关的工作进程,此时原master进程仍然保持监听新的请求但不会发送给它下面的工作进程,而是发送给newworkprocess4)最后等到所有原来的work进程都关闭后,给原来的master进程发送一个QUIT信号,原来的master进程就终止了。至此,Nginx的热升级就完成了。注:在*nix系统中,信号(Signal)是一种进程间通信机制,它为应用程序提供一个异步的软件中断,使应用程序有机会接受其他程序发送的命令(即信号)或终端。同样,Golang热更新也可以类似处理。上一篇文章中提到,使用了用户自定义信号USR2。注:Plugin包方式的Golang热更新本文不做讨论。3、热更新实现Golang热更新可以细分为服务热“更新”(即热升级,类比Nginx的restart命令)和配置文件热更新(类比Nginx的reload命令)。接下来,我们依次讨论实现细节。3.1服务热更新的大致流程如下:1)Golang服务进程在运行时监听USR2信号2)进程收到USR2信号后,fork子进程(启动新版本服务)并将当前socket句柄和其他进程环境交给它3)新进程开始监听socket请求4)等待旧服务连接停止主要代码示例如下:监听USR2信号func(a*app)signalHandler(wg*sync.WaitGroup){ch:=make(chanos.Signal,10)信号。Notify(ch,syscall.SIGINT,syscall.SIGTERM,syscall.SIGUSR2)for{sig:=<-chswitchsig{casesyscall.SIGINT,syscall.SIGTERM://确保当INT/接收到TERM信号signal.Stop(ch)a.term(wg)returncasesyscall.SIGUSR2:err:=a.preStartProcess()iferr!=nil{a.errors<-err}//开始的进程终止行为一个新进程if_,err:=a.net.StartProcess();err!=nil{a.errors<-err}}}}复制当前进程套接字连接,启动新进程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)...详细源码可见:https://scalingo.com/articles...以上只是代码示例。目前成熟的开源实现主要有:endless和facebook的grace,原理基本类似,fork一个子进程,子进程监听原父进程的socket端口,父进程优雅退出。在实际生产环境中,推荐使用上述开源库。使用热更新开源库非常方便。下面是facebook的grace库Example:importgithub.com/facebookgo/grace/gracehttppackagefuncmain(){app:=gin.New()//项目是gin框架router.Route(app)varserver*http.Serverserver=&http.Server{Addr:":8080",Handler:app,}gracehttp.Serve(server)}使用gobuild命令编译生成服务可执行文件。然后用shell封装service命令,生成restat.sh命令文件#!/bin/shpsaux|grepwingocount=`ps-ef|grep“wingo”|grep-v“grep”|wc-l`echo""if[0==$count];然后echo"Wingostarting..."sudo./wingo&echo"Wingostarted"elseecho"WingoRestarting..."sudokill-USR2$(ps-ef|grep"wingo"|grep-vgrep|awk'{print$2}')echo"WingoRestarted"fisleep1psaux|grepwingo注意:其中wingo是服务的二进制名称。因此,可以通过执行./restart.sh命令来实现服务的热升级。3.2配置文件热更新配置文件热更新是指在不停止服务的情况下重新加载服务的所有配置文件。同3.1服务热升级原理一样,使用自定义信号:USR1实现服务配置文件的热更新。1)服务监听USR1信号2)服务接收到USR1信号后,停止接受新连接,等待当前连接停止,重新加载配置文件,重启服务器,从而实现比较顺利的非-停止服务更改。主要代码实现://LoadAllConf调用加载配置文件函数//load是具体的加载配置文件方法funcLoadAllConf(loadfunc(bool)){load(true)listenSIGUSR1(load)}//listenSIGUSR1监听SIGUSR1信号funclistenSIGUSR1(ffunc(bool)){s:=make(chanos.Signal,1)signal.Notify(s,syscall.SIGUSR1)gofunc(){for{<-sf(false)log.Println("Reloaded")}}()}详细源码可以在:https://www.openmymind.net/Go...使用gobuild命令编译生成服务的可执行文件。然后使用shell封装配置重载命令生成reload.sh命令文件#!/bin/shpsaux|grepwingoecho""echo"WingoReloading..."sudokill-USR1$(ps-ef|grep"wingo"|grep-vgrep|awk'{print$2}')echo"WingoReloaded"echo""sleep1ps辅助|grepwingo然后,就可以执行./reload.sh命令来达到热升级服务配置文件的目的。4.总结本文主要介绍Golang服务热升级和配置文件热更新的原理和主要代码实现。它本质上并不新鲜。如果你以前读过《Unix环境高级编程》,你会觉得很熟悉。底层原理基本上是利用信号的软件中断机制来改变常驻进程运行时的行为。参考资料https://scalingo.com/articles...http://kuangchanglang.com/gol...https://blog.csdn.net/black_O...https://www.openmymind.net/去。..https://blog.csdn.net/qq_1543...https://wrfly.kfd.me/posts/%E...
