那谁的技术博客

感兴趣领域:高性能服务器编程,存储,算法,Linux内核
随笔 - 210, 文章 - 0, 评论 - 1183, 引用 - 0
数据加载中……

Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡

紧跟着上一节所讲的,Nginx里面worker子进程之间相互独立,各自接收新的连接,接收数据报文,处理请求,等等。

但是,由于监听socket是由父进程创建的,所以在子进程调用accept函数接收新的连接时可能会出现所谓的惊群(thundering herd), 其实这个现象即使出现,我个人认为对性能的影响也不会太大,不就是一次accept操作失败么。另外呢,子进程之间虽然是独立工作的,但是,也可能出现不同的子进程之间负载不均衡的情况,比如某些子进程处理1000个连接,有的子进程处理100个连接,差了十倍。

Nginx里面,针对上面提到的两点,对程序进行了一些优化。来看看ngx_process_events_and_timers 函数里面的代码,这个函数是worker子进程中服务器主循环每次循环都会调用的函数:

    // 如果使用了accept mutex 
   
if (ngx_use_accept_mutex) {
        // 如果当前禁止accept操作的计数还大于0
        
if (ngx_accept_disabled > 0) {
            // 将这个计数减一
            ngx_accept_disabled
--;

        } 
else {
            // 尝试获取accept锁
            
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                
return;
            }
          
            // 如果获取到了accept锁
            
if (ngx_accept_mutex_held) {
                flags 
|= NGX_POST_EVENTS;

            } 
else {
                // 否则, 如果满足下面两个条件,将定时器置为ngx_accept_mutex_delay, 这个值是一个配置参数指定的
                
if (timer == NGX_TIMER_INFINITE
                    
|| timer > ngx_accept_mutex_delay)
                {
                    timer 
= ngx_accept_mutex_delay;
                }
            }
        }
    }

    // 记下当前时间点
    delta 
= ngx_current_msec;

    //  处理事件
    (
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);

    // 如果有accept事件发生, 就去处理accept事件,其实最后就是调用accept函数接收新的连接
    
if (ngx_posted_accept_events) {
        ngx_event_process_posted(cycle, 
&ngx_posted_accept_events);
    }

    // 已经处理完接收新连接的事件了,如果前面获取到了accept锁,那就解锁
    
if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(
&ngx_accept_mutex);
    }

    
if (delta) {
        ngx_event_expire_timers();
    }

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle
->log, 0,
                   
"posted events %p", ngx_posted_events);

    // 除了accepet事件之外的其他事件放在这个队列中, 如果队列不为空,就去处理相关的事件
    
if (ngx_posted_events) {
        
if (ngx_threaded) {
            ngx_wakeup_worker_thread(cycle);

        } 
else {
            ngx_event_process_posted(cycle, 
&ngx_posted_events);
        }
    }

上面的代码中,ngx_accept_disabled变量是在函数ngx_event_accept中被赋值的:
        ngx_accept_disabled = ngx_cycle->connection_n / 8
                              
- ngx_cycle->free_connection_n;
简单的理解,当前已经接收的连接(connection_n)越多,可用的空闲连接(free_connection_n)越少,这个值越大。

所以呢,假如某个子进程,当前已经接收了一定数量的连接,那么这个计数值就会相应的大,也就是说,这个子进程将会暂缓(注意,不是暂停)接收新的连接,而去专注于处理当前已经接收的连接;反之,如果某子进程当前处理的连接数量少,那么这个计数就会相应的小,于是这个较为空闲的子进程就会更多的接收新的连接请求。这个策略,确保了各个worker子进程之间负载是相对均衡的,不会出现某些子进程接收的连接特别多,某些又特别少。

同样的,在这段代码中,看到了即使现在子进程可以接收新的连接,也需要去竞争以获取accept锁,只有获得了这个锁才能接收新的连接,所以避免了前面提到的惊群现象的发生。

另外,在linux的新版内核中,加入了一个所谓的"cpu affinity"的特性,利用这个特性,你可以指定哪些进程只能运行在哪些CPU上面,我对这个不太熟悉,查看了一些资料,提到的说法是,有了这个特性,比如进程A在CPU1上面运行,那么在CPU1上面会cache一些与该进程相关的数据,那么再次调用的时候性能方面就会高效一些,反之,如果频繁的切换到不同的CPU上面去运行,那么之前在别的CPU上面的cache就会失效。

Nginx里面针对Linux内核的这个特性做了一些优化,它也可以在配置文件中指定哪些子进程可以运行在哪些CPU上面,这个优化与配置选项worker_cpu_affinity有关。可以在 http://wiki.nginx.org/NginxCoreModule 找到关于这个配置相关的信息。


posted on 2009-12-08 21:48 那谁 阅读(7003) 评论(7)  编辑 收藏 引用 所属分类: 服务器设计Nginx

评论

# re: Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡  回复  更多评论   

很好,不错,
没研究过nginx, 根据你的描述有两个问题问一下
1. 主进程listen, listen_fd 可读了,通知子进程,或者子进程自己能检测到
如果子进程正在处理事件呢?好,就算全都是非阻塞的,那么循环很快转
回来,处理accept或检查listen_fd是否可读,那这样就不算是事件驱动
了,每次都是自己主动是检测?
2. 如果子进程没有事件怎么样?会不会空转? 睡眠机制是什么?
2009-12-09 11:45 | cui

# re: Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡  回复  更多评论   

你的http://code.google.com/p/commoncache/wiki/demo这个工程,代码高亮是怎么弄的呀???
2009-12-09 21:41 | hlysh

# re: Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡  回复  更多评论   

http://code.google.com/p/commoncache/wiki/demo
2009-12-09 21:41 | hlysh

# re: Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡  回复  更多评论   

@cui
1)父进程不用通知子进程,因为子进程是继承了从父进程里创建的监听socket
2)没有事件的时候子进程将会阻塞在select/poll操作上。
2009-12-09 23:14 | 那谁

# re: Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡  回复  更多评论   

@hlysh
http://code.google.com/p/support/wiki/WikiSyntax#Code
2009-12-09 23:15 | 那谁

# re: Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡  回复  更多评论   

看了下代码,所有的work子进程都可以accept 父进程创建的listenning socket?
很少见过这样的用法阿
wirelesser[at]hotmail.com
2010-04-13 17:51 | ww

# re: Nginx0.7.61代码分析(二)--worker子进程之间的负载均衡[未登录]  回复  更多评论   

http://simohayha.javaeye.com/说得好
2010-07-22 16:08 | 111

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理