从PHP迁移至Golang - 热更新篇

news/2025/5/24 1:49:00

上篇大致提到的Golang的热更新,本篇将详细论述。

1、什么是热更新

网络上有这么一个例子来形容热更新,我觉得很形象很贴切:

一架行驶在高速上的大卡车,行驶过程中突然遭遇爆胎,热更新则是要求在不停车的情况下将车胎修补好,且补胎过程中卡车需要保持正常行驶。

软件的热更新就是指在保持系统正常运行的情况下对系统进行更新升级。常见的情况有:系统服务升级、修复现有逻辑、服务配置更新等。

2、热更新原理

先来看下Nginx热更新是如何做的?
Nginx支持运行中接收信号,方便开发者控制进程。

  • 1)首先备份原有的Nginx二进制文件,并用新编译好的Nginx二进制文件替换旧的
  • 2)然后向master进程发送USR2信号。此时Nginx进程会启动一个新版本Nginx,该新版本Nginx进程会发起一个新的master进程与work进程。即此时会有两个Nginx实例在运行,一起处理新来的请求。
  • 3)再向原master进程发送WINCH信号,它会逐渐关闭相关work进程,此时原master进程仍保持监听新请求但不会发送至其下work进程,而是交给新的work进程
  • 4)最后等到所有原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(chan os.Signal, 10)signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)for {sig := <-chswitch sig {case syscall.SIGINT, syscall.SIGTERM:// 确保接收到INT/TERM信号时可以触发Golang标准的进程终止行为signal.Stop(ch)a.term(wg)returncase syscall.SIGUSR2:err := a.preStartProcess()if err != nil {a.errors <- err}// 发起新进程if _, err := a.net.StartProcess(); err != nil {a.errors <- err}}}
}

复制当前进程socket连接,发起新进程

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库的例子:
引入github.com/facebookgo/grace/gracehttp

func main() {app := gin.New()// 项目中时候的是gin框架router.Route(app)var server *http.Serverserver = &http.Server{Addr:    ":8080",Handler: app,}gracehttp.Serve(server)
}

利用go build命令编译,生成服务的可执行文件。
然后再用shell封装一下服务命令,生成restat.sh命令文件

#!/bin/shps aux | grep wingo
count=`ps -ef | grep "wingo" | grep -v "grep" | wc -l`
echo ""if [ 0 == $count ]; thenecho "Wingo starting..."sudo ./wingo &echo "Wingo started"
elseecho "Wingo Restarting..."sudo kill -USR2 $(ps -ef | grep "wingo" | grep -v grep | awk '{print $2}')echo "Wingo Restarted"
fisleep 1ps aux | grep wingo

:其中wingo为服务的二进制名称。

于是,便可通过执行./restart.sh命令,达到对服务的热升级目的。

3.2 配置文件热更新

配置文件热更新是指在不停止服务的情况下,重新加载服务所有配置文件。
与3.1服务热升级原理一样,利用用户自定义信号:USR1,即可实现服务的配置文件热更新。

  • 1)服务监听USR1信号
  • 2)服务接收到USR1信号后,停止接受新的连接,等待当前连接停止,重新载入配置文件,重启服务器,从而实现相对平滑的不停服的更改。

主要代码实现:

// LoadAllConf 调用加载配置文件函数
// load为具体加载配置文件方法
func LoadAllConf(load func(bool)) {load(true)listenSIGUSR1(load)
}// listenSIGUSR1 监听SIGUSR1信号
func listenSIGUSR1(f func(bool)) {s := make(chan os.Signal, 1)signal.Notify(s, syscall.SIGUSR1)go func() {for {<-sf(false)log.Println("Reloaded")}}()
}

详细源码可见:https://www.openmymind.net/Go...

利用go build命令编译,生成服务的可执行文件。
然后再用shell封装一下配置重载命令,生成reload.sh命令文件

#!/bin/shps aux | grep wingo
echo ""echo "Wingo Reloading..."
sudo kill -USR1 $(ps -ef | grep "wingo" | grep -v grep | awk '{print $2}')
echo "Wingo Reloaded"
echo ""sleep 1ps aux | grep wingo

于是,便可通过执行./reload.sh命令,达到对服务的配置文件热升级目的。

4、总结

