1. 程式人生 > >不停服更新二進制文件

不停服更新二進制文件

bee execvp unistd.h 信號 cor consola die rap acc

原文

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

不停服更新二進制文件

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


不停服更新二進制文件