Nginx介紹
Nginx是一款高性能的Web服務器,最初由俄羅斯程式員Igor Sysoev開發,自2004年問世以來,憑借其高性能、高可靠、易擴展等優點,在反向代理、負載均衡、靜態檔案托管等主流場合得到了廣泛的應用,
Nginx具有以下優點,
- 高性能:相比于其他Web服務器如Apache,在正常請求以及高峰請求期,可以更快地回應請求,
- 高可靠:Nginx采用多行程模型,分為主行程和作業行程,主行程負責監視作業行程,當作業行程例外退出時,可以快速拉起一個新的作業行程,從而為用戶提供穩定服務
- 高并發:Nginx通常作為網關級服務,其支持的并發量通常在萬級別,經過優化甚至可以達到十萬級別,
- 易擴展:Nginx是模塊化設計,具有極高的擴展性,使用者可以根據自身需求,定制開發相應模塊,
- 熱部署:Nginx提供了優雅重啟以及平滑升級的方案,使用戶在修改組態檔或者升級Nginx時,不會影響線上服務,
- 跨平臺:支持Linux、Windows、macOS多種平臺,
Nginx基礎架構
多行程模型
Nginx使用了Master管理行程和Worker作業行程(Worker行程)的設計,如下圖

nginx作業時,會生成一個master行程和若干個worker行程,master行程主要負責讀取、應用配置,并管理worker行程,worker行程負責處理請求,master與worker之間采用基于事件的通信模式,多行程模型的設計充分利用了多核處理器的并發能力,
異步非阻塞
Nginx的Worker行程全程作業在異步非阻塞模式下,從TCP連接的建立到讀取內核緩沖區里的請求資料,再到各HTTP模塊處理請求,或者反向代理時將請求轉發給上游服務器,最后再將回應資料發送給用戶,Worker行程幾乎不會阻塞,
當某個系統呼叫發生阻塞時(例如進行I/O操作,但是作業系統還沒將資料準備好),Worker行程會立即處理下一個請求,當處理條件滿足時,作業系統會通知Worker行程繼續完成這次操作,
一個請求可能需要多個階段才能完成,但是整體上看每個Worker行程一直處于高效的作業狀態,因此Nginx只需要少數Worker行程就能處理大量的并發請求,
CPU系結
通常在生產環境中會配置Nginx的Worker行程數量等于CPU核心數,同時會通過worker_cpu_affinity將Worker行程系結到固定的核上,讓每個Worker行程獨享一個CPU核心,這樣既能有效避免CPU頻繁地背景關系切換,也能大幅提高CPU的快取命中率,
Worker行程的負載均衡機制
當客戶端試圖與Nginx服務器建立連接時,如果每個Worker行程都爭搶著去接受連接就會造成“驚群效應“,Worker行程都被作業系統喚醒,但最終卻只有一個Worker行程成功接受連接,這會降低系統的整體性能,
此外,如果有的Worker行程總是爭搶連接失敗,而有的Worker行程本身已經很忙碌了,卻爭搶成功,就會造成Worker行程之間負載的不均衡,也會降低服務器的處理能力與吞吐量,
為了解決第一個問題,nginx引入了一把全域的accept_mutex鎖,每個Worker行程在監聽之前都會嘗試獲取accept_mutex鎖,只有成功搶到鎖的Worker行程才會真正監聽埠并接受新的連接,
關于第二個問題,nginx采用了一套負載均衡演算法,比較繁忙的Worker行程會放棄對accept_mutex鎖的爭搶,專注于處理已有的連接,
模塊化設計
Nginx由眾多模塊構成,每種模塊各司其職,但所有的模塊都遵循相同的介面規范,總體來說Nginx的模塊按功能可以劃分為如下5類:

