前言
nginx采用多進(jìn)程的模,當(dāng)一個(gè)請(qǐng)求過來的時(shí)候,系統(tǒng)會(huì)對(duì)進(jìn)程進(jìn)行加鎖操作,保證只有一個(gè)進(jìn)程來接受請(qǐng)求。
本文基于Nginx 0.8.55源代碼,并基于epoll機(jī)制分析
1. accept鎖的實(shí)現(xiàn)
1.1 accpet鎖是個(gè)什么東西
提到accept鎖,就不得不提起驚群?jiǎn)栴}。
所謂驚群?jiǎn)栴},就是指的像Nginx這種多進(jìn)程的服務(wù)器,在fork后同時(shí)監(jiān)聽同一個(gè)端口時(shí),如果有一個(gè)外部連接進(jìn)來,會(huì)導(dǎo)致所有休眠的子進(jìn)程被喚醒,而最終只有一個(gè)子進(jìn)程能夠成功處理accept事件,其他進(jìn)程都會(huì)重新進(jìn)入休眠中。這就導(dǎo)致出現(xiàn)了很多不必要的schedule和上下文切換,而這些開銷是完全不必要的。
而在Linux內(nèi)核的較新版本中,accept調(diào)用本身所引起的驚群?jiǎn)栴}已經(jīng)得到了解決,但是在Nginx中,accept是交給epoll機(jī)制來處理的,epoll的accept帶來的驚群?jiǎn)栴}并沒有得到解決(應(yīng)該是epoll_wait本身并沒有區(qū)別讀事件是否來自于一個(gè)Listen套接字的能力,所以所有監(jiān)聽這個(gè)事件的進(jìn)程會(huì)被這個(gè)epoll_wait喚醒。),所以Nginx的accept驚群?jiǎn)栴}仍然需要定制一個(gè)自己的解決方案。
accept鎖就是nginx的解決方案,本質(zhì)上這是一個(gè)跨進(jìn)程的互斥鎖,以這個(gè)互斥鎖來保證只有一個(gè)進(jìn)程具備監(jiān)聽accept事件的能力。
實(shí)現(xiàn)上accept鎖是一個(gè)跨進(jìn)程鎖,其在Nginx中是一個(gè)全局變量,聲明如下:
ngx_shmtx_t ngx_accept_mutex;
這是一個(gè)在event模塊初始化時(shí)就分配好的鎖,放在一塊進(jìn)程間共享的內(nèi)存中,以保證所有進(jìn)程都能訪問這一個(gè)實(shí)例,其加鎖解鎖是借由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)
可以看出,調(diào)用ngx_shmtx_trylock失敗后會(huì)立刻返回而不會(huì)阻塞。
1.2 accept鎖如何保證只有一個(gè)進(jìn)程能夠處理新連接
要解決epoll帶來的accept鎖的問題也很簡(jiǎn)單,只需要保證同一時(shí)間只有一個(gè)進(jìn)程注冊(cè)了accept的epoll事件即可。
Nginx采用的處理模式也沒什么特別的,大概就是如下的邏輯:
嘗試獲取accept鎖
if 獲取成功:
在epoll中注冊(cè)accept事件
else:
在epoll中注銷accept事件
處理所有事件
釋放accept鎖
當(dāng)然這里忽略了延后事件的處理,這部分我們放到后面討論。
新聞熱點(diǎn)
疑難解答
圖片精選