優雅地reload

今天做項目,借鑑nginx的代碼,做了一個優雅的重啓,在重啓過程中不斷服務,保持系統可用性。具體的過程如下
1. 註冊信號量
程序在運行過程中,當kill -s SIGUSR1 pid的shell語句執行時,程序接收到該信號,執行sig_reload函數, 將全局變量reload = 1

static void sig_reload(int signo)
{
    reload = 1;
}
struct sigaction sa_reload;
sa_reload.sa_handler = sig_reload;
sigaction(SIGUSR1, &sa_reload, NULL);

2. 主程序邏輯
我的主進程在while的循環反覆執行, 當reload = 1 的時候,先將reload置回0,以免重複調用reload,接着執行CoreExecNewBin的函數, 這個函數接下去會講。它的主要功能是創建子進程,父進程及時退出,子進程執行新的bin文件。
父進程在退出時,使stop= 1 從而使得父進程stop,從而執行停止的邏輯,釋放資源。

while (!stop)
    {
        sleep(10);
        int ret;
        if (reload == 1)
        {
            reload = 0;
            log_debug("reloading");
            ret = CoreExecNewBin();
            if (ret != 0)
            {
                log_error("fail in reloading");
                continue;
            }

            log_debug("reload success");
            stop = 1;
        }

    }

3.CoreExecNewBin函數
這裏關鍵的CoreExecNewBin函數來了
它先fork進程,根據pid判斷父子進程,父進程直接退出, 子進程獲取全局變量g_szenv,然後複製給envp,envp作爲執行新的bin文件的環境變量

static int CoreExecNewBin()
{
    char *fds = NULL;
    int pid = fork();
    switch (pid) {
    case -1:
        log_error("fork in CoreExecNewBin  got error");
        return -1;

    case 0: /* child */
        break;

    default: /* parent */
        return 0;
    }

    char *envp[] = { NULL, NULL }; 
    envp[0] = g_szenv;
    log_error("fork env %s", g_szenv);
    execve(g_argv[0], g_argv , envp);
}

如上可能,實現了功能,可是有兩個疑問,
1.如果父進程綁定了一個端口,那麼這個端口已經被佔用,子進程綁定同樣的端口時fail,怎麼辦?
2.如上的環境變量起到什麼作用。

那麼就扯到環境變量的問題上了,本文的環境變量記錄了被佔用的fd,從fd可以獲取端口號,從而做下述邏輯
我在綁定端口的時候,有4步要走:


1. 從環境變量讀取當前的環境變量,下面是讀取的代碼,
首先用getenv獲取得到char類型的環境變量,如果爲null,則表示沒有端口被佔用,那麼返回0.
然後解析環境變量的值,從它獲得綁定的fd
UnresolveDesc通過得到的fd,獲得與其綁定的addr,如果這個addr與現在想要綁定的addr一樣,則dup一個fd1,返回fd1.

    char *p, *q;
    int sock;
    char *inherited;
    inherited = getenv(NC_ENV_FDS);

    //log_debug("sock addr %s", UnresolveDesc(sock));

    char address[NI_MAXHOST + NI_MAXSERV];

    if (inherited == NULL) {
        return 0;
    }


    strncpy(address, listen_address, sizeof(address));
    log_debug("address %s", address);

    for (p = inherited, q = inherited; *p; p++) {
        if (*p == ';') {
            sock = cpa_atoi(q, p - q);
            //log_debug("listen_address %s",  UnresolveDesc(sock));
            if (strcmp(address, UnresolveDesc(sock)) == 0) {
                log_debug("get inherited socket %d for '%s' from '%s'", sock,
                    address, inherited);
                sock = dup(sock);
                log_debug("dup inherited socket as %d", sock);
                return sock;
            }
            q = p + 1;
        }
    }

    return 0;




char *UnresolveDesc(int sd)
{

    struct sockaddr *addr;
    socklen_t addrlen;
    int status;

    addr = (struct sockaddr *)malloc(sizeof(struct sockaddr));
    status = getsockname(sd, addr, &addrlen);
    if (status < 0) {
        return "unknown";
    }
    return UnresolveAddr(addr, addrlen);
}


2. 判斷通過環境變量獲得的fd值,如果fd<= 0 , 新綁定一個fd2,否則fd值作爲現在fd使用

if (fd <= 0)
    {
        if ((fd = SockBind(&m_Addr, blog, 0, 0, 1/*reuse*/, 1/*nodelay*/, 1/*defer_accept*/)) == -1)
        {
            return -1;
        }
    }


3. 通過環境變量,關閉已經打開的fd,從而讓父進程的fd關閉

int CleanSocketEnv()
{
    int sock = 0;
    char *inherited;
    char *p, *q;

    inherited = getenv(NC_ENV_FDS);
    if (inherited == NULL) {
        return 0 ;
    }

    for (p = inherited, q = inherited; *p; p++) {
        if (*p == ';') {
            sock = cpa_atoi(q, p - q);
            close(sock);
            q = p + 1;
        }
    }
    return 0;
}


4. 將現在佔用的fd寫入全局變量,作爲下次的reload的環境變量

    int len = 0;
    memcpy(g_szenv, NC_ENV_FDS, strlen(NC_ENV_FDS));
    len = strlen(NC_ENV_FDS);
    memcpy(g_szenv + len, "=", strlen("="));
    len += strlen("=");
    sprintf(g_szenv + len, "%d;", fd);
    log_debug("global env is %s", g_szenv);


對了比較重要的一點是: 在accept 新的fd的時候要設置,不然在子進程會繼承已經連接的fd,從而導致殭屍連接

int status = fcntl(newfd, F_SETFD, FD_CLOEXEC);
發佈了46 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章