本文主要描述了Golang服务热升级与配置文件热更新原理与主要代码实现,本质上也不是什么新内容,如果之前读过《Unix环境高级编程》,就会觉得很亲切。底层原理基本上是利用了信号这个软件中断机制,在运行中改变常驻进程的行为。

References

https://scalingo.com/articles...
http://kuangchanglang.com/gol...
https://blog.csdn.net/black_O...
https://www.openmymind.net/Go...
https://blog.csdn.net/qq_1543...
https://wrfly.kfd.me/posts/%E...


https://dhexx.cn/news/show-3440606.html

相关文章

leetcode笔记:Pow(x, n)

一. 题目描写叙述 Implement pow(x, n). 二. 题目分析 实现pow(x, n)。即求x的n次幂。 最easy想到的方法就是用递归直接求n个x的乘积&#xff0c;这里须要依据n的值&#xff0c;推断结果是正数还是负数&#xff0c;这样的方法的时间复杂度为O(n)。更加快捷的方法是。使用分治法…

SpringBoot相关:yaml/properties配置文件中实体类属性的提示功能

引入依赖 <!-- Spring Boot提供的配置处理器依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency>…

手把手学IOT服务端API编程[9、批量获取设备状态]|MVP讲堂

作者&#xff1a;阿里云MVP洵云 上节回顾&#xff1a;手把手学IOT服务端API编程[8、按设备名查信息]|MVP讲堂 上节介绍按设备名查信息后,只能查询一个设备的信息&#xff0c;实际使用过程中&#xff0c;如果用户打算一次查询多个设备的状态&#xff0c;服务端的API接口也提供了…

13 策略模式

在阎宏博士的《JAVA与模式》一书中开头是这样描述策略&#xff08;Strategy&#xff09;模式的&#xff1a; 策略模式属于对象的行为模式。其用意是针对一组算法&#xff0c;将每一个算法封装到具有共同接口的独立的类中&#xff0c;从而使得它们可以相互替换。策略模式使得算法…

Spring Boot相关:@ConfigurationProperties@Value读取配置文件的对比

对比点ConfigurationPropertiesValue底层框架Spring BootSpring功能批量注入配置文件中的属性单个注入属性setXX()方法需要不需要复杂类型属性注入支持不支持松散绑定支持不支持JSR303数据校验支持不支持SpEL表达式不支持支持Spring Boot可以读取默认名称的配置文件&#xff0c…

MySQL索引原理以及类型

1、什么是索引 索引是在MySQL的存储引擎上&#xff0c;对其表中的某个列或多列通过一些算法实现可快速查询出结果的一种方法。 2、为什么要有索引 就像一本书要有目录一样&#xff0c;我们可快速通过目录来查找对应的章节得出结果。索引在MySQL中也叫做“键”&#xff0c;是存储…

python之input()、while、title()和upper()

代码举例&#xff1a; # 小应用&#xff1a;问卷调查&#xff0c;记录下调查者名字和回答&#xff0c;询问是否继续。 # 运用数据字典、while、input()、title()和upper()。 responses {} flag True while flag:name input("\n请输入姓名&#xff1a;")answer in…

Spring Boot相关:@Configuration @Bean 将类加入容器

Configuration &#xff1a;标记当前类是配置类 Bean&#xff1a;将当前方法创建类 并放入容器中&#xff0c;相当与bean.xml种的<bean> 通常&#xff1a;Component 等价于 Configuration EnableConfigurationProperties 在springboot 将类放置在容器中 1.常用的注…

常用接口文档模板

接口规范说起来大&#xff0c;其实也就那么几个部分&#xff0c;接口规范、接口管理工具、接口文档编写、开发文档编写。以下将详细介绍&#xff0c;下面进入正文&#xff1a; 接口规范文档具体内容如下&#xff1a;一&#xff1a;协议规范二&#xff1a;域名规范三&#xff1a…

$emit使用报错处理

在Vue SFC模式中&#xff0c;父元素传值给子组件采用&#xff1a;props 子组件传值到父元素需要用到$emit。 当然&#xff0c;可以用store实现&#xff08;这里记录一下遇到的坑&#xff09; 由于没有仔细阅读Vue的文档&#xff0c;遇到了一个最基础的问题&#xff0c;导出程序…