一、簡介
Nginx 是一款輕量級的 Web (HTTP)服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器
關鍵字: 事件驅動 反向代理 負載平衡 響應靜態頁面的速度非常快
優勢:能支持高達 50,000 個並發連接數 ;支持熱部署 ;很高的穩定性(抵御dos攻擊)
二、架構: 在 unix 系統中會以 daemon (守護進程)的方式在后台運行,后台進程包含一個 master 進程和多個 worker 進程(多進程的工作方式)
1、多個 worker 進程之間是對等的,他們同等競爭來自客戶端的請求,各進程互相之間是獨立的。
2、一個請求,只可能在一個 worker 進程中處理,一個 worker 進程,不可能處理其它進程的請求。
3、推薦設置 worker 的個數為 cpu 的核數
4、異步非阻塞 (非阻塞不會讓出cpu導致切換浪費)
三、基礎概念
1、connection
是對 tcp 連接的封裝;
Nginx 通過設置 worker_connectons 來設置每個worker進程支持的最大連接數;
Nginx 能建立的最大連接數,應該是worker_connections * worker_processes;對於 HTTP 請求本地資源來說,能夠支持的最大並發數量是
worker_connections * worker_processes
,而如果是 HTTP 作為反向代理來說,最大並發數量應該是worker_connections * worker_processes/2
。因為作為反向代理服務器,每個並發會建立與客戶端的連接和與后端服務的連接,會占用兩個連接;
2、request
Nginx 中指 http 請求;
web服務器工作流:http 請求是典型的請求-響應類型的的網絡協議,而 http 是文本協議,所以我們在分析請求行與請求頭,以及輸出響應行與響應頭,往往是一行一行的進行處理。如果我們自己來寫一個 http 服務器,通常在一個連接建立好后,客戶端會發送請求過來。然后我們讀取一行數據,分析出請求行中包含的 method、uri、http_version 信息。然后再一行一行處理請求頭,並根據請求 method 與請求頭的信息來決定是否有請求體以及請求體的長度,然后再去讀取請求體。得到請求后,我們處理請求產生需要輸出的數據,然后再生成響應行,響應頭以及響應體。在將響應發送給客戶端之后,一個完整的請求就處理完了。
3、keepalive
長連接: http 請求是基於 TCP 協議之上的,那么,當客戶端在發起請求前,需要先與服務端建立 TCP 連接(三次握手),當連接斷開后(四次揮手)。而 http 請求是請求應答式的,如果我們能知道每個請求頭與響應體的長度,那么我們是可以在一個連接上面執行多個請求的,這就是所謂的長連接,但前提條件是我們先得確定請求頭與響應體的長度。對於請求來說,如果當前請求需要有body,如 POST 請求,那么 Nginx 就需要客戶端在請求頭中指定 content-length 來表明 body 的大小,否則返回 400 錯誤。也就是說,請求體的長度是確定的,那么響應體的長度呢?先來看看 http 協議中關於響應 body 長度的確定:
-
對於 http1.0 協議來說,如果響應頭中有 content-length 頭,則以 content-length 的長度就可以知道 body 的長度了,客戶端在接收 body 時,就可以依照這個長度來接收數據,接收完后,就表示這個請求完成了。而如果沒有 content-length 頭,則客戶端會一直接收數據,直到服務端主動斷開連接,才表示 body 接收完了。
- 而對於 http1.1 協議來說,如果響應頭中的 Transfer-encoding 為 chunked 傳輸,則表示 body 是流式輸出,body 會被分成多個塊,每塊的開始會標識出當前塊的長度,此時,body 不需要通過長度來指定。如果是非 chunked 傳輸,而且有 content-length,則按照 content-length 來接收數據。否則,如果是非 chunked,並且沒有 content-length,則客戶端接收數據,直到服務端主動斷開連接。
從上面,我們可以看到,除了 http1.0 不帶 content-length 以及 http1.1 非 chunked 不帶 content-length 外,body 的長度是可知的。此時,當服務端在輸出完 body 之后,會可以考慮使用長連接。能否使用長連接,也是有條件限制的。如果客戶端的請求頭中的 connection為close,則表示客戶端需要關掉長連接,如果為 keep-alive,則客戶端需要打開長連接,如果客戶端的請求中沒有 connection 這個頭,那么根據協議,如果是 http1.0,則默認為 close,如果是 http1.1,則默認為 keep-alive。如果結果為 keepalive,那么,Nginx 在輸出完響應體后,會設置當前連接的 keepalive 屬性,然后等待客戶端下一次請求。當然,Nginx 不可能一直等待下去,如果客戶端一直不發數據過來,豈不是一直占用這個連接?所以當 Nginx 設置了 keepalive 等待下一次的請求時,同時也會設置一個最大等待時間,這個時間是通過選項 keepalive_timeout 來配置的,如果配置為 0,則表示關掉 keepalive,此時,http 版本無論是 1.1 還是 1.0,客戶端的 connection 不管是 close 還是 keepalive,都會強制為 close。
如果服務端最后的決定是 keepalive 打開,那么在響應的 http 頭里面,也會包含有 connection 頭域,其值是"Keep-Alive",否則就是"Close"。如果 connection 值為 close,那么在 Nginx 響應完數據后,會主動關掉連接。所以,對於請求量比較大的 Nginx 來說,關掉 keepalive 最后會產生比較多的 time-wait 狀態的 socket。一般來說,當客戶端的一次訪問,需要多次訪問同一個 server 時,打開 keepalive 的優勢非常大,比如圖片服務器,通常一個網頁會包含很多個圖片。打開 keepalive 也會大量減少 time-wait 的數量。
4、pipe
http1.1 引入新特性,keepalive 的一種升華,基於長連接的,目的就是利用一個連接做多次請求;
對 pipeline 來說,客戶端不必等到第一個請求處理完后,就可以馬上發起第二個請求;
5、linger_close
延遲關閉,也就是說,當 Nginx 要關閉連接時,並非立即關閉連接,而是先關閉 tcp 連接的寫,再等待一段時間后再關掉連接的讀。
四、配置
1、nginx.conf
指令上下文:
- main: Nginx 在運行時與具體業務功能(比如http服務或者email服務代理)無關的一些參數,比如工作進程數,運行的身份等。
- http: 與提供 http 服務相關的一些配置參數。例如:是否使用 keepalive 啊,是否使用gzip進行壓縮等。
- server: http 服務上支持若干虛擬主機。每個虛擬主機一個對應的 server 配置項,配置項里面包含該虛擬主機相關的配置。在提供 mail 服務的代理時,也可以建立若干 server,每個 server 通過監聽的地址來區分。
- location: http 服務中,某些特定的URL對應的一系列配置項。
- mail: 實現 email 相關的 SMTP/IMAP/POP3 代理時,共享的一些配置項(因為可能實現多個代理,工作在多個監聽地址上)。
示例:
worker_processes 1; //一般設置為cpu核數
error_log logs/error.log error;
pid logs/nginx.pid;
events {
worker_connections 1024; //每個worker的最大連接數
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
server_names_hash_bucket_size 128;
keepalive_timeout 1800s; //支持長連接
client_max_body_size 0;
proxy_connect_timeout 5s;
proxy_read_timeout 1800s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
upstream web_vmaxfmproxy_pool { server 10.43.136.220:27430; }
upstream web_vmaxdatacheck_pool { server 10.43.136.220:27340; }
server {
listen 28888;
server_name web_web_pool;
location ~ ^/web/cometd {
proxy_pass http://web_web_pool;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
if ($uri ~ ^/vmaxfmproxy/){ proxy_pass http://web_vmaxfmproxy_pool; break; }
if ($uri ~ ^/vmaxdatacheck/){ proxy_pass http://web_vmaxdatacheck_pool; break; }
}
location /rdk/service {
proxy_pass http://localhost:5555;
}
location ~ /rdk/app/(?<section>.*) {
proxy_pass http://web_rdk_server_pool/rdk_server/app/$section;
}
location /web/res/web-framework/default.html {
rewrite /web/res/web-framework/default.html /rdk/app/portal/web/index.html permanent;
}
}
}
五、nginx模塊
-
event module: 搭建了獨立於操作系統的事件處理機制的框架,及提供了各具體事件的處理。包括 ngx_events_module, ngx_event_core_module和ngx_epoll_module 等。Nginx 具體使用何種事件處理模塊,這依賴於具體的操作系統和編譯選項。
-
phase handler: 此類型的模塊也被直接稱為 handler 模塊。主要負責處理客戶端請求並產生待響應內容,比如 ngx_http_static_module 模塊,負責客戶端的靜態頁面請求處理並將對應的磁盤文件准備為響應內容輸出。
-
output filter: 也稱為 filter 模塊,主要是負責對輸出的內容進行處理,可以對輸出進行修改。例如,可以實現對輸出的所有 html 頁面增加預定義的 footbar 一類的工作,或者對輸出的圖片的 URL 進行替換之類的工作。
-
upstream: upstream 模塊實現反向代理的功能,將真正的請求轉發到后端服務器上,並從后端服務器上讀取響應,發回客戶端。upstream 模塊是一種特殊的 handler,只不過響應內容不是真正由自己產生的,而是從后端服務器上讀取的。
- load-balancer: 負載均衡模塊,實現特定的算法,在眾多的后端服務器中,選擇一個服務器出來作為某個請求的轉發服務器。
六、nginx 請求過程
所有實際上的業務處理邏輯都在 worker 進程。worker 進程中有一個函數,執行無限循環,不斷處理收到的來自客戶端的請求,並進行處理,直到整個 Nginx 服務被停止。
worker 進程中,ngx_worker_process_cycle()函數就是這個無限循環的處理函數。在這個函數中,一個請求的簡單處理流程如下:
- 操作系統提供的機制(例如 epoll, kqueue 等)產生相關的事件。
- 接收和處理這些事件,如是接受到數據,則產生更高層的 request 對象。
- 處理 request 的 header 和 body。
- 產生響應,並發送回客戶端。
- 完成 request 的處理。
- 重新初始化定時器及其他事件。
為了讓大家更好的了解 Nginx 中請求處理過程,我們以 HTTP Request 為例,來做一下詳細地說明。
從 Nginx 的內部來看,一個 HTTP Request 的處理過程涉及到以下幾個階段。
- 初始化 HTTP Request(讀取來自客戶端的數據,生成 HTTP Request 對象,該對象含有該請求所有的信息)。
- 處理請求頭。
- 處理請求體。
- 如果有的話,調用與此請求(URL 或者 Location)關聯的 handler。
- 依次調用各 phase handler 進行處理。
在這里,我們需要了解一下 phase handler 這個概念。phase 字面的意思,就是階段。所以 phase handlers 也就好理解了,就是包含若干個處理階段的一些 handler。
在每一個階段,包含有若干個 handler,再處理到某個階段的時候,依次調用該階段的 handler 對 HTTP Request 進行處理。
通常情況下,一個 phase handler 對這個 request 進行處理,並產生一些輸出。通常 phase handler 是與定義在配置文件中的某個 location 相關聯的。
一個 phase handler 通常執行以下幾項任務:
- 獲取 location 配置。
- 產生適當的響應。
- 發送 response header。
- 發送 response body。
當 Nginx 讀取到一個 HTTP Request 的 header 的時候,Nginx 首先查找與這個請求關聯的虛擬主機的配置。如果找到了這個虛擬主機的配置,那么通常情況下,這個 HTTP Request 將會經過以下幾個階段的處理(phase handlers):
- NGX_HTTP_POST_READ_PHASE: 讀取請求內容階段
- NGX_HTTP_SERVER_REWRITE_PHASE: Server 請求地址重寫階段
- NGX_HTTP_FIND_CONFIG_PHASE: 配置查找階段:
- NGX_HTTP_REWRITE_PHASE: Location請求地址重寫階段
- NGX_HTTP_POST_REWRITE_PHASE: 請求地址重寫提交階段
- NGX_HTTP_PREACCESS_PHASE: 訪問權限檢查准備階段
- NGX_HTTP_ACCESS_PHASE: 訪問權限檢查階段
- NGX_HTTP_POST_ACCESS_PHASE: 訪問權限檢查提交階段
- NGX_HTTP_TRY_FILES_PHASE: 配置項 try_files 處理階段
- NGX_HTTP_CONTENT_PHASE: 內容產生階段
- NGX_HTTP_LOG_PHASE: 日志模塊處理階段
在內容產生階段,為了給一個 request 產生正確的響應,Nginx 必須把這個 request 交給一個合適的 content handler 去處理。如果這個 request 對應的 location 在配置文件中被明確指定了一個 content handler,那么Nginx 就可以通過對 location 的匹配,直接找到這個對應的 handler,並把這個 request 交給這個 content handler 去處理。這樣的配置指令包括像,perl,flv,proxy_pass,mp4等。
如果一個 request 對應的 location 並沒有直接有配置的 content handler,那么 Nginx 依次嘗試:
- 如果一個 location 里面有配置 random_index on,那么隨機選擇一個文件,發送給客戶端。
- 如果一個 location 里面有配置 index 指令,那么發送 index 指令指明的文件,給客戶端。
- 如果一個 location 里面有配置 autoindex on,那么就發送請求地址對應的服務端路徑下的文件列表給客戶端。
- 如果這個 request 對應的 location 上有設置 gzip_static on,那么就查找是否有對應的
.gz
文件存在,有的話,就發送這個給客戶端(客戶端支持 gzip 的情況下)。 - 請求的 URI 如果對應一個靜態文件,static module 就發送靜態文件的內容到客戶端。
內容產生階段完成以后,生成的輸出會被傳遞到 filter 模塊去進行處理。filter 模塊也是與 location 相關的。所有的 fiter 模塊都被組織成一條鏈。輸出會依次穿越所有的 filter,直到有一個 filter 模塊的返回值表明已經處理完成。
這里列舉幾個常見的 filter 模塊,例如:
- server-side includes。
- XSLT filtering。
- 圖像縮放之類的。
- gzip 壓縮。
在所有的 filter 中,有幾個 filter 模塊需要關注一下。按照調用的順序依次說明如下:
- write: 寫輸出到客戶端,實際上是寫到連接對應的 socket 上。
- postpone: 這個 filter 是負責 subrequest 的,也就是子請求的。
- copy: 將一些需要復制的 buf(文件或者內存)重新復制一份然后交給剩余的 body filter 處理。
七、upstream模塊
1、upstream 模塊
將使 Nginx 跨越單機的限制,完成網絡數據的接收、處理和轉發。
數據轉發功能,為 Nginx 提供了跨越單機的橫向處理能力,使 Nginx 擺脫只能為終端節點提供單一功能的限制,而使它具備了網路應用級別的拆分、封裝和整合的戰略功能。
在雲模型大行其道的今天,數據轉發是 Nginx 有能力構建一個網絡應用的關鍵組件。
upstream 屬於 handler,只是他不產生自己的內容,而是通過請求后端服務器得到內容,所以才稱為 upstream(上游)。
請求並取得響應內容的整個過程已經被封裝到 Nginx 內部,所以 upstream 模塊只需要開發若干回調函數,完成構造請求和解析響應等具體的工作。
2、負載均衡模塊
負載均衡模塊用於從upstream
指令定義的后端主機列表中選取一台主機。
Nginx 先使用負載均衡模塊找到一台主機,再使用 upstream 模塊實現與這台主機的交互。
如果需要使用 ip hash 的負載均衡算法:
示例 :
worker_processes 1;
error_log logs/error.log error;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
server_names_hash_bucket_size 128;
keepalive_timeout 600s;
proxy_connect_timeout 5s;
proxy_read_timeout 600s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
upstream lw_web_rdk_server_pool {
server 10.43.149.160:26180 weight=1;
server 10.43.136.220:5812 weight=1;
}
server {
listen 26188;
server_name localhost;
location /{
proxy_pass http://lw_web_rdk_server_pool;
proxy_redirect default;
}
}
server {
listen 26185;
server_name localhost;
location /rdk/service {
proxy_pass http://localhost:5812;
}
location / {
root ../../;
index index.html index.htm;
}
}
}