免费空间申请网站,免费做网站公司ydwzjs,中国科技大学,先做网站装修还是先买虚拟主机Nginx使用多进程的方法进行任务处理#xff0c;每个worker进程只有一个线程#xff0c;单线程循环处理全部监听的事件。本文重点分析一下多进程间的负载均衡问题以及Nginx多进程事件处理流程#xff0c;方便大家自己写程序的时候借鉴。 一、监听建立流程 整个建立监听socket…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)主要完成的工作包括基本的创建socketsetsockoptbind和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开始循环处理eventstimers。最终调用的是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可以是mailhttp和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函数会遭遇“惊群”问题返回错误errnoEAGAIN直接返回个人觉得这里有改进的空间见补充部分。 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_acceptsrc/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 / 88 - 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