nginx---如何實現輕量級和高併發
轉自:https://bijian1013.iteye.com/blog/2232124
Nginx 不同於 Apache2 的一點就是,Nginx 採用單執行緒,非阻塞,非同步 IO 的工作模型。Apache2 對於每一個請求,都會建立一個新程序或執行緒,會浪費很多記憶體和 CPU 時間,而 Nginx 使用作業系統提供的IO多路複用技術(epoll), 在一個執行緒中處理所有的請求。當一個 IO 操作開始的時候,Nginx 不會等待操作完成就會去處理下一個請求,等到某個 IO 操作完成後,Nginx 再回過頭去處理這次 IO 的後續工作。
Linux系統通過軟限制和硬限制,制約了開啟檔案的最大個數,而且每個埠偵聽的連線數受限於/etc/sytctl.conf中的ip_local_port_range的範圍,那麼nginx是如何做到輕量級和高併發的。
一.Nginx的程序模型
各個work程序間通過accept_mutex互斥鎖進行連線的獲取,以防止驚群現象的發生(即所有程序都收到通知,卻只有一個程序執行)。負載的實現通過accept_mutex_disable=所有連線數/8-空閒連線數,該值越大越不容易去獲取accept_mutex鎖,也即空閒連線數小於所有連線數的1/8時,不再去主動獲取連線。
二.Nginx處理連線過程
首先,nginx在啟動時,會解析配置檔案,得到需要監聽的埠與ip地址,然後在nginx的master程序裡面,先初始化好這個監控的socket(建立socket,設定addrreuse等選項,繫結到指定的ip地址埠,再listen),然後再fork出多個子程序出來,然後子程序會競爭accept新的連線。此時,客戶端就可以向nginx發起連線了。當客戶端與服務端通過三次握手建立好一個連線後,nginx的某一個子程序會accept成功,得到這個建立好的連線的socket,然後建立nginx對連線的封裝,即ngx_connection_t結構體。設定socket的屬性( 比如非阻塞),然後再通過新增讀寫事件,呼叫connect/read/write來呼叫連線接著,與客戶端進行資料的交換。最後,nginx或客戶端來主動關掉連線,到此,一個連線就結束了。
三.Nginx的連線與檔案描述符的關係
在nginx中,每個程序會有一個連線數的最大上限,這個上限與系統對fd的限制不一樣。在作業系統中,通過ulimit -n,可以得到一個程序所能夠開啟的fd的最大數,即nofile,因為每個socket連線會佔用掉一個fd,所以這也會限制程序的最大連線數,當然也會直接影響到程式所能支援的最大併發數,當fd用完後,再建立socket時,就會失敗。nginx通過設定worker_connectons來設定每個程序支援的最大連線數。如果該值大於nofile,那麼實際的最大連線數是nofile,nginx會有警告。nginx在實現時,是通過一個連線池來管理的,每個worker程序都有一個獨立的連線池,連線池的大小是worker_connections。這裡的連線池裡面儲存的其實不是真實的連線,它只是一個worker_connections大小的一個ngx_connection_t結構的陣列。並且,nginx會通過一個連結串列free_connections來儲存所有的空閒ngx_connection_t,每次獲取一個連線時,就從空閒連線連結串列中獲取一個,用完後,再放回空閒連線連結串列裡面。
四.結論
當大量連線進來後,nginx首先把它們放進來,放入得到accept_mutex的work程序的連線池中,此時並沒有真正開啟檔案描述符,因此可以儘可能多的接受連線請求,真正處理的時候nginx採用非同步非阻塞的事件機制,通過單程序迴圈處理準備好的事件,最終利用有限的系統能開啟的最大檔案描述來承擔大量的連線請求。