Nginx是如何實現輕量級和高并發
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采用異步非阻塞的事件機制,通過單進程循環處理準備好的事件,最終利用有限的系統能打開的最大文件描述來承擔大量的連接請求。