基础知识

I/O 过程

  1. 等待数据
  2. 将数据从内核复制到用户空间

I/O 复用

调用 select / poll,该方法由一个用户态线程负责轮询多个 Channel,直到某个阶段1的数据就绪,再通知实际的用户线程执行阶段2的拷贝。通过一个专职的用户态线程执行非阻塞I/O轮询,模拟实现了阶段1的异步化。

Linux 网络编程

tcp 服务端的操作有,socket()、bind()、listen()、accpet()、read()、write()、close().

Linux epoll

epoll 操作有,epoll_create()、epoll_ctl()、epoll_wait().

epoll 有两种模式,边沿触发 ET 和 水平触发 LT。ET 仅当状态发生变化时才获得通知,所以要一直读写直到处理完发现错误;LT 只要有数据没有处理就会一直通知。

NGINX

事件模型

非阻塞 事件驱动

nginx 支持的 event 处理模型有:select、poll、epoll(linux)、kqueue(BSD)、/dev/poll(Solaris)、eventport(Solaris)、以及 win32 的 select 和 poll。 v1.9.0(2015)将 aio、rtsig(实时信号)移除。

以 epoll 为例,nginx 大部分事件采用 ET 模式,只有 listen 端口的读事件为 LT 模式。

  • ngx_epoll_init() 中调用 epoll_create(),大小为 connection_n / 2。
  • ngx_epoll_xxx() 中调用 epoll_ctl(),对事件和连接进行添加修改删除。
  • ngx_process_events_and_timers() 中调用 epoll_wait()。

进程模型

nginx 为 1 master + N worker。master 负责管理 worker,worker 处理网络请求。

一般 worker 进程数与cpu内核数一致,所以不存在大量的子进程生成和管理任务,避免了大量子进程的数据IPC共享开销和切换竞争开销。各 worker 进程之间也只是重复拷贝了监听字,除了父子进程间传递控制消息,基本没有 IPC 需求。

worker 单进程内,也不需要同步操作。

nginx_process

图片来自 nginx.com

listening

ngx_listening,以 HTTP 为例:

  1. 在 ngx_http_block() 中 ngx_http_add_listening() 调用 ngx_create_listening(),指定 listening 的 handler 为 ngx_http_init_connection, 并添加 ngx_listening 到 cycle -> listening。

  2. main() 中 ngx_init_cycle() 调用 ngx_open_listening_sockets(),对 cycle -> listening 中依次进行 socket()、setsockopt()、bind()、listen()。

  3. worker process 中,在 ngx_worker_process_init() 中会对所有模块的 init_process() 调用, ngx_event_core_module 模块中的 init_process 方法为 ngx_event_process_init();ngx_event_process_init() 中指定每个 listening 读事件的处理函数为 ngx_event_accept()

  4. accept() / accept4() 发生在 ngx_event_accept() 处理函数。
    触发读的 listening,用关联的 fd,进行 accept() / accept4() ,得到新连接的 socket 和 connection

connection

ngx_connection,以 HTTP 为例:

  1. 作为 listening 的连接

    main() 中 ngx_init_cycle() 调用 ngx_open_listening_sockets(),为每个 listening 绑定一个 connection。

  2. 作为 accept 后的连接

    在 ngx_event_accept() 中,accept 后获得新连接, 使用 ngx_event_actions 的 add_conn添加到监控队列中(具体到 epoll,使用 ngx_epoll_add_event), 并调用 (ngx_listening_t) ls -> handler,即 ngx_http_init_connection()。

    在 ngx_http_init_connection() 中,connection 的读事件处理函数指定为ngx_http_wait_request_handler,写事件处理函数指定为ngx_http_empty_handler

  3. 作为 upstream 发起请求的连接

event

主要为网络事件和定时事件。

先看定义(忽略一部分)

struct ngx_event_s ngx_event_t;
struct ngx_event_s {
    void                 *data;

    ngx_event_handler_pt  handler;

