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.cvoidngx_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;#endifr->write_event_handler = ngx_http_core_run_phases;ngx_http_core_run_phases(r);}// http/ngx_http_core_module.cvoidngx_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_tngx_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.cstatic ngx_int_tngx_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;#endifpclcf = 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;}}#endifreturn rc;}// 非正则location 匹配查找过程/** NGX_OK - exact match* NGX_DONE - auto redirect* NGX_AGAIN - inclusive match* NGX_DECLINED - no match*/static ngx_int_tngx_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_confreturn 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.cngx_int_tngx_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_execrc = ngx_regex_exec(re->regex, s, r->captures, len);// 无匹配返回 NGX_DECLINEDif (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.cngx_int_tngx_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.cngx_int_tngx_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.cngx_int_tngx_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_tngx_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);#elsengx_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 voidngx_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 voidngx_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 WCOREDUMPngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,"%s %P exited on signal %d%s",process, pid, WTERMSIG(status),WCOREDUMP(status) ? " (core dumped)" : "");#elsengx_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.cvoidngx_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");// 清理workerlive = 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 voidngx_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实现)
相信通过上面的理解,这些问题不在话下!

腾讯、阿里、滴滴后台面试题汇总总结 — (含答案)
面试:史上最全多线程面试题 !
最新阿里内推Java后端面试题
JVM难学?那是因为你没认真看完这篇文章

关注作者微信公众号 —《JAVA烂猪皮》
了解更多java后端架构知识以及最新面试宝典


看完本文记得给作者点赞+在看哦~~~大家的支持,是作者源源不断出文的动力
作者:等你归去来
出处:https://www.cnblogs.com/yougewe/p/14321413.html
