前言
nginx采用多進程的模,當一個請求過來的時候,系統會對進程進行加鎖操作,保證只有一個進程來接受請求。
本文基于Nginx 0.8.55源代碼,并基于epoll機制分析
1. accept鎖的實現
1.1 accpet鎖是個什么東西
提到accept鎖,就不得不提起驚群問題。
所謂驚群問題,就是指的像Nginx這種多進程的服務器,在fork后同時監聽同一個端口時,如果有一個外部連接進來,會導致所有休眠的子進程被喚醒,而最終只有一個子進程能夠成功處理accept事件,其他進程都會重新進入休眠中。這就導致出現了很多不必要的schedule和上下文切換,而這些開銷是完全不必要的。
而在Linux內核的較新版本中,accept調用本身所引起的驚群問題已經得到了解決,但是在Nginx中,accept是交給epoll機制來處理的,epoll的accept帶來的驚群問題并沒有得到解決(應該是epoll_wait本身并沒有區別讀事件是否來自于一個Listen套接字的能力,所以所有監聽這個事件的進程會被這個epoll_wait喚醒。),所以Nginx的accept驚群問題仍然需要定制一個自己的解決方案。
accept鎖就是nginx的解決方案,本質上這是一個跨進程的互斥鎖,以這個互斥鎖來保證只有一個進程具備監聽accept事件的能力。
實現上accept鎖是一個跨進程鎖,其在Nginx中是一個全局變量,聲明如下:
ngx_shmtx_t ngx_accept_mutex;
這是一個在event模塊初始化時就分配好的鎖,放在一塊進程間共享的內存中,以保證所有進程都能訪問這一個實例,其加鎖解鎖是借由linux的原子變量來做CAS,如果加鎖失敗則立即返回,是一種非阻塞的鎖。加解鎖代碼如下:
static ngx_inline ngx_uint_t ngx_shmtx_trylock(ngx_shmtx_t *mtx) { return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)); } #define ngx_shmtx_lock(mtx) ngx_spinlock((mtx)->lock, ngx_pid, 1024) #define ngx_shmtx_unlock(mtx) (void) ngx_atomic_cmp_set((mtx)->lock, ngx_pid, 0)
可以看出,調用ngx_shmtx_trylock失敗后會立刻返回而不會阻塞。
1.2 accept鎖如何保證只有一個進程能夠處理新連接
要解決epoll帶來的accept鎖的問題也很簡單,只需要保證同一時間只有一個進程注冊了accept的epoll事件即可。
Nginx采用的處理模式也沒什么特別的,大概就是如下的邏輯:
嘗試獲取accept鎖
if 獲取成功:
在epoll中注冊accept事件
else:
在epoll中注銷accept事件
處理所有事件
釋放accept鎖
當然這里忽略了延后事件的處理,這部分我們放到后面討論。
新聞熱點
疑難解答