Nginx0.7.61程式碼分析(二)--worker子程序之間的負載均衡
阿新 • • 發佈:2018-12-27
緊跟著上一節所講的,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 找到關於這個配置相關的資訊。
但是,由於監聽socket是由父程序建立的,所以在子程序呼叫accept函式接收新的連線時可能會出現所謂的驚群(thundering herd), 其實這個現象即使出現,我個人認為對效能的影響也不會太大,不就是一次accept操作失敗麼。另外呢,子程序之間雖然是獨立工作的,但是,也可能出現不同的子程序之間負載不均衡的情況,比如某些子程序處理1000個連線,有的子程序處理100個連線,差了十倍。
Nginx裡面,針對上面提到的兩點,對程式進行了一些優化。來看看ngx_process_events_and_timers 函式裡面的程式碼,這個函式是worker子程序中伺服器主迴圈每次迴圈都會呼叫的函式:
// 如果使用了accept mutex
// 如果當前禁止accept操作的計數還大於0
if (ngx_accept_disabled >0) {
// 將這個計數減一
ngx_accept_disabled--;
} else {
// 嘗試獲取accept鎖
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
// 如果獲取到了accept鎖
flags |= NGX_POST_EVENTS;
} else {
// 否則, 如果滿足下面兩個條件,將定時器置為ngx_accept_mutex_delay, 這個值是一個配置引數指定的
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer
}
}
}
}
// 記下當前時間點
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 找到關於這個配置相關的資訊。