Nginx——事件驱动机制(雷霆追风问题,负载均衡)
事件处理框架
所有的worker进程都在ngx_worker_process_cycle方法中循环处理事件,处理分发事件则在ngx_worker_process_cycle方法中调用ngx_process_events_and_timers方法,循环调用该方法就是 在处理全部事件,这正是事件驱动机制的核心。该方法既会处理普通的网络事件,也会处理定时器事件。
ngx_process_events_and_timers方法中核心操作主要有下面3个:
1) 调用所使用事件驱动模块实现的process_events方法。处理网络事件
2) 处理两个post事件队列中的事件,实际上就是分别调用ngx_event_process_posted(cycle, &ngx_posted_accept_events)和ngx_event_process_posted(cycle,&ngx_posted_events)方法
3) 处理定时事件,实际上就是调用ngx_event_expire_timers()方法
以下是ngx_process_events_and_timers方法中的时间框架处理流程图以及源码,能够结合理解:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2Fsa2Vya2Fscg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
源码例如以下:
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
/*假设配置文件里使用了timer_resolution配置项,也就是ngx_timer_resolution值大于0。
则说明用户希望server时间精度为ngx_timer_resolution毫秒。这时。将ngx_process_changes
的timer參数设为-1。告诉ngx_process_change方法在检測时间时不要等待。直接搜集全部已经
就绪的时间然后返回;同一时候将flag參数初始化为0,它是在告诉ngx_process_changes没有不论什么附加
动作*/
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0; } else {
/*假设没有使用timer_resolution,那么将调用ngx_event_find_timer()方法,获取近期一个将要
触发的时间距离如今有多少毫秒,然后把这个值赋予timer參数。告诉ngx_process_change方法在
检測事件时假设没有不论什么事件。最多等待timer毫秒就返回;将flag參数设置为UPDATE_TIME,告诉
ngx_process_change方法更新换成的时间*/
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME; #if (NGX_THREADS) if (timer == NGX_TIMER_INFINITE || timer > 500) {
timer = 500;
} #endif
}
/*ngx_use_accept_mutex表示是否须要通过对accept加锁来解决惊群问题。 当nginx worker进程数>1时且配置文件里打开accept_mutex时。这个标志置为1 */
if (ngx_use_accept_mutex) {
/*ngx_accept_disabled表示此时满负荷,不是必需再处理新连接了,
我们在nginx.conf以前配置了每个nginx worker进程可以处理的最大连接数。
当达到最大数的7/8时。ngx_accept_disabled为正。说明本nginx worker进程很繁忙,
将不再去处理新连接,这也是个简单的负载均衡 */
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--; } else {
/*获得accept锁,多个worker仅有一个可以得到这把锁。获得锁不是堵塞过程,
都是立马返回,获取成功的话ngx_accept_mutex_held被置为1。拿到锁,意味
着监听句柄被放到本进程的epoll中了。假设没有拿到锁,则监听句柄会被
从epoll中取出。*/
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
/*拿到锁的话,置flag为NGX_POST_EVENTS,这意味着ngx_process_events函数中,
不论什么事件都将延后处理。会把accept事件都放到ngx_posted_accept_events链表中,
epollin | epollout事件都放到ngx_posted_events链表中 */
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS; } else {
/*获取锁失败。意味着既不能让当前worker进程频繁的试图抢锁,也不能让它经过太长事件再去抢锁
以下的代码:即使开启了timer_resolution时间精度。牙须要让ngx_process_change方法在没有新
事件的时候至少等待ngx_accept_mutex_delay毫秒之后再去试图抢锁 而没有开启时间精度时,
假设近期一个定时器事件的超时时间距离如今超过了ngx_accept_mutex_delay毫秒,也要把timer设
置为ngx_accept_mutex_delay毫秒,这是由于当前进程尽管没有抢到accept_mutex锁,但也不能让
ngx_process_change方法在没有新事件的时候等待的时间超过ngx_accept_mutex_delay,这会影响
整个负载均衡机制*/
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
//计算ngx_process_events消耗的时间
delta = ngx_current_msec;
//linux下,调用ngx_epoll_process_events函数開始处理
(void) ngx_process_events(cycle, timer, flags);
//函数处理消耗时间
delta = ngx_current_msec - delta; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"timer delta: %M", delta);
//假设ngx_posted_accept_events链表有数据,就開始accept建立新连接
if (ngx_posted_accept_events) {
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
}
//释放锁后再处理以下的EPOLLIN EPOLLOUT请求
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
//假设ngx_process_events消耗的时间大于0,那么这时可能有新的定时器事件触发
if (delta) {
//处理定时事件
ngx_event_expire_timers();
} ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"posted events %p", ngx_posted_events);
//ngx_posted_events链表中有数据,进行处理
if (ngx_posted_events) {
if (ngx_threaded) {
ngx_wakeup_worker_thread(cycle); } else {
ngx_event_process_posted(cycle, &ngx_posted_events);
}
}
}
惊群问题
在建立连接的时候。Nginx处于充分发挥多核CPU架构性能的考虑。使用了多个worker子进程监听同样port的设计,这样多个子进程在accept建立新连接时会有争抢,这会带来的“惊群”问题。子进程数量越多越明显,这会造成系统性能的下降。
master进程開始监听Webport,fork出多个worker子进程,这些子进程同一时候监听同一个Webport。普通情况下,有多少CPU核心就有配置多少个worker子进程。这样全部的worker子进程都在承担着Webserver的角色。从而发挥多核机器的威力。如果如今没实用户连入server。某一时刻恰好全部的子进程都休眠且等待新连接的系统调用,这时有一个用户向server发起了连接,内核在收到TCP的SYN包时。会激活全部的休眠worker子进程。终于仅仅有最先開始运行accept的子进程能够成功建立新连接,而其它worker子进程都将accept失败。
这些accept失败的子进程被内核唤醒是不必要的,他们被唤醒会的运行非常可能是多余的,那么这一时刻他们占用了本不须要占用的资源,引发了不必要的进程切换,添加了系统开销。
非常多操作系统的最新版本号的内核已经在事件驱动机制中攻克了惊群问题,但Nginx作为可移植性极高的webserver。还是在自身的应用层面上较好的攻克了这一问题。既然惊群是个多子进程在同一时刻监听同一个port引起的。那么Nginx的解决方法也非常easy,它规定了同一时刻仅仅能有唯一一个worker子进程监听Webport,这样就不会发生惊群了。此时新连接时间就仅仅能唤醒唯一正在监听port的worker子进程。
怎样限制在某一时刻仅能有一个子进程监听webport呢?在打开accept_mutex锁的情况下。仅仅有调用ngx_trylock_accept_mutex方法后。当前的worker进程才会去试着监听webport。
该方法详细实现例如以下:
ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
/*
使用进程间的同步锁,试图获取accept_mutex。注意,ngx_trylock_accept_mutex返回1表示成功
拿到锁,返回0表示获取锁失败。这个获取所的过程是非堵塞的。此时一旦锁被其它worker子进程占
用,该方法会立马返回。 */
if (ngx_shmtx_trylock(&ngx_accept_mutex)) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex locked");
/*假设获取到accept_mutex锁。但ngx_accept_mutex_held为1,则立马返回。 ngx_accept_mutex_held
是一个标志位,当它为1时,表示当前进程已经获取到锁了*/
if (ngx_accept_mutex_held
&& ngx_accept_events == 0
&& !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
{
//ngx_accept_mutex锁之前已经获取到了。立马返回
return NGX_OK;
}
//将全部监听连接的事件加入到当前epoll等事件驱动模块中
if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
/*既然将监听句柄加入到事件驱动模块失败,就必须释放ngx_accept_mutex锁*/
ngx_shmtx_unlock(&ngx_accept_mutex);
return NGX_ERROR;
}
/*经过ngx_enable_accept_events方法的调用。当前进程的时间驱动模块已经開始监
听全部的port,这时须要把ngx_accept_mutex_heald标志置为1,方便本进程的其它模
块了解它眼下已经获取到了锁*/
ngx_accept_events = 0;
ngx_accept_mutex_held = 1; return NGX_OK;
}
/*假设ngx_shmtx_trylock返回0,则表明获取ngx_accept_mutex锁失败,这时假设
ngx_accept_mutex_held标志还为1,即当前进程还在获取到锁的状态,这显然不对,须要处理*/
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"accept mutex lock failed: %ui", ngx_accept_mutex_held); if (ngx_accept_mutex_held) {
/*ngx_disable_accept_events(会将全部监听连接的读事件从事件驱动模块中移除*/
if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
return NGX_ERROR;
}
/*在没有获取到ngx_accept_mutex锁时,必须把ngx_accept_mutex_held置为0*/
ngx_accept_mutex_held = 0;
} return NGX_OK;
}
在上面的代码中,ngx_accept_mutex是进程间的同步锁(见http://blog.csdn.net/walkerkalr/article/details/38237147),ngx_accept_mutex_held是当前进程的一个全局变量。他们的定义例如以下:
ngx_shmtx_t ngx_accept_mutex;
ngx_uint_t ngx_accept_mutex_held;
因此。在调用ngx_try_accept_mutex方法后,假设没有获取到锁。当前进程调用process_events时仅仅能处理已有连接上的事件。
假设唯一获取到锁且其epoll等事件驱动模块開始监控webport上的新连接事件。这种情况下,调用process_events时就会既处理已有连接上的事件,也处理新连接的事件,但这种话,什么时候释放ngx_accept_mutex锁呢?假设等到这批事件所有运行完。因为这个worker进程上可能有非常多活跃的连接,处理这些连接上的事件会占用非常长时间,也就是说。会非常长时间都没有释放ngx_accept_mutex锁,这样,其它worker进程就非常难得到处理新连接的机会。
怎样解决长时间占用ngx_accept_mutex的问题呢?这就要依靠ngx_posted_accept_events队列(存放新连接事件的队列)和ngx_posted_events队列(存放普通事件的队列)。实际上ngx_posted_accepted_events队列和ngx_posted_events队列把事件进行了归类,以使先处理ngx_posted_accept_events队列中的事件,处理完后就要释放ngx_accept_mutex锁,接着再处理ngx_posted_events队列中的时间,这样就大大降低了ngx_accept_mutex锁占用的时间。
负载均衡
在建立连接时。在多个子进程争抢处理一个新连接时间时,一定仅仅有一个worker子进程终于会成功简历连接。随后,它会一直处理这个连接直到连接关闭。那么,假设有的子进程非常勤奋。他们抢着建立并处理了大部分连接,而有的子进程则运气不好。仅仅处理了少量连接,这对多核CPU架构下的应用是非常不利的。由于子进程之间应该是平等的。每一个子进程应该尽量独占一个CPU核心。子进程间负载不均衡,必然会影响整个服务的性能。
与惊群问题的解决方法一样。仅仅有打开了accept_mutex锁,才干实现子进程间的负载均衡。在这里。初始化了一个全局变量ngx_accept_disabled。他就是负载均衡机制实现的关键阈值。实际上它就是一个整型数据。
ngx_int_t ngx_accept_disabled;
这个阈值是与连接池中连接的使用密切相关的,在建立连接时会进行赋值,例如以下所看到的
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
因此。在启动时该阈值是一个负值。其绝对值是连接总数的7/8。事实上ngx_accept_disabled的使用方法非常easy,当它为负数时,不会触发负载均衡操作,正常获取accept锁。试图处理新连接。
而当ngx_accept_disabled是正数时,就会触发Nginx进行负载均衡操作了。nginx此时不再处理新连接事件,取而代之的不过ngx_accept_disabled值减1,。这表示既然经过一轮事件处理。那么相对负载肯定有所减小,所以要对应调整这个值。例如以下所看到的
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
//调用ngx_trylock_accept_mutex方法,尝试获取accept锁
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
Nginx各worker子进程间的负载均衡仅在某个worker进程处理的连接数达到它最大处理总数的7/8时才会触发。这时该worker进程就会降低处理新连接的机会,这样其它较空暇的worker进程就有机会去处理很多其它的新连接。以达到整个webserver的均衡效果。
版权声明:本文博主原创文章,博客,未经同意不得转载。
Nginx——事件驱动机制(雷霆追风问题,负载均衡)的更多相关文章
- nginx的概念与几种负载均衡算法
Nginx的背景 Nginx和Apache一样都是一种WEB服务器.基于REST架构风格,以URI(Uniform Resources Identifier,统一资源描述符)或URL(Uniform ...
- nginx之rewrite重写,反向代理,负载均衡
rewrite重写(伪静态): 在地址栏输入xx.com/user-xxx.html, 实际上访问的就是xxx.com/user.php?id=xxx rewrite就这么简单 附上ecshop re ...
- (转载)Nginx/LVS/HAProxy三种主流负载均衡软件的对比
原地址:http://www.ha97.com/5646.html PS:Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件,本人都在多个项目中实施过,参考了一些资料,结合自己的一些 ...
- 在Linux上使用Nginx为Solr集群做负载均衡
在Linux上使用Nginx为Solr集群做负载均衡 在Linux上搭建solr集群时需要用到负载均衡,但测试环境下没有F5 Big-IP负载均衡交换机可以用,于是先后试了weblogic的proxy ...
- nginx作反向代理,实现负载均衡
nginx作反向代理,实现负载均衡按正常的方法安装好 ngixn,方法可参考http://www.cnblogs.com/lin3615/p/4376224.html其中作了反向代理的服务器的配置如下 ...
- LVS + keepalived + nginx + tomcat 实现主从热备 + 负载均衡
前言 首先声明下,由于这两天找资料,看了不少博客 ,但是出于不细心,参考者的博客地址没有记录下来,所有文中要是出现了与大家博客相同的地方,那么请大家在评论区说明并附上博客地址,我好引用进来:这里表示抱 ...
- Nginx + Memcached 实现Session共享的负载均衡
session共享 我们在做站点的试试,通常需要保存用户的一些基本信息,比如登录就会用到Session:当使用Nginx做负载均衡的时候,用户浏览站点的时候会被分配到不同的服务器上,此时如果登录后Se ...
- 使用nginx sticky实现基于cookie的负载均衡
在多台后台服务器的环境下,我们为了确保一个客户只和一台服务器通信,我们势必使用长连接.使用什么方式来实现这种连接呢,常见的有使用nginx自带的ip_hash来做,我想这绝对不是一个好的办法,如果前端 ...
- 用Nginx搭建IIS集群实现负载均衡
长话短说,我们用Nginx来搭建一个简单的集群,实现Web应用的负载均衡,架构图如下: 两台Web服务器,一台静态资源服务器,因为是演示,我们以网站形式部署在本机IIS中 一台Nginx代理服务器,安 ...
随机推荐
- Qt 的内部进程通信机制
Qt 的内部进程通信机制 续欣 (xxin76@hotmail.com), 博士.大学讲师 2004 年 4 月 01 日 Qt 作为一种跨平台的基于 C++ 的 GUI 系统,能够提供给用户构造图形 ...
- 【前段开发】10步掌握CSS定位: position static relative absolute float
希望能帮到须要的人,转自:http://www.see-design.com.tw/i/css_position.html 1. position:static 元素的 position 屬性默認值為 ...
- VMwave下Ubuntu扩展磁盘空间
VMwave下Ubuntu扩展磁盘空间 Ubuntu原预装磁盘空间20G,随着系统安装软件越来越多,空间慢慢不够用. 打算安装DB2,许扩展磁盘空间. 环境: No LSB modules are a ...
- CS0433: 类型“BasePage”同一时候存在于“c:\Windows\Microsoft.NETxxxxxxxxxxxxxxxx
网上常见的我就不说了. 假设其他地址的方法解决不了你的问题,那么请往下看. 该类是否存放于 App_Code 下,假设是把该类从App_Code中拉出来,然后再次执行试试.
- Linux Shell 之 我的第一个Shell程序
这里我首先会介绍一个Shell是什么,再介绍我的第一个Shell程序和从中总结的经验. 一.Shell是什么 在说我的这个Shell程序之前,还是先跟大家说说什么是Shell吧,相信Shell这个 ...
- make工具与Makefile文件
make工具与Makefile文件 阅读目录 1. make工具 2. Makefile文件 3. Makefile的简单示例 4. 伪目标 5. Makefile 自动化变量 6. 编译生成多个可执 ...
- 墙体裂缝推荐的情况下驱动的PhoneGap入门,早看早收据
清华大学出版社推出<构建跨平台APP:PhoneGap移动应用实战> 零门槛的学习APP发展 刮 进步 20以上示范样本APP 3项目APP 台à跨终端à移动开发 完美生命周期:搭建好开发 ...
- JDBC/XML的一些基本使用
原文:JDBC/XML的一些基本使用 一.知识点题目:JDBC核心API的使用 关键字:JDBC核心API 内容: 1)加载JDBC驱动: Oracle:Class.forName(“oracle.j ...
- 蓝桥杯 【dp?】.cpp
题意: 给出一个2*n的方格,当刷完某一个方格的漆后可以且只可以走到相邻的任何一格,即上 下 左 右 左上 左下 右上 右下.可以从任意一个格子开始刷墙,问有多少种刷法,因为随着n的增大方案数会变多, ...
- 关于split与StringTokenizer的理解
关于split与StringTokenizer的理解 一.split 依据匹配给定的正則表達式来拆分此字符串.此方法返回的数组包括此字符串的子字符串,每一个子字符串都由还有一个匹配给定表达式的子 ...