Nginx 多进程连接请求/事件分发流程分析

Nginx使用多进程的方法进行任务处理,每个worker进程只有一个线程,单线程循环处理全部监听的事件。本文重点分析一下多进程间的负载均衡问题以及Nginx多进程事件处理流程,方便大家自己写程序的时候借鉴。

 

一、监听建立流程

整个建立监听socket到accept的过程如下图:

 

说明:

1.main里面调用ngx_init_cycle(src/core/ngx_cycle.c),ngx_init_cycle里面完成很多基本的配置,如文件,共享内存,socket等。

2.上图左上角是ngx_init_cycle里面调用的ngx_open_listening_sockets(src/core/ngx_connection.c)主要完成的工作,包括基本的创建socket,setsockopt,bind和listen等。

3.然后是正常的子进程生成过程。在每个子worker进程的ngx_worker_process_cycle中,在调用ngx_worker_process_init里面调用各模块的初始化操作init_process。一epoll module为例,这里调用ngx_event_process_init,里面初始化多个NGX_EVENT_MODULE类型的module.NGX_EVENT_MODULE类型的只有ngx_event_core_module和ngx_epoll_module。前一个module的actions部分为空。ngx_epoll_module里面的init函数就是ngx_epoll_init。ngx_epoll_init函数主要完成epoll部分相关的初始化,包括epoll_create,设置ngx_event_actions等。

4.初始化完ngx_epoll_module,继续ngx_event_process_init,然后循环设置每个listening socket的read handler为ngx_event_accept.最后将每个listening socket的READ事件添加到epoll进行等待。

5.ngx_event_process_init初始化完成后,每个worker process开始循环处理events&timers。最终调用的是epoll_wait。由于之前listening socket以及加入到epoll,所以如果监听字有read消息,那么久调用rev->handler进行处理,监听字的handler之前已经设置为ngx_event_accept。ngx_event_accept主要是调用accept函数来接受新的客户端套接字client socket。

下面是监听字的处理函数ngx_event_accept流程图:

 

说明:

1.前半部分主要是通过accept接受新连接字,生成并设置相关结构,然后添加到epoll中。

2.后半部分调用connection中的listening对应的handler,即ngx_xxx_init_connection,其中xxx可以是mail,http和stream。顾名思义,该函数主要是做新的accepted连接字的初始化工作。上图以http module为例,初始化设置了连接字的read handler等。

 

二、负载均衡问题

 Nginx里面通过一个变量ngx_accept_disabled来实施进程间获取客户端连接请求的负载均衡策略。ngx_accept_disabled使用流程图:

 

说明:

1.ngx_process_events_and_timers函数中,通过ngx_accept_disabled的正负判断当前进程负载高低(大于0,高负载;小于0,低负载)。如果低负载时,不做处理,进程去申请accept锁,监听并接受新的连接。

2.如果是高负载时,ngx_accept_disabled就发挥作用了。这时,不去申请accept锁,让出监听和接受新连接的机会。同时ngx_accept_disabled减1,表示通过让出一次accept申请的机会,该进程的负载将会稍微减轻,直到ngx_accept_disabled最后小于0,重新进入低负载的状态,开始新的accept锁竞争。

 

参考链接:http://www.jb51.net/article/52177.htm

 

三、“惊群”问题

“惊群”问题:多个进程同时监听一个套接字,当有新连接到来时,会同时唤醒全部进程,但只能有一个进程与客户端连接成功,造成资源的浪费。

Nginx通过进程间共享互斥锁ngx_accept_mutex来控制多个worker进程对公共监听套接字的互斥访问,获取锁后调用accept取出与客户端已经建立的连接加入epoll,然后释放互斥锁。

Nginx处理流程示意图:

说明:

1.ngx_accept_disabled作为单个进程负载较高(最大允许连接数的7/8)的标记,计算公式:

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

即进程可用连接数free_connection_n小于总连接数connection_n的1/8时ngx_accept_disabled大于0;否则小于0.或者说ngx_accept_disabled小于0时,表示可用连接数较多,负载较低;ngx_accept_disabled大于0时,说明可用连接数较少,负载较高。

2.如果进程负载较低时,即ngx_accept_disabled 小于0,进程允许竞争accept锁。

