Go和C/C++类型语言的在线热重启

golang,c,c++

Posted by hughnian on October 13, 2020

原因

热重启的这种需求是在很多编译型的服务端代码中经常遇到的情况,如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下的脚本文件。