- 核心模塊:Nginx中最重要的一類模塊,每個核心模塊定義了同一種風格型別的模塊,包括
ngx_core_module
ngx_http_module
ngx_events_module
ngx_mail_module
ngx_openssl_module
ngx_errlog_module
- HTTP模塊:與處理HTTP請求密切相關的一類模塊,數量最多的型別,Nginx的大量功能是通過HTTP模塊實作的,
- Event模塊:定義了一系列可以運行在不同作業系統、不同內核版本的事件驅動模塊,Nginx的事件處理框架完美地支持各類作業系統提供的事件驅動模型,包括epoll、poll、select、kqueue、eventport等,
- Mail模塊:與郵件服務相關的模塊,提供了代理IMAP、POP3、SMTP等的能力,
- 配置模塊:此類模塊只有ngx_conf_module一個成員,是其他模塊的基礎,因為其他模塊在生效前都需要依賴配置模塊處理配置指令并完成各自的準備作業,配置模塊指導所有模塊按照組態檔提供功能,是Nginx可配置、可定制、可擴展的基礎,
事件驅動
Nginx全異步事件驅動框架是保障其高性能的重要基石,事件驅動框架通常由3部分組成:事件收集器、事件發生器和事件處理器,
Nginx主要處理的事件來自網路和磁盤,包括TCP連接的建立與斷開、接收和發送網路資料包、磁盤檔案的I/O操作等,
事件處理器作為消費者,負責接收分發過來的各種事件并處理,Nginx中每個模塊都有可能成為事件消費者,
Event模塊負責事件的收集、管理和分發,不同作業系統提供了不同事件驅動模型,例如Linux 2.6系統同時支持epoll、poll、select模型,FreeBSD系統支持kqueue模型,Solaris 10系統支持eventport模型,為了保證其跨平臺特性,Nginx的事件驅動框架可以支持各類作業系統的事件驅動模型,針對每一種模型,Nginx設計了一個Event模塊,如ngx_epoll_module、ngx_poll_module、ngx_select_module等,事件驅動框架會在模塊初始化時根據作業系統選取一個合適的Event模塊,Linux環境下,Nginx默認選擇性能最強的epoll模型,
Nginx的配置與啟動
在docker容器中運行nginx
docker run --name nginx-demo --rm \
-p 80:80 nginx
然后就可以通過localhost訪問了, 80埠映射可省略,
Nginx的運行控制
使用nginx -s signal命令可以控制nginx的啟停、多載配置等操作,其中signal為具體的動作:
stop — fast shutdown
quit — graceful shutdown
reload — reloading the configuration file
reopen — reopening the log files
相比stop命令,quit在停止nginx前首先等待worker行程處理完當前的請求,
reolad命令可以重新加載最新修改的組態檔,
master行程在收到reolad命令時,會首先檢查組態檔的有沒有語法錯誤,確認沒問題就會給所有的worker行程發送停止命令,空閑中的worker行程會立即停止,而正在處理請求的worker行程也會在處理結束后停止,隨后master行程會使用最新的配置啟動新的一批worker行程,
前文的nginx容器啟動后,在另一個terminal執行docker exec -it nginx-demo /bin/bash進入容器,然后就可以執行nginx -s signal命令了,
停止nginx除了使用nginx -s quit外,也可以使用kill命令,與quit命令一樣,nginx也會優雅退出,
ps -ax | grep nginx # 查找nginx行程ID
kill -s QUIT <PID>
組態檔的結構
組態檔可以包含簡單指令和塊指令:
- 簡單指令為單條的指令,如
worker_processes 1;,設定worker行程的數量,以分號結尾; - 塊指令包裹在
{ }中,塊指令內部可以包含簡單指令或嵌套塊指令,如events, http, server, location; - 每個塊指令都會有自己對應的context,最外層的簡單指令屬于main context;
托管靜態檔案
首先拷貝nginx.conf檔案
docker container cp nginx-demo:/etc/nginx/nginx.conf ./cp
修改為:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
server {
location / {
root /usr/share/nginx/html;
}
}
}
然后創建/nginx/www/index.html檔案,重新啟動nginx容器并設定掛載項,宿主機目錄需要修改為真實的目錄:
docker run --name nginx-demo --rm \
-p 80:80 \
-v "$(pwd)"/nginx/www:/usr/share/nginx/html \
-v "$(pwd)"/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
-v "$(pwd)"/nginx/log:/var/log/nginx \
nginx
再次訪問localhost看到的就是自定義的index.html頁面了,
Error Log
想要開啟Error Log首先在build nginx時帶上--with-debug指令,不過這里使用的docker鏡像已經默認開啟了Error Log,只需要啟動容器時設定好掛載項-v "$(pwd)"/nginx/log:/var/log/nginx,并對應設定error_log /var/log/nginx/error.log error;就可以查看error.log了,
設定使用的指令為error_log file [level],如
error_log /path/to/log debug;
http {
server {
...
log默認error級別,也可以手動指定為debug, info, notice, warn, error, crit, alert, emerg,這個指令支持的Context有main, http, mail, stream, server, location,
Nginx如何處理請求
IP-based
首先會基于listen指定的IP地址和埠來定位虛擬主機:
server {
listen 80;
return 200 "a.com";
}
server {
listen 81;
return 200 "b.com";
}
Name-based
如果多個虛擬主機監聽相同的埠,類似下面的配置,則會根據請求頭中的Host欄位找到與server_name匹配的虛擬主機:
server {
listen 80;
server_name a.com www.a.com;
return 200 "a.com";
}
server {
listen 80;
server_name b.com www.b.com;
return 200 "b.com";
}
如果找不到匹配,或者請求頭中不包含Host欄位,Nginx會默認將請求導向第一個虛擬主機,也可以使用default_server指定默認主機;
server {
listen 80 default_server;
server_name b.com www.b.com;
return 200 "b.com";
}
Server Names
匹配server_name時,除了精確匹配,還可以使用通配符或正則
通配符(Wildcard names)匹配
使用*來替代開頭、結尾的字符,如:
*.example.org // 可以匹配www.example.org, www.sub.example.org等
www.example.* // 可以匹配www.example.org, www.example.api.org等
*.example.* // 不支持
*.example.org必須有前綴,如果既想匹配*.example.org, 又想匹配example.org,可以使用點號.,如.example.org, 但點號只能出現在前面;
正則匹配
使用正則匹配時要以~開頭,如:
~^w+\.c\.com$ // 可以匹配www.c.com, abc.c.com等
^ $,開頭、結尾符號可以選擇不加,但為了匹配精確性建議加上,
需要注意的是如果正則運算式使用了限定符{ },則需要把整段運算式用引號括起來:
"~^w{1,3}+\.c\.com$",否則nginx會報錯directive "server_name" is not terminated by ";"
此外,正則匹配捕獲的內容還可以作為后續的變數被使用,如:
server {
listen 80;
server_name "~^(?<name>w+\.(?<domain>c\.com))$";
return 200 "$name & $domain";
}
這里的正則運算式定義了兩個group,并使用?<name>語法定義了group的名稱,接下來就可以使用$符號獲取變數值了,或者基于位置來獲取變數值,
匹配順序、性能
Nginx在匹配server_name時,會按照:精確匹配、通配符匹配、正則匹配的順序,匹配性能依次降低,所以對于經常使用的server_name,最好精確指定,
通配符匹配時,*開頭的server_name優先級高于*結尾的server_name;
類似.example.org也屬于通配符匹配的一種,它的優先級高于www.example.*,且同一埠下,不能與*.example.org共存,
反向代理
反向代理使用proxy_pass指令來配置,如
server {
listen 80;
location / {
proxy_pass http://10.205.18.30:5000;
}
}
如此訪問http://localhost:80時就會被代理到http://10.205.18.30:5000
還可以進一步配置成server_group的形式,為后面的負載均衡做準備:
upstream api_server {
server 10.205.18.30:5000;
}
server {
listen 80;
location / {
proxy_pass http://api_server;
}
}
負載均衡
Nginx支持三種負載均衡模式:
- round-robin,輪詢,這也是默認的模式
- least-connected,請求會被轉發到當前連接數最少的節點
- ip-hash,基于請求方的IP地址的Hash值決定轉發目標
round-robin
輪詢是默認的模式,不需要額外的配置:
upstream api_server {
server 10.205.18.30:5000;
server 10.205.18.30:5001;
}
Weighted
通過weight指令還可以配置輪詢目標的權重,每個目標默認的權重都是1,如下設定了第一個目標的權重為2,那么2/3的請求會被導向目標1,
upstream api_server {
server 10.205.18.30:5000 weight=2;
server 10.205.18.30:5001;
}
least-connected
upstream api_server {
least_conn;
server 10.205.18.30:5000;
server 10.205.18.30:5001;
}
請求會被轉發到當前連接數最少的節點,這樣可以盡量避免有些已經很繁忙的服務器過載,
ip-hash
upstream api_server {
ip_hash;
server 10.205.18.30:5000;
server 10.205.18.30:5001;
}
使用ip_hash模式時,會基于請求方的IP地址的散列值,將請求映射到可用的目標,相同的IP地址會映射到同一個目標,所以主要用于實作“會話粘滯”,
Health check
負載均衡需要搭配健康檢查,Nginx的負載均衡采用了被動檢查的方案,如果一個目標呼叫失敗,nginx會將其標記為不可用,并將請求轉交給其它的節點,
可以類似這樣配置:
upstream api_server {
server 192.168.31.216:5000 max_fails=2 fail_timeout=10;
server 192.168.31.216:5001;
}
- max_fails,設定呼叫失敗的次數,超過設定的次數后,nginx會認為這個目標不可用,默認為1次,如果被設定為0,健康檢查會被禁用,
- fail_timeout,設定將目標標記為不可用的時長,默認為10秒,超過這個時間后,nginx會嘗試將少部分流量導到這個目標,如果目標恢復回應,則取消其不可用標記,
參考檔案
http://nginx.org/en/docs
https://openresty.org/cn/
《Nginx底層設計與原始碼分析》聶松松,趙禹,施洪寶等 著
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/373815.html
標籤:其他
