上一篇中,我们了解了如何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本身不再完美。大家需要为这一个个的边界问题,伤透了脑筋,这样也许它就不再那么流行了。

// 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实现)

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

Nginx(七):location的使用以及nginx关闭原理的更多相关文章

  1. 17.Nginx 重写(location rewrite)

    Nginx 重写(location / rewrite) 目录 Nginx 重写(location / rewrite) 常见的nginx正则表达式 location lication的分类 loca ...

  2. 13、Nginx七层负载均衡

    1.Nginx负载均衡基本概述 1.1为什么需要使用负载均衡 当我们的Web服务器直接面向用户,往往要承载大量并发请求,单台服务器难以负荷,我使用多台WEB服务器组成集群,前端使用Nginx负载均衡, ...

  3. nginx 七层负载均衡

    [tcp] nginx 七层负载均衡 nginx负载均衡概述 当我们的Web服务器直接面向用户,往往要承载大量并发请求,单台服务器难以负荷,我使用多台Web服务器组成集群,前端使用Nginx负载均衡, ...

  4. 七年开发浅谈Nginx负载均衡

    一 特点 1.1 应用情况 Nginx做为一个强大的Web服务器软件,具有高性能.高并发性和低内存占用的特点.此外,其也能够提供强大的反向代理功能.俄罗斯大约有超过20%的虚拟主机采用Nginx作为反 ...

  5. Linux架构之Nginx 七层负载均衡

    第50章 Nginx七层负载均衡 一.Nginx负载均衡基本概述 1)为什么要使用负载均衡 当我们的Web服务器直接面向用户,往往要承载大量并发请求,单台服务器难以负荷.使用多台Web服务器组成集群, ...

  6. Nginx 七层反向代理

    目录 1.代理 2.正向代理 3.反向代理 4.Nginx 反向代理 5.Nginx 反向代理相关指令介绍 ①.listen ②.server_name ③.location ④.proxy_pass ...

  7. 第十五章 nginx七层负载均衡

    一.Nginx负载均衡 1.为什么做负载均衡 当我们的Web服务器直接面向用户,往往要承载大量并发请求,单台服务器难以负荷,我使用多台Web服务器组成集群,前端使用Nginx负载均衡,将请求分散的打到 ...

  8. Nginx配置location总结及rewrite规则写法

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 32.0px "Helvetica Neue"; color: #323333 } p. ...

  9. rewrite规则写法及nginx配置location总结

    rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用. 例如http://seanlook.com/a/we/index.php ...

  10. Nginx之location 匹配规则详解

    有些童鞋的误区 1. location 的匹配顺序是“先匹配正则,再匹配普通”. 矫正: location 的匹配顺序其实是“先匹配普通,再匹配正则”.我这么说,大家一定会反驳我,因为按“先匹配普通, ...

随机推荐

  1. python线性回归

    一.理论基础 1.回归公式 对于单元的线性回归,我们有:f(x) = kx + b 的方程(k代表权重,b代表截距). 对于多元线性回归,我们有: 或者为了简化,干脆将b视为k0·x0,,其中k0为1 ...

  2. ASP.NET Core 中间件 自定义全局异常中间件以及 MVC异常过滤器作用

    中间件是一种装配到应用管道以处理请求和响应的软件. 每个组件: 选择是否将请求传递到管道中的下一个组件. 可在管道中的下一个组件前后执行工作. 请求委托用于生成请求管道. 请求委托处理每个 HTTP ...

  3. ios私钥证书和profile文件的创建方法

    做过ios开发的朋友们,对ios私钥证书和profile文件(描述文件)可能并不陌生,可以通过mac电脑来创建,但是,假如我们是用H5开发工具开发的,我们没有mac电脑怎么创建证书呢? 目前H5打包用 ...

  4. docker 使用教程1

    1.概念理解 镜像:docker镜像就像一个个模具. 容器:docker容器就是模具翻模出来的东西. 仓库:仓库就是存放模具的地方. 下面通过运行 hello-world 来理解 docker镜像运行 ...

  5. JDK,JRE,JVM三者之间的关系和作用

    1,定义: JDK: Java Develpment Kit java 开发工具 bin:最主要的是编译器(javac.exe) include:java和JVM交互用的头文件 lib:类库 JRE: ...

  6. File类的特点?如何创建File类对象?Java中如何操作文件内容,什么是Io流Io流如何读取和写入文件?字节缓冲流使用原则?

    重难点提示 学习目标 1.能够了解File类的特点(存在的意义,构造方法,常见方法) 2.能够了解什么是IO流以及分类(IO流的概述以及分类) 3.能够掌握字节输出流的使用(继承体系结构介绍以及常见的 ...

  7. RocketMQ(十):数据存储模型设计与实现

    消息中间件,说是一个通信组件也没有错,因为它的本职工作是做消息的传递.然而要做到高效的消息传递,很重要的一点是数据结构,数据结构设计的好坏,一定程度上决定了该消息组件的性能以及能力上限. 1. 消息中 ...

  8. jdbc事务、连接池概念、c3p0、Driud、JDBC Template、DBUtils

    JDBC 事务控制 什么是事务:一个包含多个步骤或者业务操作.如果这个业务或者多个步骤被事务管理,则这多个步骤要么同时成功,要么回滚(多个步骤同时执行失败),这多个步骤是一个整体,不可分割的. 操作: ...

  9. Linux 下挂载新硬盘方法(转)

    1.关闭服务器加上新硬盘   2.启动服务器,以root用户登录   3.查看硬盘信息 #fdisk -l Disk /dev/sda: 42.9 GB, 42949672960 bytes 255  ...

  10. 利用python 5分钟制作一款小游戏

    1.安装pygame 在命令行cmd中输入:pip install pygame ( 注:如果安装不成功,需要输入:python -m pip install --user --upgrade pip ...