被問懵逼:談談 Nginx 快的緣由?

首先要明白,Nginx 採用的是多進程(單線程) & 多路IO複用模型。使用了 I/O 多路複用技術的 Nginx,就成了」併發事件驅動「的服務器。nginx

多進程的工做模式

Nginx 在啓動後,會有一個 master 進程和多個相互獨立的 worker 進程。編程

master 接收來自外界的信號,向各 worker 進程發送信號,每一個進程都有可能來處理這個鏈接。服務器

master 進程能監控 worker 進程的運行狀態,當 worker 進程退出後(異常狀況下),會自動啓動新的 worker 進程。

注意 worker 進程數,通常會設置成機器 cpu 核數。由於更多的 worker 數,只會致使進程相互競爭 cpu ,從而帶來沒必要要的上下文切換。網絡

使用多進程模式,不只能提升併發率,並且進程之間相互獨立,一個 worker 進程掛了不會影響到其餘 worker 進程。併發

使用多進程模式,不只能提升併發率,並且進程之間相互獨立,一個 worker 進程掛了不會影響到其餘 worker 進程。socket

驚羣現象

主進程(master 進程)首先經過 socket() 來建立一個 sock 文件描述符用來監聽,而後fork生成子進程(workers 進程),子進程將繼承父進程的 sockfd(socket 文件描述符),以後子進程 accept() 後將建立已鏈接描述符(connected descriptor),而後經過已鏈接描述符來與客戶端通訊。高併發

那麼,因爲全部子進程都繼承了父進程的 sockfd,那麼當鏈接進來時,全部子進程都將收到通知並「爭着」與它創建鏈接,這就叫「驚羣現象」。大量的進程被激活又掛起,只有一個進程能夠accept() 到這個鏈接,這固然會消耗系統資源。命令行

Nginx 對驚羣現象的處理

Nginx 提供了一個 accept_mutex 這個東西,這是一個加在accept上的一把共享鎖。即每一個 worker 進程在執行 accept 以前都須要先獲取鎖,獲取不到就放棄執行 accept()。有了這把鎖以後,同一時刻,就只會有一個進程去 accpet(),這樣就不會有驚羣問題了。accept_mutex 是一個可控選項,咱們能夠顯示地關掉,默認是打開的。線程

Nginx 進程詳解

Nginx 在啓動後,會有一個 master 進程和多個 worker 進程。日誌

master 進程

主要用來管理 worker 進程,包含接收來自外界的信號,向各 worker 進程發送信號,監控 worker 進程的運行狀態,當 worker 進程退出後(異常狀況下),會自動從新啓動新的 worker 進程。

master 進程充當整個進程組與用戶的交互接口,同時對進程進行監護。它不須要處理網絡事件,不負責業務的執行,只會經過管理 worker 進程來實現重啓服務、平滑升級、更換日誌文件、配置文件實時生效等功能。

咱們要控制 nginx,只須要經過 kill 向 master 進程發送信號就好了。好比 kill -HUP pid 是告訴 nginx 從容地重啓 nginx。咱們通常用這個信號來重啓 nginx,或從新加載配置,由於是從容地重啓,所以服務是不中斷的。master 進程在接收到 HUP 信號後是怎麼作的呢?

首先 master 進程在接到信號後,會先從新加載配置文件,而後再啓動新的 worker 進程,並向全部老的 worker 進程發送信號,告訴他們能夠光榮退休了。新的 worker 在啓動後,就開始接收新的請求,老的 worker 在收到來自 master 的信號後,就再也不接收新的請求,而且在當前進程中的全部未處理完的請求處理完成後,再退出。

固然,直接給 master 進程發送信號,這是比較老的操做方式,nginx 在 0.8 版本以後,引入了一系列命令行參數,來方便咱們管理。好比 ./nginx -s reload 就是來重啓 nginx,./nginx -s stop 就是來中止 nginx 的運行。如何作到的呢?咱們仍是拿 reload 來講,咱們看到,執行命令時,咱們是啓動一個新的 nginx 進程,而新的 nginx 進程在解析到 reload 參數後,就知道咱們的目的是控制 nginx 來從新加載配置文件了,它會向 master 進程發送信號,而後接下來的動做,就和咱們直接向 master 進程發送信號同樣了。

worker 進程

而基本的網絡事件,則是放在 worker 進程中來處理了。多個 worker 進程之間是對等的,他們同等競爭來自客戶端的請求,各進程互相之間是獨立的。一個請求只可能在一個 worker 進程中處理,一個 worker 進程不可能處理其它進程的請求。worker 進程的個數是能夠設置的,通常咱們會設置與機器 cpu 核數一致,這裏面的緣由與 nginx 的進程模型以及事件處理模型是分不開的。

worker 進程之間是平等的,每一個進程處理請求的機會也是同樣的。當咱們提供 80 端口的 http 服務時,一個鏈接請求過來,每一個進程都有可能處理這個鏈接,怎麼作到的呢?首先,每一個 worker 進程都是從 master 進程 fork 過來,在 master 進程裏面,先創建好須要 listen的socket(listenfd)以後,而後再 fork 出多個 worker 進程。全部 worker 進程的 listenfd 會在新鏈接到來時變得可讀,爲保證只有一個進程處理該鏈接,全部 worker 進程在註冊 listenfd 讀事件前搶 accept_mutex,搶到互斥鎖的那個進程註冊 listenfd 讀事件,在讀事件裏調用 accept 接受該鏈接。當一個 worker 進程在 accept 這個鏈接以後,就開始讀取請求,解析請求,處理請求,產生數據後,再返回給客戶端,最後才斷開鏈接,這樣一個完整的請求就是這樣的了。咱們能夠看到,一個請求,徹底由 worker 進程來處理,並且只在一個 worker 進程中處理。

worker 進程工做流程

當一個 worker 進程在 accept() 這個鏈接以後,就開始讀取請求,解析請求,處理請求,產生數據後,再返回給客戶端,最後才斷開鏈接,一個完整的請求。一個請求徹底由 worker 進程來處理,並且只能在一個 worker 進程中處理。

這樣作帶來的好處:

節省鎖帶來的開銷。每一個 worker 進程都是獨立的進程,不共享資源,不須要加鎖。同時在編程以及問題查上時,也會方便不少。

獨立進程,減小風險。採用獨立的進程,可讓互相之間不會影響,一個進程退出後,其它進程還在工做,服務不會中斷,master 進程則很快從新啓動新的 worker 進程。固然,worker 進程的也能發生意外退出。

多進程模型每一個進程/線程只能處理一路 IO,那麼 Nginx 是如何處理多路 IO 呢?

若是不使用 IO 多路複用,那麼在一個進程中,同時只能處理一個請求,好比執行 accept(),若是沒有鏈接過來,那麼程序會阻塞在這裏,直到有一個鏈接過來,才能繼續向下執行。而多路複用,容許咱們只在事件發生時纔將控制返回給程序,而其餘時候內核都掛起進程,隨時待命。

核心:Nginx 採用的 IO 多路複用模型 epoll

Nginx 會註冊一個事件:「若是來自一個新客戶端的鏈接請求到來了,再通知我」,此後只有鏈接請求到來,服務器纔會執行 accept() 來接收請求。又好比向上遊服務器(好比 PHP-FPM)轉發請求,並等待請求返回時,這個處理的 worker 不會在這阻塞,它會在發送完請求後,註冊一個事件:「若是緩衝區接收到數據了,告訴我一聲,我再將它讀進來」,因而進程就空閒下來等待事件發生。

相關文章
相關標籤/搜索