3.如果进程负载较高时,放弃竞争accept锁,同时ngx_accept_disabled 减1,即认为由于让出一次竞争accept锁的机会,负载稍微减轻(ngx_accept_disabled 小于0可用)。由于负载较高时(ngx_accept_disabled >0)只是将ngx_accept_disabled 减1,这里不申请accept锁,所以后续的accept函数会遭遇“惊群”问题,返回错误errno=EAGAIN,直接返回(个人觉得这里有改进的空间,见补充部分)。

ngx_process_events_and_timers函数部分代码如下:

 1 if (ngx_use_accept_mutex) {
 2         if (ngx_accept_disabled > 0) {
 3             ngx_accept_disabled--;
 4 
 5         } else {
 6             if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
 7                 return;
 8             }
 9 
10             if (ngx_accept_mutex_held) {
11                 flags |= NGX_POST_EVENTS;
12 
13             } else {
14                 if (timer == NGX_TIMER_INFINITE
15                     || timer > ngx_accept_mutex_delay)
16                 {
17                     timer = ngx_accept_mutex_delay;
18                 }
19             }
20         }
21     }

4.如果竞争加锁失败(6-7行),直接返回,返回到ngx_worker_process_cycle的for循环里面,此次不参与事件处理,进行下一次循环。

5.如果竞争加锁成功,设置NGX_POST_EVENTS标记,表示将事件先放入队列中,稍后处理,优先释放ngx_accept_mutex,防止单个进程过多占用锁时间,影响事件处理效率。ngx_epoll_process_events函数有如下部分(写事件wev部分也一样):

1 if (flags & NGX_POST_EVENTS) {
2     queue = rev->accept ? &ngx_posted_accept_events
3                         : &ngx_posted_events;
4 
5     ngx_post_event(rev, queue);//先将event放入队列,稍后处理
6 
7 } else {
8     rev->handler(rev);
9 }

6.从ngx_epoll_process_events返回ngx_process_events_and_timers,然后是处理accept事件(下面代码10行);处理完accept事件,马上释放锁(下面代码13-15行),给其他进程机会去监听连接事件。最后处理一般的连接事件。

 1 delta = ngx_current_msec;
 2 
 3 (void) ngx_process_events(cycle, timer, flags);
 4 
 5 delta = ngx_current_msec - delta;
 6 
 7 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 8                    "timer delta: %M", delta);
 9 
10 ngx_event_process_posted(cycle, &ngx_posted_accept_events);//这里处理ngx_process_events 里面post的accept事件
11 
12 //处理完accept事件,马上释放锁
13 if (ngx_accept_mutex_held) {
14     ngx_shmtx_unlock(&ngx_accept_mutex);
15 }
16 
17 //在处理一般的connection事件之前,先处理超时。
18 if (delta) {
19     ngx_event_expire_timers();
20 }
21 
22 //处理普通的connection事件请求
23 ngx_event_process_posted(cycle, &ngx_posted_events);

7.在处理accept事件时,handler是ngx_event_accept(src/event/ngx_event_accept.c),在这个函数里面,每accept一个新的连接,就更新ngx_accept_disabled。

 1 do {
 2 ...
 3 //接受新连接
 4 accept();
 5 ...
 6 //更新ngx_accept_disabled 
 7 ngx_accept_disabled = ngx_cycle->connection_n / 8
 8                               - ngx_cycle->free_connection_n;
 9 
10 ...
11 
12 }while(ev->available)

 补充:

ngx_accept_disabled 减1这条路径很明显没有申请accept锁,所以后面的epoll_wait和accept函数会出现“惊群”问题。建议按如下图改进:

 

说明:

添加红色框步骤,在负载过高时,ngx_accept_disabled 减1进行均衡操作同时,将accept事件从当前进程epoll中清除。这样epoll当前循环只处理自己的普通connection事件。当然,左侧路径可能执行多次,ngx_disable_accept_events操作只需要执行一次即可。

如果过了一段时间,该进程负载降低,进入右侧路径,在申请accept锁的函数中ngx_trylock_accept_mutex中,申请加锁成功后,会调用ngx_enable_accept_events将accept事件再次加入到epoll中,这样就可以监听accept事件和普通connection事件了。

