Nginx - 启动流程

栏目: 服务器 · Nginx · 发布时间: 5年前

内容简介:断断续续看Nginx的源码差不多有一年多的时间了,一直准备写几篇文章来深入了解一下,一直没有下定决心来写,就从这篇文章开始吧。本文主要从整体上了解一下Nginx的架构,以及Nginx的启动流程。nginx的启动是从初始化完成之后,如果需要起多个worker进程则Master进程会进入

断断续续看Nginx的源码差不多有一年多的时间了,一直准备写几篇文章来深入了解一下,一直没有下定决心来写,就从这篇文章开始吧。本文主要从整体上了解一下Nginx的架构,以及Nginx的启动流程。

nginx架构

nginx的目标是提供一个高性能,高并发,可扩展的web服务器,所以nginx的代码架构实现为模块化、事件驱动、异步、非阻塞、单线程的。 nginx大量使用多路复用和事件通知机制,连接请求会被分发到不同的worker进程中去处理,每个worker进程每秒可以并发处理数千个请求连接。

nginx的架构如下图,一个Master进程管理多个Worker进程,Worker进程通过 kqueueepoll 等事件通知机制循环处理连接请求,具体的请求处理逻辑是通过注册到进程中的各个模块来处理的。

Nginx - 启动流程

请求分发

因为Nginx会同时启动多个worker进程,80端口是各worker进程所共享的,多进程同时listen 80端口就会产生竞争,当一个请求到来时内核会唤醒所有worker进程,谁能抢到连接谁就去处理,而其他进程则继续进入休眠状态,这就是所谓的 惊群效应 。当然Nginx也实现了一种锁机制,当进程被唤醒时会先去获得锁,谁获得锁之后再去调用 accept ,这样就避免了多进程同时调用 accept (需要配置才会开启accept锁机制)。

Nginx - 启动流程 accept锁

自Nginx 1.9.1之后开始支持socket的portreuse新特性,此时当请求到来时内核不会唤醒所有监听的进程,而是仅唤醒其中一个进程来处理请求,其他进程没有任何影响继续休眠。也就是说之前请求的分发靠进程自己去抢,而现在是靠内核来主动分配

Nginx - 启动流程

nginx启动流程

nginx的启动是从 /src/core/nginx.c 文件中的main函数开始执行的,代码的开始当然是读取配置以及各种初始化,这里只关心网络的初始化以及master进程和worker进程的启动。master进程会完成所有的初始化工作,然后调用 fork() 函数来启动worker进程,这样所有的初始化工作就相当于直接继承给了worker进程,网络初始化也是如此。网络的初始化主要是在 ngx_open_listening_sockets 函数中进行的:

ngx_int_t
ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
    /* 代码有删减,这里只看重要的部分 */
    log = cycle->log;

    /* 如果初始化失败,进行多次尝试 */
    for (tries = 5; tries; tries--) {
        failed = 0;

        /* for each listening socket */

        ls = cycle->listening.elts;
        for (i = 0; i < cycle->listening.nelts; i++) {

            if (ls[i].ignore) {
                continue;
            }
            /* 创建socket */
            s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);

            if (s == (ngx_socket_t) -1) {
                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                              ngx_socket_n " %V failed", &ls[i].addr_text);
                return NGX_ERROR;
            }

            if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
                           (const void *) &reuseaddr, sizeof(int))
                == -1)
            {
                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                              "setsockopt(SO_REUSEADDR) %V failed",
                              &ls[i].addr_text);

                if (ngx_close_socket(s) == -1) {
                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                  ngx_close_socket_n " %V failed",
                                  &ls[i].addr_text);
                }

                return NGX_ERROR;
            }

#if (NGX_HAVE_REUSEPORT)
            /* 如果系统支持端口复用,并且配置了端口复用,则设置SO_REUSEPORT */
            if (ls[i].reuseport && !ngx_test_config) {
                int  reuseport;

                reuseport = 1;

                if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT,
                               (const void *) &reuseport, sizeof(int))
                    == -1)
                {
                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                  "setsockopt(SO_REUSEPORT) %V failed",
                                  &ls[i].addr_text);

                    if (ngx_close_socket(s) == -1) {
                        ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                      ngx_close_socket_n " %V failed",
                                      &ls[i].addr_text);
                    }

                    return NGX_ERROR;
                }
            }
