• Nginx(七):location的使用以及nginx优雅停机原理


      上一篇中,我们了解了如何nginx的配置原则及解析框架,以及解析location配置的具体实现,相信大家对该部分已经有了比较深刻的认识。

      本篇,我们进一步来了解下,解析之后的配置,如何应用到实际中的吧。当然,我们只讲解 location 的查找过程。

    1. location的接入流程

      在nginx的前几篇中,我们已经了解了,nginx对于网络的请求接入过程,是一个基于事件的io模型,这是其高性能的根本。io接入之后,再通过 accept -> read -> init_http -> wait_request -> process_request_line -> process_request_header -> process_request ... 的过程,然后就是具体的处理实现。

      而对于location的处理,则是在 ngx_http_handler() 接入之后的分发工作。

    // http/ngx_http_core_module.c    
    void
    ngx_http_handler(ngx_http_request_t *r)
    {
        ngx_http_core_main_conf_t  *cmcf;
    
        r->connection->log->action = NULL;
    
        if (!r->internal) {
            switch (r->headers_in.connection_type) {
            case 0:
                r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
                break;
    
            case NGX_HTTP_CONNECTION_CLOSE:
                r->keepalive = 0;
                break;
    
            case NGX_HTTP_CONNECTION_KEEP_ALIVE:
                r->keepalive = 1;
                break;
            }
    
            r->lingering_close = (r->headers_in.content_length_n > 0
                                  || r->headers_in.chunked);
            r->phase_handler = 0;
    
        } else {
            cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
            r->phase_handler = cmcf->phase_engine.server_rewrite_index;
        }
    
        r->valid_location = 1;
    #if (NGX_HTTP_GZIP)
        r->gzip_tested = 0;
        r->gzip_ok = 0;
        r->gzip_vary = 0;
    #endif
    
        r->write_event_handler = ngx_http_core_run_phases;
        ngx_http_core_run_phases(r);
    }
    
    // http/ngx_http_core_module.c
    void
    ngx_http_core_run_phases(ngx_http_request_t *r)
    {
        ngx_int_t                   rc;
        ngx_http_phase_handler_t   *ph;
        ngx_http_core_main_conf_t  *cmcf;
    
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    
        ph = cmcf->phase_engine.handlers;
        // 依次遍历各checker, 直到有一个可以处理
        while (ph[r->phase_handler].checker) {
    
            rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
    
            if (rc == NGX_OK) {
                return;
            }
        }
    }
    
    ngx_int_t
    ngx_http_core_find_config_phase(ngx_http_request_t *r,
        ngx_http_phase_handler_t *ph)
    {
        u_char                    *p;
        size_t                     len;
        ngx_int_t                  rc;
        ngx_http_core_loc_conf_t  *clcf;
    
        r->content_handler = NULL;
        r->uri_changed = 0;
        // 查找location的实现,接入查找流程
        rc = ngx_http_core_find_location(r);
    
        if (rc == NGX_ERROR) {
            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_OK;
        }
    
        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    
        if (!r->internal && clcf->internal) {
            ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
            return NGX_OK;
        }
    
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "using configuration "%s%V"",
                       (clcf->noname ? "*" : (clcf->exact_match ? "=" : "")),
                       &clcf->name);
        // 将查找到的信息,更新到当前会话中
        ngx_http_update_location_config(r);
    
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http cl:%O max:%O",
                       r->headers_in.content_length_n, clcf->client_max_body_size);
    
        if (r->headers_in.content_length_n != -1
            && !r->discard_body
            && clcf->client_max_body_size
            && clcf->client_max_body_size < r->headers_in.content_length_n)
        {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "client intended to send too large body: %O bytes",
                          r->headers_in.content_length_n);
    
            r->expect_tested = 1;
            (void) ngx_http_discard_request_body(r);
            ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE);
            return NGX_OK;
        }
    
        if (rc == NGX_DONE) {
            ngx_http_clear_location(r);
    
            r->headers_out.location = ngx_list_push(&r->headers_out.headers);
            if (r->headers_out.location == NULL) {
                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return NGX_OK;
            }
    
            r->headers_out.location->hash = 1;
            ngx_str_set(&r->headers_out.location->key, "Location");
    
            if (r->args.len == 0) {
                r->headers_out.location->value = clcf->name;
    
            } else {
                len = clcf->name.len + 1 + r->args.len;
                p = ngx_pnalloc(r->pool, len);
    
                if (p == NULL) {
                    ngx_http_clear_location(r);
                    ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                    return NGX_OK;
                }
    
                r->headers_out.location->value.len = len;
                r->headers_out.location->value.data = p;
    
                p = ngx_cpymem(p, clcf->name.data, clcf->name.len);
                *p++ = '?';
                ngx_memcpy(p, r->args.data, r->args.len);
            }
    
            ngx_http_finalize_request(r, NGX_HTTP_MOVED_PERMANENTLY);
            return NGX_OK;
        }
    
        r->phase_handler++;
        return NGX_AGAIN;
    }

      以上就是location的接入过程,主要就是接受http_handler的分配处理,具体如何进行查找.即 location 的处理是在nginx读取完所有的请求体之后,依次处理的其中一个步骤。我们下节再看。

    2. location的查找过程

      上节看到,http模块在处理location时,使用一个ngx_http_core_find_location()封装好了其查找过程。想想其实现,应该差不多就是依次匹配原来解析出的信息,当然这里面应该是有各种优先级的体现。

    // http/ngx_http_core_module.c
    static ngx_int_t
    ngx_http_core_find_location(ngx_http_request_t *r)
    {
        ngx_int_t                  rc;
        ngx_http_core_loc_conf_t  *pclcf;
    #if (NGX_PCRE)
        ngx_int_t                  n;
        ngx_uint_t                 noregex;
        ngx_http_core_loc_conf_t  *clcf, **clcfp;
    
        noregex = 0;
    #endif
    
        pclcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
        // 委托给 static_location 查找
        rc = ngx_http_core_find_static_location(r, pclcf->static_locations);
    
        if (rc == NGX_AGAIN) {
    
    #if (NGX_PCRE)
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    
            noregex = clcf->noregex;
    #endif
    
            /* look up nested locations */
            // NGX_AGAIN, 则进行多次嵌套查找,以保证最佳匹配
            rc = ngx_http_core_find_location(r);
        }
        // 匹配成功,则返回,主要是针对'='的匹配
        if (rc == NGX_OK || rc == NGX_DONE) {
            return rc;
        }
    
        /* rc == NGX_DECLINED or rc == NGX_AGAIN in nested location */
    
    #if (NGX_PCRE)
    
        if (noregex == 0 && pclcf->regex_locations) {
            // 正则匹配, 只要存在正则配置,那么正则匹配都会运行
            // 相比于字符匹配,正则匹配性能更差
            // 所以,当你的正则配置越多,则查找效率则必然越差,没必要配置正则就不要配了
            for (clcfp = pclcf->regex_locations; *clcfp; clcfp++) {
    
                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                               "test location: ~ "%V"", &(*clcfp)->name);
                // 只要有一个正则匹配,则返回该配置
                n = ngx_http_regex_exec(r, (*clcfp)->regex, &r->uri);
    
                if (n == NGX_OK) {
                    // 符合正则表达式,则loc_conf应用上去
                    r->loc_conf = (*clcfp)->loc_conf;
    
                    /* look up nested locations */
                    // 与正常匹配相反,正则匹配是在一个匹配成功后,再进入嵌套查询
                    rc = ngx_http_core_find_location(r);
    
                    return (rc == NGX_ERROR) ? rc : NGX_OK;
                }
    
                if (n == NGX_DECLINED) {
                    continue;
                }
    
                return NGX_ERROR;
            }
        }
    #endif
    
        return rc;
    }
    
    // 非正则location 匹配查找过程
    /*
     * NGX_OK       - exact match
     * NGX_DONE     - auto redirect
     * NGX_AGAIN    - inclusive match
     * NGX_DECLINED - no match
     */
    
    static ngx_int_t
    ngx_http_core_find_static_location(ngx_http_request_t *r,
        ngx_http_location_tree_node_t *node)
    {
        u_char     *uri;
        size_t      len, n;
        ngx_int_t   rc, rv;
    
        len = r->uri.len;
        uri = r->uri.data;
    
        rv = NGX_DECLINED;
    
        for ( ;; ) {
    
            if (node == NULL) {
                // node为null时,代表匹配完成,此时将返回之前最匹配的一个 loc_conf
                return rv;
            }
    
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "test location: "%*s"",
                           (size_t) node->len, node->name);
            // 取小值进行比较
            n = (len <= (size_t) node->len) ? len : node->len;
            // 包含性检查
            rc = ngx_filename_cmp(uri, node->name, n);
    
            if (rc != 0) {
                // 二叉树查找过程, 小于0在左,大于0在右
                node = (rc < 0) ? node->left : node->right;
    
                continue;
            }
            // 相等的情况有两种,第1种是本次uri 长于当前配置的location
            // 第2种是本次uri 短于当前配置的location
            // 针对第1种情况,是属于一种完全匹配的
            if (len > (size_t) node->len) {
    
                if (node->inclusive) {
    
                    r->loc_conf = node->inclusive->loc_conf;
                    rv = NGX_AGAIN;
                    // 向前迭代匹配
                    node = node->tree;
                    uri += n;
                    len -= n;
    
                    continue;
                }
    
                /* exact only */
    
                node = node->right;
    
                continue;
            }
            // 此为 uri >= location配置的情况
            if (len == (size_t) node->len) {
    
                if (node->exact) {
                    r->loc_conf = node->exact->loc_conf;
                    return NGX_OK;
    
                } else {
                    // 包含性匹配成功
                    r->loc_conf = node->inclusive->loc_conf;
                    return NGX_AGAIN;
                }
            }
    
            /* len < node->len */
            // 以'/'结尾的配置, 比uri 多一个值
            if (len + 1 == (size_t) node->len && node->auto_redirect) {
    
                r->loc_conf = (node->exact) ? node->exact->loc_conf:
                                              node->inclusive->loc_conf;
                rv = NGX_DONE;
            }
    
            node = node->left;
        }
    }
    // 正则location查找
    // http/ngx_http_variable.c
    ngx_int_t
    ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re, ngx_str_t *s)
    {
        ngx_int_t                   rc, index;
        ngx_uint_t                  i, n, len;
        ngx_http_variable_value_t  *vv;
        ngx_http_core_main_conf_t  *cmcf;
    
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
    
        if (re->ncaptures) {
            len = cmcf->ncaptures;
    
            if (r->captures == NULL || r->realloc_captures) {
                r->realloc_captures = 0;
    
                r->captures = ngx_palloc(r->pool, len * sizeof(int));
                if (r->captures == NULL) {
                    return NGX_ERROR;
                }
            }
    
        } else {
            len = 0;
        }
        // 正则匹配, pcre_exec
        rc = ngx_regex_exec(re->regex, s, r->captures, len);
        // 无匹配返回 NGX_DECLINED
        if (rc == NGX_REGEX_NO_MATCHED) {
            return NGX_DECLINED;
        }
    
        if (rc < 0) {
            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                          ngx_regex_exec_n " failed: %i on "%V" using "%V"",
                          rc, s, &re->name);
            return NGX_ERROR;
        }
    
        for (i = 0; i < re->nvariables; i++) {
    
            n = re->variables[i].capture;
            index = re->variables[i].index;
            vv = &r->variables[index];
    
            vv->len = r->captures[n + 1] - r->captures[n];
            vv->valid = 1;
            vv->no_cacheable = 0;
            vv->not_found = 0;
            vv->data = &s->data[r->captures[n]];
    
    #if (NGX_DEBUG)
            {
            ngx_http_variable_t  *v;
    
            v = cmcf->variables.elts;
    
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http regex set $%V to "%v"", &v[index].name, vv);
            }
    #endif
        }
    
        r->ncaptures = rc * 2;
        r->captures_data = s->data;
    
        return NGX_OK;
    }

      以上就是整个nginx非正则的location的匹配过程,可以看到其核心是使用一个有序二叉树,进行的快速查找过程,以尽可能多的匹配为准。即 /api/a/b, /api/a 这两个同时匹配的情况,则会选择匹配最多的 /api/a/b 配置。借助于二叉树的高效数据结构,其复杂度非常常低,O(lgn). 当然,这个快速查找是依赖于其在解析配置时的良好数据维护。

    // http/ngx_http_core_module.c    
    ngx_int_t
    ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations,
        ngx_http_core_loc_conf_t *clcf)
    {
        ngx_http_location_queue_t  *lq;
    
        if (*locations == NULL) {
            *locations = ngx_palloc(cf->temp_pool,
                                    sizeof(ngx_http_location_queue_t));
            if (*locations == NULL) {
                return NGX_ERROR;
            }
    
            ngx_queue_init(*locations);
        }
    
        lq = ngx_palloc(cf->temp_pool, sizeof(ngx_http_location_queue_t));
        if (lq == NULL) {
            return NGX_ERROR;
        }
    
        if (clcf->exact_match
    #if (NGX_PCRE)
            || clcf->regex
    #endif
            || clcf->named || clcf->noname)
        {
            lq->exact = clcf;
            lq->inclusive = NULL;
    
        } else {
            lq->exact = NULL;
            lq->inclusive = clcf;
        }
    
        lq->name = &clcf->name;
        lq->file_name = cf->conf_file->file.name.data;
        lq->line = cf->conf_file->line;
        // 虽然看不懂在做什么,但是感觉很厉害的样子
        ngx_queue_init(&lq->list);
    
        ngx_queue_insert_tail(*locations, &lq->queue);
    
        return NGX_OK;
    }

      查找过程分解完毕,和nginx的官方文档描述自然是一致的。优先匹配 '=' 类的配置,其次会按照最长配置为原则查找,但正则配置的优先级高于字符的匹配,没必要不要随意配置正则,因为正则会每次都全量查找。

      不过,因为这些所有的操作都是直接基于内存的,并没有io类的重量级操作,即使配置了几百上千个location规则,性能也并不会有太大影响。但我们应该要其根本原因。

      以上所说,仅是location的最外部匹配过程,但location本身是一个块级的配置,它的内部又有非常多的配置规则,这又要细化到其内部解析了。

    3. nginx优雅停机原理

      nginx进程的控制与前面location使用有什么关系?? 当然没有关系了,只是想着也简单,顺便就一起讲讲了。

      一般的应用进程管理,只需使用系统提供的相关命令即可完成,比如 kill -9 <pid> 。 而nginx专门提供了一些用于控制其进程的方法,原因是其需要更优雅地处理各种意外情况,如果直接使用系统的控制命令,会导致非常多的边界问题,从而使得nginx本身不再完美。大家需要为这一个个的边界问题,伤透了脑筋,这样也许它就不再那么流行了。

          要实现优雅停机,最本质的工作是要实现资源的优雅管理工作,但还有很重要的点是如何实现这管理工作的调用。来看看nginx如何处理:

    // core/ngx_cycle.c
    ngx_int_t
    ngx_signal_process(ngx_cycle_t *cycle, char *sig)
    {
        ssize_t           n;
        ngx_pid_t         pid;
        ngx_file_t        file;
        ngx_core_conf_t  *ccf;
        u_char            buf[NGX_INT64_LEN + 2];
    
        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "signal process started");
    
        ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
    
        ngx_memzero(&file, sizeof(ngx_file_t));
    
        file.name = ccf->pid;
        file.log = cycle->log;
    
        file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
                                NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);
    
        if (file.fd == NGX_INVALID_FILE) {
            ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                          ngx_open_file_n " "%s" failed", file.name.data);
            return 1;
        }
    
        n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);
    
        if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_close_file_n " "%s" failed", file.name.data);
        }
    
        if (n == NGX_ERROR) {
            return 1;
        }
    
        while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }
    
        pid = ngx_atoi(buf, ++n);
    
        if (pid == (ngx_pid_t) NGX_ERROR) {
            ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
                          "invalid PID number "%*s" in "%s"",
                          n, buf, file.name.data);
            return 1;
        }
        // 以上解析pid, 验证有效性, 下面进行实际进程管控
        return ngx_os_signal_process(cycle, sig, pid);
    
    }
    
    // os/unix/ngx_process.c
    ngx_int_t
    ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
    {
        ngx_signal_t  *sig;
        // 遍历所有控制指令,转换成系统的控制标识
        for (sig = signals; sig->signo != 0; sig++) {
            if (ngx_strcmp(name, sig->name) == 0) {
                if (kill(pid, sig->signo) != -1) {
                    return 0;
                }
    
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "kill(%P, %d) failed", pid, sig->signo);
            }
        }
    
        return 1;
    }
    // 控制命令配置表
    ngx_signal_t  signals[] = {
        { ngx_signal_value(NGX_RECONFIGURE_SIGNAL),    // SIG##1
          "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),    // SIG#HUP
          "reload",
          ngx_signal_handler },
    
        { ngx_signal_value(NGX_REOPEN_SIGNAL),        // SIG##30
          "SIG" ngx_value(NGX_REOPEN_SIGNAL),        // SIG#USR1
          "reopen",
          ngx_signal_handler },
    
        { ngx_signal_value(NGX_NOACCEPT_SIGNAL),    // SIG##28
          "SIG" ngx_value(NGX_NOACCEPT_SIGNAL),        // SIG#WINCH
          "",
          ngx_signal_handler },
    
        { ngx_signal_value(NGX_TERMINATE_SIGNAL),    // SIG##15
          "SIG" ngx_value(NGX_TERMINATE_SIGNAL),    // SIG#TERM
          "stop",
          ngx_signal_handler },
    
        { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),    // SIG##3
          "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),        // SIG#QUIT
          "quit",
          ngx_signal_handler },
    
        { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),    // ##31
          "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),    // USR2
          "",
          ngx_signal_handler },
    
        { SIGALRM, "SIGALRM", "", ngx_signal_handler },
    
        { SIGINT, "SIGINT", "", ngx_signal_handler },
    
        { SIGIO, "SIGIO", "", ngx_signal_handler },
    
        { SIGCHLD, "SIGCHLD", "", ngx_signal_handler },
    
        { SIGSYS, "SIGSYS, SIG_IGN", "", NULL },
    
        { SIGPIPE, "SIGPIPE, SIG_IGN", "", NULL },
    
        { 0, NULL, "", NULL }
    };
    // 以上命令的注册过程如下, 以便在响应时可处理
    ngx_int_t
    ngx_init_signals(ngx_log_t *log)
    {
        ngx_signal_t      *sig;
        struct sigaction   sa;
    
        for (sig = signals; sig->signo != 0; sig++) {
            ngx_memzero(&sa, sizeof(struct sigaction));
    
            if (sig->handler) {
                sa.sa_sigaction = sig->handler;
                sa.sa_flags = SA_SIGINFO;
    
            } else {
                sa.sa_handler = SIG_IGN;
            }
    
            sigemptyset(&sa.sa_mask);
            if (sigaction(sig->signo, &sa, NULL) == -1) {
    #if (NGX_VALGRIND)
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                              "sigaction(%s) failed, ignored", sig->signame);
    #else
                ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                              "sigaction(%s) failed", sig->signame);
                return NGX_ERROR;
    #endif
            }
        }
    
        return NGX_OK;
    }

      以上控制指定的处理器,几乎都被设置为 ngx_signal_handler,即当nginx进程收到控制信号,将会该用该方法进行响应。

    // 其处理逻辑如下: (主要就是根据当前进程的不同角色和控制信号,设置相应标识)
    static void
    ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
    {
        char            *action;
        ngx_int_t        ignore;
        ngx_err_t        err;
        ngx_signal_t    *sig;
    
        ignore = 0;
    
        err = ngx_errno;
    
        for (sig = signals; sig->signo != 0; sig++) {
            if (sig->signo == signo) {
                break;
            }
        }
    
        ngx_time_sigsafe_update();
    
        action = "";
        // 根据当前进程的类型,做不一样的处理逻辑
        switch (ngx_process) {
        // master进程处理
        case NGX_PROCESS_MASTER:
        case NGX_PROCESS_SINGLE:
            // 根据控制标识,设置相应变量
            // 该变量将被主循环服务读取到
            switch (signo) {
            case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
                ngx_quit = 1;
                action = ", shutting down";
                break;
    
            case ngx_signal_value(NGX_TERMINATE_SIGNAL):
            case SIGINT:
                ngx_terminate = 1;
                action = ", exiting";
                break;
    
            case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
                if (ngx_daemonized) {
                    ngx_noaccept = 1;
                    action = ", stop accepting connections";
                }
                break;
    
            case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
                ngx_reconfigure = 1;
                action = ", reconfiguring";
                break;
    
            case ngx_signal_value(NGX_REOPEN_SIGNAL):
                ngx_reopen = 1;
                action = ", reopening logs";
                break;
    
            case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
                if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {
    
                    /*
                     * Ignore the signal in the new binary if its parent is
                     * not changed, i.e. the old binary's process is still
                     * running.  Or ignore the signal in the old binary's
                     * process if the new binary's process is already running.
                     */
    
                    action = ", ignoring";
                    ignore = 1;
                    break;
                }
    
                ngx_change_binary = 1;
                action = ", changing binary";
                break;
    
            case SIGALRM:
                ngx_sigalrm = 1;
                break;
    
            case SIGIO:
                ngx_sigio = 1;
                break;
    
            case SIGCHLD:
                ngx_reap = 1;
                break;
            }
    
            break;
        // worker 收到控制请求
        case NGX_PROCESS_WORKER:
        case NGX_PROCESS_HELPER:
            // 同样设置相当标识变量,在主循环中进行处理响应
            switch (signo) {
            case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
                if (!ngx_daemonized) {
                    break;
                }
                ngx_debug_quit = 1;
                /* fall through */
            case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
                ngx_quit = 1;
                action = ", shutting down";
                break;
    
            case ngx_signal_value(NGX_TERMINATE_SIGNAL):
            case SIGINT:
                ngx_terminate = 1;
                action = ", exiting";
                break;
    
            case ngx_signal_value(NGX_REOPEN_SIGNAL):
                ngx_reopen = 1;
                action = ", reopening logs";
                break;
    
            case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
            case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
            case SIGIO:
                action = ", ignoring";
                break;
            }
    
            break;
        }
    
        if (siginfo && siginfo->si_pid) {
            ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                          "signal %d (%s) received from %P%s",
                          signo, sig->signame, siginfo->si_pid, action);
    
        } else {
            ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                          "signal %d (%s) received%s",
                          signo, sig->signame, action);
        }
    
        if (ignore) {
            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
                          "the changing binary signal is ignored: "
                          "you should shutdown or terminate "
                          "before either old or new binary's process");
        }
        // 等待子进程完成
        if (signo == SIGCHLD) {
            ngx_process_get_status();
        }
    
        ngx_set_errno(err);
    }
    
    static void
    ngx_process_get_status(void)
    {
        int              status;
        char            *process;
        ngx_pid_t        pid;
        ngx_err_t        err;
        ngx_int_t        i;
        ngx_uint_t       one;
    
        one = 0;
    
        for ( ;; ) {
            pid = waitpid(-1, &status, WNOHANG);
    
            if (pid == 0) {
                return;
            }
    
            if (pid == -1) {
                err = ngx_errno;
    
                if (err == NGX_EINTR) {
                    continue;
                }
    
                if (err == NGX_ECHILD && one) {
                    return;
                }
    
                /*
                 * Solaris always calls the signal handler for each exited process
                 * despite waitpid() may be already called for this process.
                 *
                 * When several processes exit at the same time FreeBSD may
                 * erroneously call the signal handler for exited process
                 * despite waitpid() may be already called for this process.
                 */
    
                if (err == NGX_ECHILD) {
                    ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, err,
                                  "waitpid() failed");
                    return;
                }
    
                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
                              "waitpid() failed");
                return;
            }
    
    
            one = 1;
            process = "unknown process";
    
            for (i = 0; i < ngx_last_process; i++) {
                if (ngx_processes[i].pid == pid) {
                    ngx_processes[i].status = status;
                    ngx_processes[i].exited = 1;
                    process = ngx_processes[i].name;
                    break;
                }
            }
    
            if (WTERMSIG(status)) {
    #ifdef WCOREDUMP
                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                              "%s %P exited on signal %d%s",
                              process, pid, WTERMSIG(status),
                              WCOREDUMP(status) ? " (core dumped)" : "");
    #else
                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                              "%s %P exited on signal %d",
                              process, pid, WTERMSIG(status));
    #endif
    
            } else {
                ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                              "%s %P exited with code %d",
                              process, pid, WEXITSTATUS(status));
            }
    
            if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
                              "%s %P exited with fatal code %d "
                              "and cannot be respawned",
                              process, pid, WEXITSTATUS(status));
                ngx_processes[i].respawn = 0;
            }
    
            ngx_unlock_mutexes(pid);
        }
    }

      ngx_signal_handler 主要处理了各标识字段的设置,那么设置之后并没有做更多的事,即没有进行exit()操作,它又是如何达到响应控制的呢。实际上,当进程的标识变量被设置之后,会被其主循环服务稍后处理。每一次处理任务时,都会去检查相关标识,比如如果标识是退出,则主循环服务将结束自身的循环服务,从而达到响应退出命令的目的。

      实际上,我们在做操作命令时,只是读取了一个nginx的pid即master进程的pid, 所以控制实际上只向master发送了命令。只不过master接收到该命令后,会在必要的时候将其传达给到所有的worker。从而完成整体的控制。

    // master循环服务实现
    // os/unix/ngx_process_cycle.c
    void
    ngx_master_process_cycle(ngx_cycle_t *cycle)
    {
        char              *title;
        u_char            *p;
        size_t             size;
        ngx_int_t          i;
        ngx_uint_t         sigio;
        sigset_t           set;
        struct itimerval   itv;
        ngx_uint_t         live;
        ngx_msec_t         delay;
        ngx_core_conf_t   *ccf;
    
        sigemptyset(&set);
        sigaddset(&set, SIGCHLD);
        sigaddset(&set, SIGALRM);
        sigaddset(&set, SIGIO);
        sigaddset(&set, SIGINT);
        sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
        sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
        sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
        sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
        sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
        sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
    
        if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "sigprocmask() failed");
        }
    
        sigemptyset(&set);
    
    
        size = sizeof(master_process);
    
        for (i = 0; i < ngx_argc; i++) {
            size += ngx_strlen(ngx_argv[i]) + 1;
        }
    
        title = ngx_pnalloc(cycle->pool, size);
        if (title == NULL) {
            /* fatal */
            exit(2);
        }
    
        p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
        for (i = 0; i < ngx_argc; i++) {
            *p++ = ' ';
            p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);
        }
    
        ngx_setproctitle(title);
    
    
        ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
    
        ngx_start_worker_processes(cycle, ccf->worker_processes,
                                   NGX_PROCESS_RESPAWN);
        ngx_start_cache_manager_processes(cycle, 0);
    
        ngx_new_binary = 0;
        delay = 0;
        sigio = 0;
        live = 1;
    
        for ( ;; ) {
            if (delay) {
                if (ngx_sigalrm) {
                    sigio = 0;
                    delay *= 2;
                    ngx_sigalrm = 0;
                }
    
                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "termination cycle: %M", delay);
    
                itv.it_interval.tv_sec = 0;
                itv.it_interval.tv_usec = 0;
                itv.it_value.tv_sec = delay / 1000;
                itv.it_value.tv_usec = (delay % 1000 ) * 1000;
    
                if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
                    ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                                  "setitimer() failed");
                }
            }
    
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
    
            sigsuspend(&set);
    
            ngx_time_update();
    
            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "wake up, sigio %i", sigio);
    
            // 只管读取相应标识即可
            if (ngx_reap) {
                ngx_reap = 0;
                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
                // 清理worker
                live = ngx_reap_children(cycle);
            }
    
            if (!live && (ngx_terminate || ngx_quit)) {
                // woker退出后,master再退出
                ngx_master_process_exit(cycle);
            }
    
            if (ngx_terminate) {
                if (delay == 0) {
                    delay = 50;
                }
    
                if (sigio) {
                    sigio--;
                    continue;
                }
    
                sigio = ccf->worker_processes + 2 /* cache processes */;
    
                if (delay > 1000) {
                    ngx_signal_worker_processes(cycle, SIGKILL);
                } else {
                    ngx_signal_worker_processes(cycle,
                                           ngx_signal_value(NGX_TERMINATE_SIGNAL));
                }
    
                continue;
            }
    
            if (ngx_quit) {
                ngx_signal_worker_processes(cycle,
                                            ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
                ngx_close_listening_sockets(cycle);
    
                continue;
            }
    
            if (ngx_reconfigure) {
                ngx_reconfigure = 0;
    
                if (ngx_new_binary) {
                    ngx_start_worker_processes(cycle, ccf->worker_processes,
                                               NGX_PROCESS_RESPAWN);
                    ngx_start_cache_manager_processes(cycle, 0);
                    ngx_noaccepting = 0;
    
                    continue;
                }
    
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
    
                cycle = ngx_init_cycle(cycle);
                if (cycle == NULL) {
                    cycle = (ngx_cycle_t *) ngx_cycle;
                    continue;
                }
    
                ngx_cycle = cycle;
                ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
                                                       ngx_core_module);
                ngx_start_worker_processes(cycle, ccf->worker_processes,
                                           NGX_PROCESS_JUST_RESPAWN);
                ngx_start_cache_manager_processes(cycle, 1);
    
                /* allow new processes to start */
                ngx_msleep(100);
    
                live = 1;
                ngx_signal_worker_processes(cycle,
                                            ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
            }
    
            if (ngx_restart) {
                ngx_restart = 0;
                ngx_start_worker_processes(cycle, ccf->worker_processes,
                                           NGX_PROCESS_RESPAWN);
                ngx_start_cache_manager_processes(cycle, 0);
                live = 1;
            }
    
            if (ngx_reopen) {
                ngx_reopen = 0;
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
                ngx_reopen_files(cycle, ccf->user);
                ngx_signal_worker_processes(cycle,
                                            ngx_signal_value(NGX_REOPEN_SIGNAL));
            }
    
            if (ngx_change_binary) {
                ngx_change_binary = 0;
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
                ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
            }
    
            if (ngx_noaccept) {
                ngx_noaccept = 0;
                ngx_noaccepting = 1;
                ngx_signal_worker_processes(cycle,
                                            ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
            }
        }
    }

      master进程的主要作用,实际也是管理worker,所以控制命令发送到master, 剩余工作就由master完成。总体来说,就是master先将命令发送给worker,然后自身最后再响应命令,保证命令的正确执行。

      woker进程则主要负责真正的业务处理,以及接收master发达过来的控制命令。与master各有分工,其对应控制指令只需自身响应即可。

    // worker主循环的实现
    static void
    ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
    {
        ngx_int_t worker = (intptr_t) data;
    
        ngx_process = NGX_PROCESS_WORKER;
        ngx_worker = worker;
    
        ngx_worker_process_init(cycle, worker);
    
        ngx_setproctitle("worker process");
    
        for ( ;; ) {
            // 只管读取相应标识即可
            if (ngx_exiting) {
                if (ngx_event_no_timers_left() == NGX_OK) {
                    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
                    ngx_worker_process_exit(cycle);
                }
            }
    
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
            // 业务处理
            ngx_process_events_and_timers(cycle);
    
            if (ngx_terminate) {
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
                ngx_worker_process_exit(cycle);
            }
    
            if (ngx_quit) {
                ngx_quit = 0;
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                              "gracefully shutting down");
                ngx_setproctitle("worker process is shutting down");
    
                if (!ngx_exiting) {
                    ngx_exiting = 1;
                    ngx_set_shutdown_timer(cycle);
                    ngx_close_listening_sockets(cycle);
                    ngx_close_idle_connections(cycle);
                }
            }
    
            if (ngx_reopen) {
                ngx_reopen = 0;
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
                ngx_reopen_files(cycle, -1);
            }
        }
    }

      可以看到,worker的主体循环工作,大多是在响应master的控制指定,也就是前面看到的接收到控制指令后,设置好相应的标识,在该主循环中进行响应。

      最后,我们来思考几个问题:

        1. 如果我直接kill -9 掉master, 那么nginx将会如何?
        2. 如果直接kill -9 掉woker, 那么nginx又将如何?
        3. 如何优雅的关闭nginx?
        4. 如果不使用nginx的控制命令,能否实现ngnix的优雅关闭?(shell实现)

      相信通过上面的理解,这些问题不在话下!

    不要害怕今日的苦,你要相信明天,更苦!
  • 相关阅读:
    视频分帧
    windows开启ssh服务
    使用geopy计算经纬度表示的坐标之间的距离
    哔站视频下载
    后缀树(Suffix Tree)
    [回滚莫队] AtCoder 歴史の研究
    [长链剖分优化dp] Codeforces 1499F
    [长链剖分优化dp] BZOJ 3522/4543 Hotel
    长链剖分O(nlogn)-O(1)求K级祖先
    [数论] Codeforces 1499D The Number of Pairs
  • 原文地址:https://www.cnblogs.com/yougewe/p/14321413.html
Copyright © 2020-2023  润新知