以上补充部分为个人理解,有错误之处,欢迎指正。

 

四、多进程(每个进程单线程)高效的原因

 一点思考:

1.master/worker多进程模式,保证了系统的稳定。master对多个worker子进程和其他子进程的管理比较方便。由于一般worker进程数与cpu内核数一致,所以不存在大量的子进程生成和管理任务,避免了大量子进程的数据IPC共享开销和切换竞争开销。各worker进程之间也只是重复拷贝了监听字,除了父子进程间传递控制消息,基本没有IPC需求。

2.每个worker单线程,不存在大量线程的生成和同步开销。

以上两个方面都使Nginx避免了过多的同步、竞争、切换和IPC数据传递,即尽可能把cpu从不必要的计算开销中解放出来,只专注于业务计算和流程处理。

解放了CPU之后,就是内存的高效操作了。像cache_manager_process,内存池ngx_pool_t等等。还有可以设置进程的affinity来绑定cpu单个内核等。

这样的模型更简单,大连接量扩展性更好。

 

“伟大的东西,总是简单的”,此言不虚。

 

 

注:引用本人文章请注明出处,谢谢。

转载于:https://www.cnblogs.com/NerdWill/p/4992345.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/404667.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[react] useState和this.state的区别是什么?

[react] useState和this.state的区别是什么? useState内部基于 useReducer 实现,方法返回 state 本身以及一个修改 state 的方法。 通过 setXXX 修改数据,不会和 setState 一样进行对象属性合并,会直接覆盖。Hooks 函数组件中&a…

h264检测是I帧还是P帧

From: http://blog.csdn.net/zgyulongfei/article/details/7558031 今天在网上找了一些资料,知道了如何检测h264中的帧类型,在这里记录下来。 首先,贴出nal单元类型定义(图从《新一代视频压缩编码标准H.264》摘录)&am…

经典排序算法 - 鸡尾酒排序Cocktail sort

经典排序算法 - 鸡尾酒排序Cocktail sort 鸡尾酒排序基于冒泡排序,双向循环 还是看例子吧,给定待排数组[2 3 4 5 1] 第一趟过去时的每一步 第一步迭代,2 < 3不换 [2 3 4 5 1] 第二步迭代,3 < 4不换 [2 3 4 5 1] 第三步迭代,4 < 5不换 [2 3 4 5 1] 第四步迭代,5 > 1…

[react] 举例说明在react中怎么使用样式

[react] 举例说明在react中怎么使用样式 all in js 个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

C#之out和ref区别

out与ref的区别总结&#xff1a;1.两者都是通过引用来传递。2.两者都按地址传递的&#xff0c;使用后都将改变原来参数的数值。3.属性不是变量&#xff0c;因此不能作为 out或ref 参数传递。4.若要使用 ref 或 out,方法定义和调用方法都必须显式使用 out、ref 关键字。5.rel可以…

一次ssh登录不成功的解决经历

一、列出解决过程中所有报错信息 ssh connection refused port 22Stopped OpenBSD Secure Shell server. Failed to start OpenBSD Secure Shell server.OpenSSL version mismatch. Built against 1010104f, you have 101000cf Unable to fetch some archives, maybe run apt-…

IOS自动化打包介绍

摘要 随着苹果手持设备用户的不断增加&#xff0c;ios应用也增长迅速&#xff0c;同时随着iphone被越狱越来越多的app 的渠道也不断增多&#xff0c;为各个渠道打包成了一件费时费力的工作&#xff0c;本文提供一种比较智能的打包方式来减少其带来的各种不便。 TAG Ios打包&…

I帧和IDR帧区别

From: http://blog.csdn.net/skygray/article/details/6223358 I 帧和 IDR 帧的区别&#xff1a; IDR 帧属于 I 帧。解码器收到 IDR frame 时&#xff0c;将所有的参考帧队列丢弃 &#xff08;用x264_reference_reset 函数实现——在 encoder.c 文件中&#xff09; 。这点是所…

HDU 3486 Interviewe RMQ

