[nginx] 异步非阻塞多进程模型

来源:互联网 发布:广州java编程培训 编辑:程序博客网 时间:2024/05/21 00:17
  • 异步:事件注册机制处理请求,当本次接收到的数据处理完就返回,并有能力处理其它request,当后面数据到达时便触发该注册的事件,参考Nginx 的高并发性
  • 非阻塞IO:socket默认是阻塞模式,包括accept()、connect()、write类、read类,会使数据IO进程挂起,而nginx采用非阻塞socket,并使用了linux的epoll多路IO复用
  • 多进程:采用master-work进程模型,每个进程仅含独立线程,master进程负责整个进程组与用户的交互接口,并对work进程监控,work进程负责完成具体的任务逻辑

一、Master-Worker进程

Master进程负责

  1. 调用各module的初始化函数,读取解析nginx配置文件,模块module 解析执行 nginx.conf 配置文件流程分析 ;
  2. 充当整个进程组与用户的交互接口,接收用户操作信号管理worker进程,nginx核心进程模型

Worker进程负责

  1. 抢占进程锁,从监听套接字中获取客户端连接,实现负载均衡;
  2. 接收客户端套接字的数据,传递给upstream(如果有upstream),则否返回静态文件;
  3. 接收并处理Master套接字中的交互命令。

1.1 Master进程

nginx启动时读取并解析完配置文件,如果不是测试启动(-t)则开始绑定socket端口并设置socket属性(比如非阻塞模式),便结束掉ngx_init_cycle()函数,跳回到主函数main():

  • 如果是测试启动,则程序结束;
  • 如果是信号启动(-s),则进行信号处理函数ngx_signal_process(),打开.pid文件读出进程号,并将信号名映射成信号值,参考:Nginx中与信号有关的内容;
  • 调用ngx_init_signals()初始化信号机制,注册信号处理函数;
  • 默认daemon模式下进入ngx_daemon()fork出新进程更新进程号继续执行,本进程主动exit;
  • 调用ngx_master_process_cycle()创建worker进程,登记接受信号,然后大循环等待信号处理:
    • ngx_start_worker_processes()创建worker进程,并建立master与worker的频道通信机制
      • ngx_spawn_process()master调用socketpair创造一对未命名的UNIX域套接字,设置为非阻塞模式;
      • ngx_pass_open_channel()因为后生成的子进程可以继承channel[0]往前生成的子进程发送信息,但前生成的子进程没有后生成的子进程频道描述符,需要master进程把当前生成的子进程频道描述符发送给所有子进程。虽然nginx的子进程通信没有使用频道机制,但机制本身是完善的;

1.2 Worker进程

master进程执行时会调用ngx_start_worker_processes()创建worker进程(数量是conf.worker_processes),并设置worker进程入口函数ngx_worker_process_cycle()

  • 初始化工作,ngx_worker_process_init(),调用各module的init_process句柄,关闭之前生成的子进程的1号频道和master进程的0号频道,并调用ngx_add_channel_event()把自己slot的1号频道(ngx_channel)放入读事件监听集里,注册ngx_channel_handler
  • 当接收到频道信息,触发读事件ngx_channel_handler(),接受操作类型,记录标记,共5种:退出消息ngx_quit(NGX_CMD_QUIT)、终止消息ngx_terminate(NGX_CMD_TERMINATE)、重启消息ngx_reopen(NGX_CMD_REOPEN)、打开其他线程的消息(NGX_CMD_OPEN_CHANNEL)、关闭其他线程的消息(NGX_CMD_CLOSE_CHANNEL);
  • 进入大循环,调用ngx_process_events_and_timers()完成一次服务响应(由于socket是非阻塞的所以并不是一次完整的web请求)。然后分别对以上3类消息做出响应:
    • 终止ngx_terminate,函数ngx_worker_process_exit()调用各module的进程结束句柄;
    • 退出ngx_quit,优雅结束,调用ngx_close_listening_sockets()关闭各连接并删除相关的读事件;
    • 重启ngx_reopen,调用ngx_reopen_files(),功能还不清楚

二、多进程竞争监听套接字

nginx在这里实现了多进程的负载均衡:一个进程长期拥有监听套接字则期间的所有请求都将被这个工作进程处理;而多个工作进程同时拥有监听套接字,当请求到达时会引发多个进程去争抢这个请求,这种现象称之为惊群(thundering herd)。nginx使用全局进程锁来处理,ngx_process_events_and_timers()

  • 变量ngx_use_accept_mutex是负载均衡的开关;
  • 变量ngx_accept_disabled用来标记该进程是否过载,ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n,connection_n默认为512,free_connection_n初始值为connection_n,故最大负载为connection_n * 7 / 8,当获得连接时free_connection_n会减少;
  • 如果进程没有过载则会去竞争锁ngx_trylock_accept_mutex(),具体逻辑见下图
    竞争锁
  • 抢占到锁的进程会把监听套接字添加到读事件集,由于事件module的进程初始化函数为每个listening_t申请了connection并绑定了rev->handler = ngx_event_accept并设置rev->accept = 1,所以被epoll触发时将调用ngx_event_accept()
  • 抢占到锁的进程会标记上NGX_POST_EVENTS,表示所有发生的事件都将延后处理,尽量缩短占用锁的时间,把事件放在释放掉锁之后再处理:
    • epoll的ngx_epoll_process_events()检查到socket可读写时,判断有标志NGX_POST_EVENTS存在,会把accept事件放到ngx_posted_accept_events队列,而socket的read、write事件放到ngx_posted_events队列;
    • 返回到函数ngx_process_events_and_timers(),先处理ngx_posted_accept_events队列的事件,再释放竞争锁,再处理ngx_posted_events队列的事件。
0 0