优雅地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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章