题意&#xff1a; 将\(n\)个数分成\(m\)段相邻区间&#xff0c;每段区间的长度为\(\left \lfloor \frac{n}{m} \right \rfloor\)&#xff0c;从每段区间选一个最大值&#xff0c;要让所有的最大值之和大于\(k\)。求最小的\(m\)。 分析&#xff1a; 预处理RMQ&#xff0c;维护区…

[react] 举例说明useState

[react] 举例说明useState import { useState } from react; const [count, setCount] useState(0); function Demo() {const doAdd1 () > {setCount(prevCount 1);};const doAdd2 () > {setCount(prevCount > prevCount 1);};return <><h1>{count}…

win10 vscode 无法激活python 虚拟环境的解决办法

一、powershell中 python创建虚拟环境无法激活 二、管理员模式运行powershell&#xff0c;执行策略更改&#xff1a; Set-ExecutionPolicy RemoteSigned&#xff0c;输入y 三、vscode再次激活&#xff1a; .\flask-venv\Scripts\activate 激活成功。 四、退出虚拟环境&#x…

客户端获取游客IP,获取客户地理信息,展示地图

参考自&#xff1a;http://www.cnblogs.com/phphuaibei/archive/2011/09/08/2171903.html 三维地图&#xff0c;目前只有都市圈和E都市有提供。 Google街景可以在浏览器安装google earth插件后显示。转载于:https://blog.51cto.com/icersummer/724481

vscode 升级过后自带的四种终端

一、版本 二、终端 自带了四种默认配置终端&#xff0c;删除以前Edit in settings.json的“terminal.integrated.shell.windows”字段。 四种默认终端&#xff1a; powershellwslcmdjavaScript Debug Terminal

[react] 使用ES6的class定义的组件不支持mixins了,那用什么可以替代呢?

[react] 使用ES6的class定义的组件不支持mixins了&#xff0c;那用什么可以替代呢&#xff1f; HOC 高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象。最常见的可能是 Redux 的 connect 函数。除了简单分享工具库和简单的组合&#xf…

接口委托实现

1 TFMDesign class(TFMBase, IDataSetOperator)2 public3 //注意名称要不一样,重新定义保存方法4 procedure IDataSetOperator.DoApplyUpdates ApplyUpdates;5 6 //重写保存方法7 procedure ApplyUpdates; stdcall;8 9 //接口用对象10 property …

2015第19本:异类--不一样的成功启示录

一位移民加拿大的高中同学在2012年回国探亲&#xff0c;聚会时曾推荐了《异类--不一样的成功启示录》这本书&#xff0c;英文书名叫《Outliers - the story of success》&#xff0c;一直没有系统地看完。在整理Omnifocus的读书列表时又发现了此书&#xff0c;还是趁这个机会把…

windows10 安装mqtt服务器和client客户端进行本地调试

一、安装mqtt服务器 使用emqx作为mqtt服务器&#xff0c;下载emqx-windows-4.3.8.zip。 emqx-windows-4.3.8.zip 其他版本&#xff1a;Directory listing for broker: / | EMQ 解压到自定义目录位置&#xff0c;在cmd窗口进入解压后的bin目录 cd /d D:\Tools\exqxServer\em…

I,P,B帧和PTS,DTS的关系

From: http://www.cnblogs.com/qingquan/archive/2011/07/27/2118967.html 基本概念&#xff1a; I frame &#xff1a;帧内编码帧 又称intra picture&#xff0c;I 帧通常是每个 GOP&#xff08;MPEG 所使用的一种视频压缩技术&#xff09;的第一个帧&#xff0c;经过适度地压…

[react] 为何说虚拟DOM会提高性能?

[react] 为何说虚拟DOM会提高性能&#xff1f; 虚拟dom相当于在js和真实dom中间加了一个缓存&#xff0c;利用dom diff算法避免了没有必要的dom操作&#xff0c;从而提高性能 个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定…

不断学习才能进步!

51CTO确实是一个很不错的技术性很强的社交网络&#xff0c;在这里我发现有很多东西值得我去学习&#xff0c;只不过现在从事教师职业&#xff0c;没有太多的时间。但我只要有时间就会来学习的&#xff0c;希望大家能够多多帮助&#xff01;转载于:https://blog.51cto.com/anday…