原因
热重启的这种需求是在很多编译型的服务端代码中经常遇到的情况,如JAVA,C/C++,Golang等,目前JAVA有自己一套成熟的热重启方案,又方便又好用,相比而言,C/C++的
热重启有点麻烦,需要自己写一些代码。Golang的热重启我了解到的大部分情况都是基于http的服务,基于http的服务热重启,Golang有着方便的解决方案,
server *http.Server server.Shutdown()
但是我这边大部分是用Golang做socket长连接服务端,很类似C/C++干的事,发现这方面的Golang热重启似乎和C/C++一样。
归纳、总结
这里的归纳、总结主要是针对socket长连接服务端的热重启,以C/C++的热重启为列。主要原理是在signal mask中取消SA_RESTART的设置,当有请求进入时,中断accept的运行, 并调用exec函数启动新打包的程序,在新的进程中用accept函数接收新的请求,监控socket描述符,原程序在处理完客户端所有之前的请求后就会终止,新进程将取代旧进程继续运行。
具体整法
我们的设想:
1.不关闭现有连接
2.socket能正常的接收客户端的请求并把它们缓存,后面仍然会处理这些请求
3.新的进程取代旧的进程
Golang的写法
//定义channel接受signal
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGHUP, syscall.SIGTERM)
for sig := range signals {
if sig == syscall.SIGTERM {
go func() {
//处理信号, 做主要的重启逻辑
}{}
}
...
}
C/C++的写法
struct sigaction action;
action.sa_handler = Handler; //处理信号,做主要的重启逻辑
sigemptyset(&action.sa_mask);
if (sigaction(SIGALRM, &action, NULL) < 0) {
syslog(LOG_ERR, "set signal error: %s(errno: %d)\n", strerror(errno), errno);
} else {
syslog(LOG_INFO, "set signal ok...");
}
不管Golang版的还是C/C++版的重启逻辑里都使用exec
函数族,可能很多朋友会想到用fork函数,但是fork的作用是复制出一个与原进程一样的子进程,它们完成的是同样的操作。但是,在更新了程序,或者打了新程序包后
我们想要重新启动时,fork是无法完成的,这就需要exec函数族。exec函数族提供可以在一个程序中启动另一个程序的执行方法,可以根据指定的文件名和目录名找到可执行文件,并用它
取代原调用进程数据段,代码段,堆栈段。在执行完成后,原调用进程的内容除进程号外,其他全部被新进程替代。这里exec可以执行的文件,可以是可执行文件也可以是二进制文件,
也可以是在LINUX下的脚本文件。