Go 实现热重启的详细介绍实现热重启的详细介绍
主要介绍了Go 实现热重启的详细介绍,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价
值,需要的朋友可以参考下
最近在优化公司框架 trpc 时发现了一个热重启相关的问题,优化之余也总结沉淀下,对 go 如何实现热重启这方面的内容做一
个简单的梳理。
1.什么是热重启?什么是热重启?
热重启(Hot Restart),是一项保证服务可用性的手段。它允许服务重启期间,不中断已经建立的连接,老服务进程不再接
受新连接请求,新连接请求将在新服务进程中受理。对于原服务进程中已经建立的连接,也可以将其设为读关闭,等待平滑处
理完连接上的请求及连接空闲后再行退出。通过这种方式,可以保证已建立的连接不中断,连接上的事务(请求、处理、响
应)可以正常完成,新的服务进程也可以正常接受连接、处理连接上的请求。当然,热重启期间进程平滑退出涉及到的不止是
连接上的事务,也有消息服务、自定义事务需要关注。
这是我理解的热重启的一个大致描述。热重启现在还有没有存在的必要?我的理解是看场景。
以后台开发为例,假如运维平台有能力在服务升级、重启时自动踢掉流量,服务就绪后又自动加回流量,假如能够合理预估服
务 QPS、请求处理时长,那么只要配置一个合理的停止前等待时间,是可以达到类似热重启的效果的。这样的话,在后台服
务里面支持热重启就显得没什么必要。但是,如果我们开发一个微服务框架,不能对将来的部署平台、环境做这种假设,也有
可能使用方只是部署在一两台物理机上,也没有其他的负载均衡设施,但不希望因为重启受干扰,热重启就很有必要。当然还
有一些更复杂、要求更苛刻的场景,也需要热重启的能力。
热重启是比较重要的一项保证服务质量的手段,还是值得了解下的,这也是本文介绍的初衷。
2.如何实现热重启?如何实现热重启?
如何实现热重启,这里其实不能一概而论,要结合实际的场景来看(比如服务编程模型、对可用性要求的高低等)。大致的实
现思路,可以先抛一下。
一般要实现热重启,大致要包括如下步骤:
首先,要让老进程,这里称之为父进程了,先要 fork 出一个子进程来代替它工作;
然后,子进程就绪之后,通知父进程,正常接受新连接请求、处理连接上收到的请求;
再然后,父进程处理完已建立连接上的请求后、连接空闲后,平滑退出。
听上去是挺简单的...
2.1.认识认识 fork
大家都知道fork()系统调用,父进程调用 fork 会创建一个进程副本,代码中还可以通过 fork 返回值是否为 0 来区分是子进程还
是父进程。
int main(char **argv, int argc) {
pid_t pid = fork();
if (pid == 0) {
printf("i am child process");
} else {
printf("i am parent process, i have a child process named %d", pid);
}
}
可能有些开发人员不知道 fork 的实现原理,或者不知道 fork 返回值为什么在父子进程中不同,或者不知道如何做到父子进程
中返回值不同……了解这些是要有点知识积累的。
2.2.返回值返回值
简单概括下,ABI 定义了进行函数调用时的一些规范,如何传递参数,如何返回值等等,以 x86 为例,如果返回值是 rax 寄存
器能够容的一般都是通过 rax 寄存器返回的。
如果 rax 寄存器位宽无法容纳下的返回值呢?也简单,编译器会安插些指令来完成这些神秘的操作,具体是什么指令,就跟语
言编译器实现相关了。
c 语言,可能会将返回值的地址,传递到 rdi 或其他寄存器,被调函数内部呢,通过多条指令将返回值写入 rdi 代指的内
存区;
c 语言,也可能在被调函数内部,用多个寄存器 rax,rdx...一起暂存返回结果,函数返回时再将多个寄存器的值赋值到变
量中;
也可能会像 golang 这样,通过栈内存来返回;
2.3.fork 返回值返回值