#endif
            /* 设置为非阻塞 */
            if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
                if (ngx_nonblocking(s) == -1) {
                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                  ngx_nonblocking_n " %V failed",
                                  &ls[i].addr_text);

                    if (ngx_close_socket(s) == -1) {
                        ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                      ngx_close_socket_n " %V failed",
                                      &ls[i].addr_text);
                    }

                    return NGX_ERROR;
                }
            }

            ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
                           "bind() %V #%d ", &ls[i].addr_text, s);
            /* 绑定 */
            if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
                err = ngx_socket_errno;

                if (err != NGX_EADDRINUSE || !ngx_test_config) {
                    ngx_log_error(NGX_LOG_EMERG, log, err,
                                  "bind() to %V failed", &ls[i].addr_text);
                }

                if (ngx_close_socket(s) == -1) {
                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                  ngx_close_socket_n " %V failed",
                                  &ls[i].addr_text);
                }

                if (err != NGX_EADDRINUSE) {
                    return NGX_ERROR;
                }

                if (!ngx_test_config) {
                    failed = 1;
                }

                continue;
            }

            if (ls[i].type != SOCK_STREAM) {
                ls[i].fd = s;
                continue;
            }
            /* 监听 */
            if (listen(s, ls[i].backlog) == -1) {
                err = ngx_socket_errno;

                /*
                 * on OpenVZ after suspend/resume EADDRINUSE
                 * may be returned by listen() instead of bind(), see
                 * https://bugzilla.openvz.org/show_bug.cgi?id=2470
                 */

                if (err != NGX_EADDRINUSE || !ngx_test_config) {
                    ngx_log_error(NGX_LOG_EMERG, log, err,
                                  "listen() to %V, backlog %d failed",
                                  &ls[i].addr_text, ls[i].backlog);
                }

                if (ngx_close_socket(s) == -1) {
                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                  ngx_close_socket_n " %V failed",
                                  &ls[i].addr_text);
                }

                if (err != NGX_EADDRINUSE) {
                    return NGX_ERROR;
                }

                if (!ngx_test_config) {
                    failed = 1;
                }

                continue;
            }

            ls[i].listen = 1;

            ls[i].fd = s;
        }

        if (!failed) {
            break;
        }

        /* TODO: delay configurable */

        ngx_log_error(NGX_LOG_NOTICE, log, 0,
                      "try again to bind() after 500ms");

        ngx_msleep(500);
    }

    if (failed) {
        ngx_log_error(NGX_LOG_EMERG, log, 0, "still could not bind()");
        return NGX_ERROR;
    }

    return NGX_OK;
}

初始化完成之后,如果需要起多个worker进程则Master进程会进入 ngx_master_process_cycle 函数中调用 ngx_start_worker_processes 函数来启动worker进程,之后Master进程会在 ngx_master_process_cycle 函数中进入无限循环成为常驻进程,等待外部命令信号的唤醒,然后执行相应命令。worker进程启动之后会进入 ngx_worker_process_cycle 函数中进行worker进程的初始化,然后进入无限循环处理请求。此时nginx全部启动完成,可以处理请求了。

当我们要重启nginx的work进程时,我们会执行 ./nginx -s reload 命令,那么这种命令是怎么传递到Master进程然后执行的了?执行命令后nginx还是从main函数开始执行,在 ngx_get_options 函数中解析传递的参数时会设置相应标记,例如如果有 -s 选项时, ngx_signal 这个指针会指向 reload 这个命令,然后main函数会调用 ngx_signal_process 函数给Master进程发送信号,Master进程收到信号后会被系统唤醒,然后在 ngx_signal_handler 函数中解析信号,回到 ngx_master_process_cycle 函数中的无限循环处理相应命令:

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;

    /* master进程的pid会放在nginx.pid文件中,这里的逻辑主要是打开文件,读取pid */
    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;
    }

    /* 通过 kill 系统函数向Master进程发送相应的信号  */
    return ngx_os_signal_process(cycle, sig, pid);

}

参考

Socket Sharding in NGINX Release 1.9.1 nginx Nginx开发从入门到精通 基于nginx-1.14.0源码分析


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ruby Cookbook

Ruby Cookbook

Lucas Carlson、Leonard Richardson / O'Reilly Media / 2006-7-29 / USD 49.99

Do you want to push Ruby to its limits? The "Ruby Cookbook" is the most comprehensive problem-solving guide to today's hottest programming language. It gives you hundreds of solutions to real-world pr......一起来看看 《Ruby Cookbook》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具