    ngx_log_t            *log;
};

data,通常指向 ngx_connection_t; handler,处理方法,例如 ngx_http_xxx_handler。

event module

event 模块的主要功能为,监听 accept 后建立的连接、对读写事件进行删除。

从 objs/ngx_modules.c 可以得知,关于 event 的模块加载了多个:ngx_events_module 、ngx_event_core_module、ngx_epoll_module、ngx_select_module。

先看 ngx_events_module 定义,可知 ngx_events_module 引入了 event block。

ngx_module_t  ngx_events_module = {
    NGX_MODULE_V1,
    &ngx_events_module_ctx,                /* module context */
    ngx_events_commands,                   /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

static ngx_core_module_t  ngx_events_module_ctx = {
    ngx_string("events"),
    NULL,
    ngx_event_init_conf
};

static ngx_command_t  ngx_events_commands[] = {

    { ngx_string("events"),
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_events_block,
      0,
      0,
      NULL },

      ngx_null_command
};

再看 ngx_event_core_module 定义,可知 ngx_event_core_module 提供了 event 的核心配置项。

ngx_module_t  ngx_event_core_module = {
    NGX_MODULE_V1,
    &ngx_event_core_module_ctx,            /* module context */
    ngx_event_core_commands,               /* module directives */
    NGX_EVENT_MODULE,                      /* module type */
    NULL,                                  /* init master */
    ngx_event_module_init,                 /* init module */
    ngx_event_process_init,                /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

static ngx_event_module_t  ngx_event_core_module_ctx = {
    &event_core_name,
    ngx_event_core_create_conf,            /* create configuration */
    ngx_event_core_init_conf,              /* init configuration */

    { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

static ngx_command_t  ngx_event_core_commands[] = {

    { ngx_string("worker_connections"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_connections,
      0,
      0,
      NULL },

    { ngx_string("use"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_use,
      0,
      0,
      NULL },

    { ngx_string("multi_accept"),
      NGX_EVENT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_event_conf_t, multi_accept),
      NULL },

    { ngx_string("accept_mutex"),
      NGX_EVENT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_event_conf_t, accept_mutex),
      NULL },

    { ngx_string("accept_mutex_delay"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      0,
      offsetof(ngx_event_conf_t, accept_mutex_delay),
      NULL },

    { ngx_string("debug_connection"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_debug_connection,
      0,
      0,
      NULL },

      ngx_null_command
};

找一个具体实现的模块,ngx_epoll_module,看它的定义,可知 ngx_epoll_module 引入了 epoll 的定制配置,并终于提供了 module actions。

ngx_module_t  ngx_epoll_module = {
    NGX_MODULE_V1,
    &ngx_epoll_module_ctx,               /* module context */
    ngx_epoll_commands,                  /* module directives */
    NGX_EVENT_MODULE,                    /* module type */
    NULL,                                /* init master */
    NULL,                                /* init module */
    NULL,                                /* init process */
    NULL,                                /* init thread */
    NULL,                                /* exit thread */
    NULL,                                /* exit process */
    NULL,                                /* exit master */
    NGX_MODULE_V1_PADDING
};

static ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
#if (NGX_HAVE_EVENTFD)
        ngx_epoll_notify,                /* trigger a notify */
#else
        NULL,                            /* trigger a notify */
#endif
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

static ngx_command_t  ngx_epoll_commands[] = {

    { ngx_string("epoll_events"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      0,
      offsetof(ngx_epoll_conf_t, events),
      NULL },

    { ngx_string("worker_aio_requests"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      0,
      offsetof(ngx_epoll_conf_t, aio_requests),
      NULL },

      ngx_null_command
};

综上,ngx_events_module 引入了 event block,在 ngx_events_block() 中对所有的 NGX_EVENT_MODULE 类型的模块,进行 create_conf()、ngx_conf_parse()、init_conf(); ngx_event_core_module 提供了核心配置和流程钩子;ngx_epoll_module 提供了可操作的 actions。

关于配置,多个模块协力通过 ngx_event_init_conf()、ngx_event_core_create_conf()、ngx_event_core_init_conf()、ngx_epoll_create_conf()、ngx_epoll_init_conf() 等方法提供了配置项。

关于流程,每个模块按需提供了module 要求的 init master,init module,init process,init thread,exit thread,exit process,exit master,例如 ngx_event_module_init()、ngx_event_process_init()。 ngx_event_process_init() 中调用 NGX_EVENT_MODULE 类型的各个模块 actions 中的 init,对于 ngx_epoll_module 即 ngx_epoll_init。

event 机制核心

worker process loop

关键方法,事件的入口函数,ngx_process_events_and_timers(),在 worker process 的 loop 中出现,处理网络事件和定时事件。

以 epoll 为例,epoll_wait 就在该方法中调用。

主要操作为:

  1. ngx_process_events 处理网络事件
  2. 处理两个队列(ngx_posted_accept_events、ngx_posted_events)中的事件
  3. ngx_event_expire_timer 处理定时事件。

实现上,ngx_process_events 只接受而不处理事件,并加入 ngx_posted_events队列,直到 ngx_accept_mutex 全局锁去掉后才处理。 ngx_posted_accept_events 是 accept 事件延时队列,ngx_posted_events 是普通事件延时队列。

ngx_accept_mutex

负载均衡问题

ngx_accept_disabled 变量在 ngx_event_accept 函数中计算。处理 accept 事件时,每 accept 一个新的连接,就更新 ngx_accept_disabled。

如果为高负载,即大于0,就表示该进程接受的连接过多,因此放弃一次争抢 accept mutex 的机会,同时减一,然后,继续处理已有连接上的事件;如果为低负载,进程就去竞争 accept 锁,接受新的连接。

ngx_accept_disabled 的计算方法,使得 worker 处理的连接数在达到其最大处理数的 7/8 时才会触发,禁止 accept 一段时间。

ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;

nginx就利用这一点实现了进程关于连接的基本负载均衡。

惊群问题

问题:当内核 accept 一个连接时,会唤醒所有等待中的进程,但其实只有一个进程能获取到连接,其他进程重新进入休眠状态。

解决:accept 锁,避免多个 process 同时调用 accept。锁的实现方式默认为 CPU 自旋锁,系统不支持的话,使用文件锁。

Syntax: accept_mutex [ on | off ]
Default: off

避免在新连接过少时,多个worker进程被唤醒,浪费系统资源。

There is no need to enable accept_mutex on systems that support the EPOLLEXCLUSIVE flag (1.11.3) or when using reuseport.

目前新版的Linux内核中增加了 EPOLLEXCLUSIVE 选项(Linux 4.5, glibc 2.24),nginx 从 1.11.3 版本(released 2016-07-26)之后也增加了 对 NGX_EXCLUSIVE_EVENT 选项的支持,这样就可以避免多 worker 的 epoll 出现的惊群效应,从此之后 accept_mutex 变成默认on。

Nginx缺省激活了accept_mutex(1.11.3之前),也就是说不会有惊群问题,但真的有那么严重么?实际上Nginx作者Igor Sysoev曾经给过相关的解释

OS may wake all processes waiting on accept() and select(), this is called thundering herd problem. This is a problem if you have a lot of workers as in Apache (hundreds and more), but this insensible if you have just several workers as nginx usually has. Therefore turning accept_mutex off is as scheduling incoming connection by OS via select/kqueue/epoll/etc (but not accept()).

实现上,ngx_process_events_and_timers()中会获取 accept_mutex 锁,竞争成功后 ngx_enable_accept_events,同时所有事件增加一个 NGX_POST_EVENTS; 其他竞争失败的进程,ngx_disable_accept_events,下次 cycle -> listening 数组中即将有新的连接,线程也不会去响应,直到它之后主动去竞争成功。


Related Posts


Published

Category

nginx

Tags

Contact