不停服更新二进制文件

 

原文 

http://www.zhaoch.top/操作系统/linux/不停服更新二进制文件.html

不停服更新二进制文件

www.zhaoch.top > 操作系统 > linux

img

虽然目前分布式架构和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


原文地址:https://www.cnblogs.com/GhostZCH/p/9637106.html