不停服更新二进制文件
www.zhaoch.top > 操作系统 > linux
虽然目前分布式架构和keepalived等工具的存在,对于某些特殊的程序,仍然需要不停服更新二进制文件。 这里参考nginx的实现介绍下如何实现这个功能的
痛点
- 已有的链接不中断,直至这个客户端完成业务处理
- 使用新的程序文件处理新的请求
- 新老进程同时存在还要监听相同的端口
思路
- 因为要监听相同端口所以一般都是父子进程
- 常用的fork不能更新二进制文件,需要再通过exec重新加载文件
- 监听相同端口可以通过把句柄作为参数或者环境变量传递给exec族函数继续使用
参考
ngx_exec_new_binary
过程
- 更新可执行程序
- 向主进程发送信号(例如:USR2),立即设置全局变量
- 事件处理循环中老进程检查到全局变量后,不再accept新的连接,已有的连接正常处理
- fork一个子进程,子进程通过exec族函数更新二进制,将listen的句柄通过参数或者环境变量
- 子进程开始accept,新接入的连接由新程序处理
- 老程序持续运行,直至所有的连接都完成业务(可设置超时时间),退出
- 此时只有子程序在运行
示例代码
编辑app_new.cpp 与 app_old.cpp两个文件,app_old.cpp内容如下
#include <stdlib.h>
#include <iostream>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
extern char **environ;
// difference form old and new in binary file
const char *TITLE = "APP-OLD ";
bool g_update = false;
bool g_stop = false;
void signal_handler(int sig)
{
cout << "signal_handler" << sig << endl;
if (sig == SIGUSR2) {
g_update = true;
}
g_stop = true;
}
void update_binary(char* argv[], int fd)
{
if (fork() != 0) {
return;
}
char *fdstr = new char[100];
snprintf(fdstr, 100, "FD=%d", fd);
int n;
for (n = 0; environ[n]; n++);
// copy environ
char **env = new char*[n + 1];
for (int i = 0; i < n - 1; i++) {
env[i] = environ[i];
}
// app fd into environ
env[n - 1] = fdstr;
env[n] = NULL;
// importent exec app_new
execvpe("./app_new", argv, env);
}
int main(int argc, char* argv[])
{
int fd = -1;
g_stop = false;
g_update = false;
cout << TITLE << getpid() << endl;
char *oldfd = getenv("FD");
if (oldfd) {
fd = atoi(oldfd);
} else {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
fd = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0);
if (fd < 0) {
return 1;
}
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 || listen(fd, 4096) < 0) {
close(fd);
return 1;
}
if (signal(SIGUSR2, signal_handler) == SIG_ERR
|| signal(SIGTERM, signal_handler) == SIG_ERR
|| signal(SIGINT, signal_handler) == SIG_ERR) {
close(fd);
return 1;
}
}
while (!g_stop) {
cout << TITLE << getpid() << " fd=" << fd << endl;
sleep(1);
}
if (g_update) {
update_binary(argv, fd);
}
// semulate waiting for the all connection diedown
for (int i = 0; i < 10; i++) {
cout << TITLE << getpid() << " fd=" << fd << endl;
sleep(1);
}
cout << TITLE << "end" << endl;
return 0;
}
app_new.cpp仅仅TITLE取名不同,用于区分新旧二进制文件
diff app_old.cpp app_new.cpp
< const char *TITLE = "APP-OLD ";
---
> const char *TITLE = "APP-NEW ";
编译两个文件用于对比测试
g++ app_old.cpp -o app_old
g++ app_new.cpp -o app_new
执行结果
./app_old
APP-OLD 8521
APP-OLD 8521 fd=3
APP-OLD 8521 fd=3
APP-OLD 8521 fd=3
signal_handler12 <- 执行命令 kill -USR2 8521
APP-OLD 8521 fd=3 <- 新旧并行
APP-NEW 8524
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD 8521 fd=3
APP-NEW 8524 fd=3
APP-OLD end <- 旧进程退出,只剩新进程
APP-NEW 8524 fd=3
APP-NEW 8524 fd=3
APP-NEW 8524 fd=3
APP-NEW 8524 fd=3
APP-NEW 8524 fd=3
APP-NEW 8524 fd=3
The End
- My github location
- View Source of this website GhostZch.github.io
- Commit issues to discuss with me and others