#### 第16章:
#### Nginx
Nginx 是一款免費開源的高性能HTTP服務器及反向代理服務器產品。并具有IMAP/POP3代理服務等功能。支持fastCGI、SSL、virtual Host、URL Rewrite、HTTP Basic Auth、Gzip等功能;并且支持第三方功能模塊的擴展。
#### 16.1 Nginx常用功能
##### HTTP代理和反向代理
在提供反向代理方面,Nginx服務器轉發請求性能穩定,轉發和業務配置分離,配置相當靈活。
Nginx的代理服務支持正則表達式匹配,根據不同的表達式匹配策略。Nginx對后端返回會進行異常判斷,剔除異常主機。Nginx支持錯誤頁面跳轉功能。
##### 負載均衡
Nginx的負載均衡主要是對大量的前端訪問流量進行分流,以保證前端用戶訪問效率。也就是將前端訪問流量分攤到后端網絡節點分別處理,這樣有效地減少了前端用戶等待響應的時間。
Nginx的負載均衡策略分為兩大類:內置策略和擴展策略。
內置策略:
- 輪詢
- 加權輪詢
- IP hash
擴展策略:
- url hash
- fair
.....
內置策略是默認被編譯進Nginx的內核,擴展策略需要手動將第三方模塊編譯進Nginx內核。
輪詢:輪詢是將每個前端請求順序地逐一分配到后端節點,對于出現問題的節點會排除。
加權輪詢:在輪詢的基礎上指定各后端節點被輪詢到的幾率。
IP hash:對前端的訪問IP進行hash操作,根據hash結果分配到不同的后端節點上。
url hash:對前端訪問的url進行hash操作,根據hash結果分配到不同的后端節點上。
IP hash與url hash都可以解決session的問題。
##### Web緩存
Nginx在進行反向代理時,proxy_Cache指令集可以將后端返回內容進行URL緩存;fastCGI_Cache指令集可以對fastCGI的動態程序進行緩存(PHP-FPM和Nginx通過fastCGI協議進行通信);ngx_cache_purge指令集用于清除指定的URL緩存。
#### 16.2 Nginx 編譯和安裝
- http://nginx.org/en/download.html下載Linux系統版本,并且解壓
:-: 
stable version 是穩定版本;Legacy versions 是過去版本。
- 進入解壓后的Nginx文件夾使用configure腳本生成Makefile文件。
| 選項 | 說明 |
| ----------------------- | ------------------------------------------------------------ |
| --prefix=<path> | 指定Nginx安裝路徑 |
| --sbin-path=<path> | 指定Nginx可執行文件安裝路徑。 |
| --conf-path=<path> | 在未給定-c選項下,指定默認的nginx.conf路徑。 |
| --pid-path=<path> | 在nginx.conf未指定pid指定的情況下,指定默認的nginx.pid路徑。 |
| --lock-path=<path> | 指定nginx.lock文件的路徑。此文件是nginx的鎖文件,如果未指定默認為/var/lock/目錄 |
| --error-log-path=<path> | 在nginx.conf未指定error_log指令的情況下,指定默認的錯誤日志的路徑。如果未指定,默認為<prefix/logs/access.log> |
? nginx常見的configure腳本支持選項
- make && make install 進行編譯安裝
安裝成功后的安裝目錄主要包括conf、html、logs、sbin。
conf存放了Nginx的所有配置文件。nginx.conf是Nginx服務器的主配置文件,其他配置文件用來配置Nginx的相關功能。
html存放了Nginx服務器運行過程中調用的一些html網頁文件。
logs存放日志文件。
sbin存放唯一文件,也就是Nginx主程序。
#### 16.3 Nginx的啟停控制
想要控制Nginx的啟停,在linux環境中有多種方法。
- 將Nginx 設置成服務,使用類似`systemctl restart nginx`進行Nginx程序的啟停。
- 通過信號控制Nginx啟停。向Nginx主進程發送信號可以控制Nginx程序的啟停。
| 信號 | 作用 |
| --------- | ------------------------------------------------------------ |
| TERM或INT | 快速停止Nginx服務 |
| QUIT | 平緩停止Nginx服務 |
| HUP | 使用新得配置文件啟動進程,之后平緩停止原有進程。也就是"平滑重啟" |
| USR1 | 重新打開日志文件,用于日志切割 |
| USR2 | 使用新版本的Nginx文件啟動服務,之后平緩停止原有的Nginx進程。也就是"平滑升級" |
| WINCH | 平緩停止worker process,用于Nginx服務平滑升級 |
? Nginx服務可接受的信號
向Nginx發送信號有兩種方法:使用nginx二進制文件;使用kill命令發送信號。
kill命令發送信號使用方法:
```
kill Singal PID //Singal指信號
```
或
```
kill Singal 'PIDfilepath'//PIDfilepath指PID文件路徑
```
##### 二進制文件啟動Nginx
啟動Nginx直接運行sbin目錄下的Nginx二進制文件即可。
##### 二進制文件停止Nginx服務
停止Nginx有兩種:快速停止和平滑停止。快速停止指的是立即停止當前Nginx正在處理的所有網絡請求。平滑停止指的是允許Nginx將當前正在處理的網絡請求處理完成,不再接收新的請求,之后關閉連接停止工作。
停止Nginx的操作
```
./sbin/Nginx -g TERM |INT|QUIT
```
或者
```
kill TERM|INT|QUIT 'Nginx/logs/nginx.pid'
```
或
```
kill -9 | Sigkill 'Nginx/logs/nginx.pid'
```
##### 二進制文件平滑重啟Nginx服務
```
./sbin/Nginx -g HUP [-c newConFile]
```
或使用新的配置文件代替舊的配置文件
```
kill HUP '/Nginx/logs/nginx.pid'
```
##### Nginx服務器升級
升級Nginx服務器版本可以直接停止當前Nginx服務再開啟新的Nginx服務,但是會導致這一段時間用戶無法訪問服務器。平滑升級解決了這個問題,Nginx服務接收到USR2信號后,會啟動新的Nginx服務,之后需要向舊的Nginx服務發送WINCH信號使之平滑停止。
```
./sbin/Nginx -g USR2
```
```
./sbin/Nginx -g WINCH
```
#### 16.4 Nginx服務器基礎配置指令
Nginx的配置文件在安裝目錄中的conf目錄里,主配置文件名為nginx.conf。
##### Nginx.conf文件的結構
```
... #全局塊
events #events塊
{
...
}
http #http塊
{
... #http全局塊
server #server塊
{
... #server全局塊
location #location塊
{
...
}
location #location塊
{
...
}
}
server #server塊
{
... #server全局塊
location #location塊
{
...
}
location #location塊
{
...
}
}
}
```
nginx.conf配置文件結構主要包括全局塊、events塊、http塊。http塊里可以有http全局塊、多個server塊。每個server塊可以包括server全局塊、多個location塊。
Nginx的配置多數指令可以在多個域(塊)起作用,所以如果在不同的兩個層級同時出現相同的指令,則采用"就近原則"。比如在server塊和location同時出現一條相同的指令,并且配置不同,則以location塊中的配置為準。
1. 全局塊:設置一些影響Nginx整體運行的配置指令。
2. events塊:設置Nginx服務器與用戶的網絡連接。比如:最大連接數,是否允許同時接收多個網絡連接,時間驅動模型等。
3. http塊:Nginx中重要的配置部分,代理、緩存、日志定義等功能以及第三方模塊配置都在這個模塊中。http全局塊中可以設置MIME-Type定義、文件引入、日志自定義、連接超時時間、單連接請求數上限等。
4. server塊:一個server塊可以作為一個邏輯獨立的服務(或網站),每個server塊的作用域只是server塊自己(包括內部的location塊)。比如可以在http塊中設置多個server塊匹配提供給用戶多個網站。
5. location塊:用于Nginx接收到除了IP或別名的字符在匹配上server塊之后,將ip或者別名之后的字符進行匹配,再進行特定處理。地址定向、數據緩存、應答控制等功能等功能就是在這里實現。比如www.baidu.com/test/1/index.php,server塊負責匹配www.baidu.com,而多個location負責匹配/test/1/index.php或者其他字符,再進行特定處理。
6. MIME-Type塊:定義MIME類型。MIME類型指的是瀏覽器可以識別的格式。比如圖片可以是gif、jpeg等格式。MIME-Type的例子
```
include mime.types;
default_type application/ocatet-stream
```
```
#cat mime.type
type{ #下面是識別類型
text/html #html htm shtml
...
image/gif #gif
...
application/x-javascript #js
...
audio/midi #mid midi kar
....
}
```
| 配置項 | 作用 | 配置位置 |
| --------------------------------------- | ------------------------------------------------------------ | ---------------------- |
| user user [group] | 指定可以運行Nginx的用戶及用戶組 | 全局塊 |
| worker_processes num | 配置允許生成的worker process數 | 全局塊 |
| pid filepath | 配置Nginx進程PID存放路徑 | 全局塊 |
| error_log file\|stderr [level] | 配置錯誤日志的存放路徑 | location及其以上塊 |
| include filepath | 引入配置文件 | 任意地方 |
| accept_mutex on\|off | 設置網絡連接序列化避免喚醒進程過多的"驚群"問題 | events塊 |
| multi_accept on\|off | 設置是否允許同時接收多個網絡連接 | events塊 |
| use epoll\|poll\|select | 設置時間驅動模型 | events塊 |
| worker_connetions number | 設置每個worker processes允許同時開啟的最大連接數量 | event塊 |
| access_log path [format [buffer=size]] | 配置記錄Nginx提供服務過程應答前端請求的日志和日志格式。支持對服務日志的格式、大小、輸出以及是否打開服務日志等進行配置 | http塊 |
| log_format name string | 配合access_log設置項,專門負責服務日志格式自定義 | http塊 |
| sendfile on\| off | 設置是否開啟sendfile方式傳輸文件 | location、server、http |
| sendfile_max_chunk num | 設置調用sendfile()傳輸的數據量最大不能超過這個值 | location、server、http |
| keepalive_time timeout [header_timeout] | 設置http長連接時間,也就是一段時間打開后http保持這條連接復用的時間,默認75s。header_timeout是響應報文顯示時間可以與timeout時間不一樣 | location、server、http |
| keepalive_requests number | 設置Nginx服務器和用戶建立會話連接后使用本條連接的請求數量 | location、server、http |
| listen address[:port] | 設置網絡監聽,監聽IP以及監聽的端口 | server塊 |
| server_name name ... | 配置基于名稱的主機,一般配合listen使用,每個name就是一個域名或者IP,可以使用多個name,以空格分隔 | server塊 |
| index option | 設置主頁 | location、server、http |
| root path | 設置默認地址 | location、server、http |
? Nginx.conf文件常見配置項
#### location 配置
location塊多樣的配置方式使Nginx服務具有靈活性。用法:
```
location [=|~|~*|^~] uri{...}
```
例子:
```
#這里匹配所有請求到這個主機的請求
location / {
root html;
index index.html index.htm;
deny 192.168.1.1; //禁止此ip訪問
allow 192.168.1.0/24; //允許此ip訪問
allow 10.1.1.0/16;
allow 2001:0db8::/32;
#deny all; //取消注釋可以禁止所有訪問呢
}
#這里匹配到所有以.php結尾請求到這個主機的請求
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
```
以上兩個location塊會按照匹配精度匹配。可以使用正則制作更多特定處理的location塊。
##### alias
可以使用alias 更改location的URI。
```
location ~/data/(.+\.(htm|html))${
alias /location/www/$1
}
```
當此location塊接收到`/data/a.html`的請求匹配成功,會根據alias 指令在location/www/目錄下找到a.html并響應請求。
##### 設置錯誤頁面
使用`error_page`指令設置錯誤頁面。用法:
```
error_page code uri
```
```
error_page 404 /404.html;
error_page 403 /403.html;
error_page 400 /400.html;
error_page 301 /301.html;
```
##### Nginx服務器基礎配置實例
```
#這里配置了開啟Nginx服務的用戶
user nobody;
#這里配置了有多少個worker工作進程
worker_processes 1;
#這里配置了錯誤日志的記錄級別和存放路徑
error_log logs/error.log;
error_log logs/error.log notice;
error_log logs/error.log info;
#這里配置了PID存放路徑
pid logs/nginx.pid;
events {
#這里配置了每個worker進程可以有多少個連接
worker_connections 1024;
}
http {
#這里配置了MIME-Type類型
include mime.types;
default_type application/octet-stream;
#這里配置了服務日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#這里配置了服務日志的格式名稱和日志文件路徑
access_log logs/access.log main;
#這里配置了是否可以使用sendfile傳輸文件
sendfile on;
#tcp_nopush on;
#這里配置了HTTP長連接復用時間
keepalive_timeout 65;
#這里配置是否打開gzip壓縮
#gzip on;
#這里配置一個server服務
server {
listen 80; #監聽端口
server_name localhost; #主機域名、IP或名稱
root /data/wwwroot/; #主目錄路徑
index index.php index.html index.htm; #首頁文件
#charset koi8-r;
#這里可以配置這個server塊的服務日志,也可以直接使用http塊的服務日志配置。
#access_log logs/host.access.log main;
#location / {
#}
#這里配置404錯誤頁面
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
#這里配置500、502、503、504 的錯誤頁面
error_page 500 502 503 504 /50x.html;
#這里配置匹配到了50x.html后的location特殊處理
#location = /50x.html {
#}
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
#這里配置以.php文件結尾的請求的特殊處理,一般用來處理php文件的請求
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
#這里設置禁止或允許訪問
#location ~ /\.ht {
# deny all;
#}
}
#這里設置另外一個server服務
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
#這里配置另一個server服務,專門用來配置ssl的
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
```
對于自己的服務,可以適當刪減或者增加服務和配置。
#### 16.5 Nginx模塊
Nginx的模塊分為`核心模塊`、`標準模塊`、`標準HTTP模塊`、`可選HTTP模塊`、`郵件服務模塊`、`第三方模塊`。
- 核心模塊:提供Nginx運行的核心基本服務,進程管理、權限控制、錯誤日志、配置解析、正則表達式解析、驅動機制等。
- 標準HTTP模塊:支持Nginx服務器的標準HTTP功能。
- 可選HTTP模塊:用于擴展HTTP功能,使其能處理一些特殊的HTTP功能。
- 郵件服務模塊:用于支持Nginx的郵件服務。
- 第三方模塊:擴展Nginx服務器應用,可完成特殊的功能,一般由第三方機構或個人編寫可編譯進Nginx。
在Nginx編譯目錄里的obj目錄中的ngx_modules.c文件包含了此版本Nginx快速編譯后的固有聲明。
:-: 
:-: 
##### 編譯可選模塊
可選模塊在快速編譯時不編譯。如果需要使用相關可選模塊的功能,必須在configure創建makefile文件時使用`--with-XXX`聲明。例子:
```
./configure --prefix=/usr/local/nginx --with-google_perftools_module --user=www --group=www --with-http_stub_status_module --with-http_gzip_static_module
```
這樣就在創建makefile文件時,添加了google_perftools_module和http_stub_status_module模塊,。使用`make && make install` 編譯安裝,后就可以使用其功能了。
##### Nginx的HTTP可選模塊
| 模塊 | 功能 |
| --------------------------- | --------------------------------------------------------- |
| ngx_http+addition_module | 在響應請求的頁面開始或結尾添加文本信息 |
| ngx_http_degradation_module | 在低內存的情況下允許Nginx服務器返回444或204錯誤 |
| ngx_http_prel_module | 在Nginx的配置文件中可以使用perl腳本 |
| ngx_http_gzip_module | 支持實時壓縮響應客戶端的輸出數據流 |
| ngx_http_gzip_static_module | 搜索并使用預壓的以'.gz'為后綴名文件代替一般文件響應客戶端 |
| ngx_http_ssl_module | 對HTTPS/SSL支持 |
? 部分常見的HTTP可選模塊
不同的Nginx版本的HTTP可選模塊的名稱可能不一樣。以上僅供參考。
##### Nginx的郵件服務模塊
目前的Nginx快速編譯的時候不會編譯郵件服務模塊。郵件模塊:
- ngx_mail_core_module
- ngx_mail_pop3_module
- ngx_mail_imap_module
- ngx_mail_smtp_module
- ngx_mail_auth_http_module
- ngx_mail_proxy_module
- ngx_mail_ssl_module
不同的Nginx版本郵件服務模塊的名稱可能不一樣。以上僅供參考。
##### 第三方模塊
Nginx的第三方模塊目前在得到不斷擴充,功能非常豐富。記錄Nginx第三方模塊的網站有許多,有興趣的同學可以自行在wiki站點查找。
#### 16.6 Nginx的處理機制
Nginx服務器的與眾不同是由于模塊化的單一職責以及它對客戶端請求的處理機制上。
Nginx并行處理請求工作有三種方式可選擇:多進程方式、多線程方式、異步方式。
##### 多進程方式
多進程方式指每當服務器接收到一個客戶端請求就由主進程生成一個子進程來和該客戶端建立連接進行交互,直到斷開連接該子進程結束。
優勢:
- 設計簡單。
- 子進程之間相互獨立,請求互不打攪,當一個子進程出現問題不會蔓延到其他子進程。
- 子進程退出時資源由操作系統回收,不留下垃圾。
缺點:
- 生成子進程需要進行內存復制等操作,在資源和時間上有一定額外開銷
##### 多線程方式
多線程方式指每當服務器接受到一個客戶端請求就有主進程生成一個線程來和該客戶端進行連接進行交互。
優勢:
- 由操作系統產生一個進程的開銷遠遠小于產生一個進程的開銷,減輕了Web服務器對系統資源的要求
缺點:
- 多個線程位于同一個進程內,可以訪問同一個內存空間,彼此之間相互影響
- 開發過程中不可避免需要開發者對內存進行管理,增加了其出錯風險
- 服務器長時間運轉錯誤的累計可能最終對整個服務器產生重大影響
##### 異步方式
同步和異步來自發送方:
同步:發送方發送請求后,等待接收到接收方的響應,才發送下一個請求。
異步:發送放發送請求后,不需要等待接收方的響應就可以發送下一個請求。
在異步機制中,所有來之發送方的請求形成一個隊列,接收方處理完成后通知發送方。
阻塞和非阻塞來自接收方:
在網絡通信中,阻塞和非阻塞用來描述進程處理調用的方式,主要是指Socket的阻塞和非阻塞,實質是指I/O操作(網絡I/O,指的是將網絡數據放入內存的過程)。
Socket阻塞:當請求來到調用結果返回之前,當前線程掛起,直到調用結果返回,線程才進入就緒狀態。
Soket的非阻塞:當請求到來調用結果不用立即返回,立刻返回執行下一個調用。
同步阻塞:發送方發送請求到接收方并等待結果,接收方進行I/O操作不做其他操作等待返回結果,再響應發送方,發送方才可以進行其他操作。
同步非阻塞:發送方發送請求到接收方并等待結果,接收方進行I/O操作并立即返回去做其他操作,直到I/O操作完成接收方獲得結果響應發送方,發送方才可以進行其他操作。
異步阻塞:發送方發送請求到接收方不等待結果繼續做其他操作,接收方進行I/O操作不能做其他操作,直到I/O操作完成接收方獲得結果響應發送方。這種方式在實際中不使用。
異步非阻塞:發送方發送請求到接收方不等待結果繼續其他操作,接收方進行I/O操作并立即去做其他操作,直到I/O操作完成將完成狀態和結果通知接收方,接收方再響應發送方。
##### Nginx服務器如何處理請求
Nginx可以處理大量并發請求,是因為它采用多進程和異步對外提供服務。一個子進程處理請求后不阻塞繼續處理另外的請求,直到有結果通知該進程,該進程得到通知暫時掛起當前處理的事務,去響應發送方。Nginx可以配置工作進程數和工作進程最大處理請求數量。所有的Nginx工作進程都用于接收和處理客戶端請求。
##### Nginx的事件處理機制
Nginx服務器的工作進程調用I/O后就去做其他工作了;當I/O調用返回后,會通知工作進程。I/O調用如何將自己的狀態通知工作進程呢?
1. 讓工作進程在進行其他工作的過程中隔一段時間就去檢查一下I/O的運行狀態,完成就響應客戶端,未完成就繼續工作。
2. I/O調用在完成后主動通知工作進程。理想的解決方案。
select/poll/epoll/kqueue(事件驅動處理庫)等系統調用就是用來支持第二種解決方案。這樣的系統調用被稱為`事件驅動模型`。它提供的機制是,進程不需要關心I/O操作的具體狀態,并且進程可以處理多個并發請求。只需要等待來自I/O完成的通知。
##### Nginx服務器的事件驅動模型
事件驅動模型一般由`事件收集器`,`事件發送器`,`事件處理器`組成。
- 事件收集器:收集來自用戶(鼠標點擊事件、鍵盤輸入事件等),來自硬件(時鐘事件等),來自軟件(操作系統,應用程序本身等)的事件。
- 事件發送器:講收集器收集到的事件分發到目標對象中。目標對象是事件處理器所處的位置。
- 事件處理器:負責具體事件的響應工作。
事件驅動程序可以由任何語言編寫。Linux內核中已經集成大部分常用事件驅動程序(事件驅動處理庫)。
比如Nginx的子進程異步過程中:事件收集器收集到帶有Nginx子進程狀態信息和操作系統完成I/O的事件,通過事件發送器發送到事件處理器,事件處理器通知Nginx子進程,子進程再獲得相關結果響應請求方。
Nginx服務器響應和處理Web請求的過程就是基于事件驅動模型。
編寫事件處理模型的程序時,"目標對象"中的"事件處理器"可以有幾種實現方法:
- "事件發送器"每傳遞過來一個請求,"目標對象"創建一個進程,調用"事件處理器"來處理請求。
- "事件發送器"每傳遞過來一個請求,"目標對象"創建一個線程,調用"事件處理器"來處理請求。
- "事件發送器"每傳遞過來一個請求,"目標對象"將其放入一個待處理事件列表,使用非阻塞I/O方式調用"事件處理器"來處理請求。
事件驅動處理庫又被稱為多路I/O復用方法:常見select模型、poll模型、epoll模型。
##### select庫
創建一個描述符集合。并且每個描述符需要關注上面的寫事件、讀事件、異常事件。所以創建三類事件描述符集合分別收集讀事件描述符、寫事件描述符、異常事件描述符。
調用select()函數等待事件發生,此時select阻塞。
然后輪詢事件描述符集合中的每一個事件描述符,檢查是否有相應事件發生。有就處理。
由于Linux的一切都是文件,所以每個事件描述符就是一個文件句柄。select默認最大文件句柄數量是1024。每次輪詢都需要把所有句柄復制到內核空間去,并且由于輪詢每次都要遍歷`事件描述符文件句柄`。這樣導致事件太多的時候性能下降。
##### poll庫
poll庫和select庫的步驟基本差不多。不同的是poll只創建一個事件描述符集合,事件描述符由鏈表連接以便輪詢,每個事件描述符上分別設置了讀事件、寫事件、異常事件,輪詢的時候可以同時檢查這三個事件是否發生。
理論上在Linux系統里由于使用鏈表保存`事件描述符文件句柄`,就沒有了監視文件數量的限制。
##### epoll庫
epoll是公認的非常優秀的事件驅動模型。它通過相關調用通知內核創建一個有N個事件描述符的事件列表;然后給這些描述符設置其所關注的事件,并將它添加到內核創建的事件描述符的事件列表中。
當某一事件發生后,內核將發生事件的描述符放入`就緒事件列表`并交由epoll庫(epoll_creat建立的epoll對象),由epoll庫進行處理。
由于就緒事件列表很短,不需要像select或者poll那樣進行大量輪詢,從而提高了效率。而且由于epoll不輪詢事件描述符列表,只輪詢就緒列表,所以其I/O效率不隨著事件描述符增加而線性下降。
注意:Linux課程時講過,大多數程序通過用戶空間的系統調用將控制權交由內核以及內核空間進行處理。內核保留多個應用程序的上下文進行切換操作。
epoll的優點:
1.支持一個進程打開大數目的socket描述符(FD)。
2.IO效率不隨FD數目增加而線性下降。
3.使用mmap加速內核與用戶空間的消息傳遞。
4.內核微調。
##### 其他驅動模型
rtsig、kqueue、dev/poll、eventport等模型有興趣的同學下來自己了解。
##### 所以為什么Nginx可以處理高并發
比較通俗的描述:
* 接收分配請求靠epoll
* 處理靠worker進程非阻塞。
接收分配請求靠epoll:將多個worker進程放進監聽Socket指向的epoll對象引用的等待隊列并阻塞(epoll\_create(),epoll\_wait),數據過來進入epoll對象引用的就緒隊列。此時epoll分配喚醒其中一個worker進程(并加鎖,否則所有worker進程都會處理此請求數據),遍歷就緒隊列里的socket取得數據進行處理。
處理靠worker進程非阻塞:
當被喚醒的worker進程處理到可能發生阻塞的地方,會注冊一個事件就直接去干其他事了。比如向下游(后端)服務器轉發請求,他會在發送完請求后,注冊“如果下游返回了,告訴我一聲,我再接著干”事件,然后它就休息去或干其他事情了。此時,如果再有請求進來,他就可以很快再按這種方式處理。一旦下游服務器返回了,會觸發這個事件,worker進程會再來接手,這個請求會接著往下走。
#### 16.7 Nginx服務器架構
Nginx服務器啟動會產生主進程,主進程會產生一個或者多個工作進程。
- 主進程作用:配置文件解析、數據結構初始化、模塊配置和注冊、信號處理、網絡監聽生成、工作進程生成和管理等。
- 工作進程作用:進程初始化、模塊調用、請求處理等,提供Nginx服務,緩存管理。
可以大致將Nginx服務器架構分為主進程、工作進程、后端服務器和緩存等部分。
:-: 
? Nginx服務器架構
##### Nginx的進程
1. 主進程作用:
- 讀取Nginx配置文件驗證有效性和正確性。
- 建立、綁定、管理Socket。
- 生成、管理、結束工作進程。
- 接收外界指令,重啟,退出,升級等。
- 開啟日志文件、獲取文件描述符。
- 編譯處理Perl腳本。
2. 工作進程作用:
- 接收客戶端請求。
- 將請求送入各個功能模塊處理。
- 與后端服務器通信,接收后端服務器的處理結果。
- I/O調用,獲取后臺服務器在內存中的響應數據。
- 獲取數據緩存。
- 發送響應結果。
- 接收主進程指令,退出、重啟等。
3. 緩存索引重建及管理進程:
主要負責緩存索引重建和緩存索引管理工作。
##### 進程交互
Nginx服務器`主進程和工作進程的交互`、`工作進程之間的交互`都依賴管道機制。
1. 主進程和工作進程交互:
首先Nginx主進程依照配置文件fork出相關數量的工作進程,并建立一個`全局工作進程表`存放未退出的所有工作進程。每當生成工作進程后,主進程將新的工作進程加入工作進程表,并建立一個單向管道傳遞給工作進程。這個管道單向由主進程指向工作進程。該管道包含了主進程發出的指令、工作進程ID、工作進程在工作進程表中的索引和必要的文件描述符信息。
主進程與外交交互使用信號,當接受到需要處理的新信號,它通過管道向相關工作進程發送指令。工作進程獲取可讀事件,并解析指令,采取相應措施。
2. 工作進程之間交互:
也是基于管道通信。首先主進程會交給其中一個工作進程另一個工作進程的ID和針對該工作進程建立的管道句柄。獲得ID和管道句柄的工作進程可以捕獲相關指令,并解析指令進行相應操作。
##### 后言
Nginx服務器各個系統模塊通過網絡、信號、通道等機制進行交互。事件處理機制在很大程度上降低了在網絡負載繁重的情況下Nginx服務器對內存、磁盤的壓力。同時又保證了Nginx服務器對客戶端請求的響應。
但是即使在這樣的情況下,Nginx工作進程任然可能阻塞,導致客戶端請求超時。試想一下下面情況:
- Nginx設置的超時時間為30秒
- Nginx有4個工作進程
- 每個工作進程可以處理100個連接
- 磁盤性能差
- 30秒內4個工作進程需要處理400個連接=400次同時的I/O調用
- 30秒內無法把所有I/O調用結果通知工作進程
此時,400個連接是否會有某些連接超時,超時的工作進程是否能夠響應客戶端呢?
#### 16.8 Nginx服務器的高級配置
##### 針對IPv4的內核優化
Nginx服務器運行在Linux系統上,所以我們可以調整Linux系統的配置以提高整體Nginx服務器性能。以下配置在
`/etc/sysctl.conf` 文件里面添加和修改。保存后使用`/sbin/sysctl -p`啟動配置。
```
net.ipv4.tcp_syncookies = 1
```
打開這個syncookies的目的實際上是:在服務器資源不足的情況下,盡量不要拒絕TCP的syn(連接)請求,盡量把syn請求緩存起來,留著過會兒有能力的時候處理這些TCP的連接請求。
```
net.ipv4.conf.all.promote_secondaries = 1
net.ipv4.conf.default.promote_secondaries = 1
```
提升ipv4性能。
```
net.core.netdev_max_backlog = 262144
```
默認128。設置每個網絡接口接收數據包速率比內核處理這些數據包速率快的時候,允許發送到隊列的數據包最大數量。
```
net.core.somaxconn = 262144
```
默認值128.設置系統允許同時發起的TCP連接最大數量。
```
net.ipv4.tcp_max_orphans = 262144
```
設置系統中允許多少個TCP套接字不被關聯到任何用戶文件句柄的最大數量。如果超過這個數量沒有關聯用戶文件句柄的TCP套接字將被復位,同時給出警告信息。在系統內存比較充足的情況下可以增大這個參數。
```
net.ipv4.tcp_max_syn_backlog = 262144
```
設置記錄未收到客戶端確認信息的連接請求的最大值。(TCP建立連接的時候需要確認,可以回憶以下TCP章節講的三次握手)
```
net.ipv4.tcp_timestamps = 0
```
設置時間戳,避免包序號卷繞。
```
net.ipv4.tcp_synack_retries = 1
```
設置內核放棄TCP連接之前向客戶端發送SYN+ACK包的數量。三次握手內核需要發送一次SYN回應之前SYN的ACK。設置之后表示內核放棄連接之前發送一次SYN+ACK包。表示客戶端發送請求連接,然后客戶端直接發送數據到服務器。
```
net.ipv4.tcp_syn_retries = 1
```
與上一個設置作用類似。
##### 針對CPU的Nginx配置優化指令
```
worker_processes 4;
```
用于配置Nginx的工作進程數。
```
worker_cpu_affinity 0001 0100 1000 0010;
```
此命令和CPU核數相關,設置工作進程使用CPU的相關。可以回憶一下系統原理章節講的一個CPU核可以處理一個控制流。
:-: 
如果是4核也可以將其worker_processes設置為8,此時worker_cpu_affinity可以這樣設置:
```
worker_cpu_affinity 0001 0100 1000 0010 0001 0010 0100 1000;
```
如果是8核可以這樣設置:
```
worker_cpu_affinity 00000001 00000100 00001000 00000010 00000001 00000010 00000100 10000000;
```
##### 與網絡連接相關的配置
```
keepalive_timeout 60 50;
```
設置服務器與客戶端保持連接時間和HTTP發送Keep-Alive頭的截至時間。
```
send_timeout 10s;
```
設置Nginx服務器響應客戶端連接的超時時間。
```
client_header_buffer_size 4k;
```
設置Nginx服務器允許客戶端請求頭的緩存區大小。
```
multi_accept off;
```
默認off。設置Nginx是否盡可能多地接收客戶端地網絡連接請求。
##### 與事件驅動模型相關的指令
```
use epoll
```
指定使用事件模型。
```
worker_connections number;
```
設置工作進程最大可連接數量。與操作系統中進程可以打開最大文件句柄數量有關。
```
worker_rlimit_sigpending limit;
```
設置事件信號隊列長度上限。
```
epoll_events number;
```
設置在epoll事件模型下Nginx服務器與內核之間可以傳遞事件的最大數量。
#### 16.9 Nginx服務器的Gzip壓縮
##### ngx_http_gzip-module模塊
主要負責Gzip功能的開啟和設置,在線實時動態壓縮。
```
gzip on | off;
```
設置Gzip開啟關閉。
```
gzip_buffer number size;
```
設置Nginx輸出響應數據進行Gzip壓縮需向系統申請number*size大小的空間用于存儲壓縮數據。
```
gzip_comp_level level;
```
設置壓縮級別,1到9,1最低使用的系統資源最低。
```
gzip_disable regex ...;
```
設置針對不同的客戶端請求選擇性開啟和關閉Gzip功能。regex根據客戶端瀏覽器標志進行設置,例如User_Agent,UA。支持正則表達式。有興趣的同學可以自行學習。
```
gzip_http_version 1.0 | 1.1;
```
設置針對不同的HTTP版本開啟或關閉Gzip功能。
```
gzip_min_length length;
```
設置需要壓縮的數據頁面的最小字節數量。有些數據量很小壓縮卻出現壓縮數據更大的情況。
```
gzip_proxied off|expired .....;
```
設置是否對后端服務器返回的結果進行Gzip壓縮。有興趣的同學可以自行學習。
```
gzip_vary on | off;
```
默認off。設置使用Gzip功能時響應頭是否帶有"Vary:Accept-Encoding"。
```
gzip mime-type ...;
```
設置需要進行Gzip壓縮的MIME類型。
##### ngx_http_gzip_static_module模塊
主要負責搜索和發送經過Gzip功能預壓縮的數據。這些數據以`.gz`作為后綴名存儲在服務器上。如果客戶端請求的數據被預壓縮過,客戶端瀏覽器支持Gzip壓縮,就直接返回預壓縮的數據。這個模塊主要是靜態壓縮。會在Content-Length頭指明報文體的長度。
```
gzip_static on | off |always;
```
是否開啟此模塊。
其他配置與上一個模塊配置相同。
##### nginx_http_gunzip_module模塊
主要用于服務器對響應數據流進行Gzip解壓和壓縮。需要服務器本身有能力,特別是在客戶端不支持Gzip的情況下。通俗講就是為不支持gzip壓縮的瀏覽器提供輸壓縮功能。
```
gunzip on | off;
```
是否開啟此模塊。
```設置
gunzip_buffers number size;
```
設置Nginx解壓Gzip文件的緩存空間大小number*size。
#### 16.10 Nginx的Rewrite功能
主要提供重定向功能。非常靈活。
##### Nginx后端服務器組配置
由HTTP模塊ngx_http_upstream_module進行解析和處理。
1. upstream指令
```
upstream name {...}
```
name是為后端組起的別名。`{}`中包括后端組的服務器。會按照前面章節將的論調策略選擇后端組進行處理,比如IP hash、輪詢、加權輪詢等。
2. server指令
```
server address [parameters]
```
address指服務器地址,可以是域名、包含端口號的IP地址,或者以'unix:'開頭的進程通信的Unix Domain Socket。
parameters是為當前服務器配置更多屬性,比如權重,和請求后計算失效次數。
3. ip_hash指令
用于輪詢發送于后端組的哪臺服務器。
4. keepalive指令
控制網絡連接保持功能。
5. least_conn指令
用于配置Nginx服務器使用負載均衡策略為網絡連接分配服務器組里的服務器。
例子:
```
upstream back
{
ip_hash;
server myweb1.proxy.com;
server myweb2.proxy.com;
}
```
##### Rewrite 功能
用于實現URL重寫。可以在server塊和location塊中配置if指令和break指令,在使用Rewrite功能時也可以使用,以達到更靈活的配置。
Nginx配置文件設置變量用set,
```
set variable value
```
```
set $num 20
```
set未指定值的變量為空。
例如:
```
location /{
if($a){
set $id $1
break;
}
}
```
Rewrite功能只接收host之后的地址并且不包括get參數。比如http://www.baidu.com/test?a=1&b=2,實際上只接收/test。也可以通過其他全局參數得到get參數。有興趣的同學可以自行學習Rewrite功能,其靈活多變的配置方式。Rewrite重寫功能甚至可以通過重寫讓請求訪問另外一個server塊或location塊。
Rewrite功能例子:
```
rewrite ^/ http://myweb3.test.com/;
```
```
if($host ~ test\.com){
rewrite ^(.*) http://jump.myweb.name$1 permanent;
}
```
##### 目錄合并
可以將http://myweb.tes.com/server/1/2/3/4/5.html重寫成http://myweb.tes.com/server-1-2-3-4-5.html。
方便搜索引擎的優化。
##### 防盜鏈
經常會有其他的網站服務器直接將我們的服務器里的連接掛到他們的服務上,這樣增加了我們服務器的帶寬使用和資源負載。可以使用valid_referes指令做防盜鏈功能。有興趣的同學自行學習。
#### 16.11 Nginx正向代理和反向代理
正向代理:客戶端請求過來,代理服務器負責將局域網客戶機指向外部的資源。正向代理不支持外部對內部網絡的訪問。
反向代理:客戶端請求過來,代理服務器負責將客戶端請求指向局域網內部系統的某臺資源服務器。
##### 正向代理的指令
```
resolver address ... [valid=time]
```
該指令用于指定DNS服務器的IP地址。
```
resolver_timeout time;
```
設置DNS服務器解析域名超時時間。
```
proxy_pass URL;
```
設置代理服務器協議個地址。一般是個固定的值。
##### 反向代理指令
反向代理非常重要。在負載均衡系統,或在Nginx服務器與后臺服務器交互時(比如和PHP為主要語言的處理程序),反向代理選擇具體的哪臺服務器等都是至關重要的。
```
proxy_pass URL;
```
設置被代理服務器的地址,可以是傳輸協議、主機名稱、IP加端口號、URI等,也可以接收UNIX-domain套接字路徑。例如:
```
proxy_pass http://www.myweb.anme/uri;
proxy_pass http://localhost:8000/uri;
```
如果被代理服務器是服務器組,可以使用uptream指令,例如:
```
upstream pro
{
proxy_pass http://192.168.1.1/uri;
proxy_pass http://192.168.1.2/uri;
proxy_pass http://192.168.1.3/uri;
}
server{
listen 80;
server_name www.myweb.com
location / {
proxy_pass pro;
}
}
```
假設上面pro服務器組由三臺服務器分別運行PHP程序。代理服務器Nginx得到請求后,會將請求輪詢分別再次發送給這三臺后臺服務器進行處理。
```
proxy_hide_header field;
```
設置Nginx發送響應式需要隱藏的頭部信息。
```
proxy_pass_header field;
```
設置Nginx發送響應時需要發送的頭部信息。
```
proxy_pass_request_body on | off;
```
設置是否向上游代理服務器發送包體部分。
```
proxy_set_header field value;
```
設置更改接收到的請求的頭部并發送給下游的被代理服務器。默認:
```
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
```
實例:
```
proxy_set_header Host $http_host; #將Host頭的值填充成客戶端地址
proxy_set_header Host $host; #將當前location塊的server_name指令值填充到Host頭
proxy_set_header Host $host:$proxy_host; #將server_name指令值listener指令值一起添加到Host頭
```
```
proxy_set_body value;
```
將Nginx服務器接收到的請求體變更并發送到下游被代理服務器。
```
proxy_bind address;
```
設置代理連接由指定主機處理。
```
proxy_connect_timeout time;
```
設置Nginx服務器與下游被代理服務器嘗試建立連接的超時時間。
```
proxy_read_timeout time;
```
設置Nginx服務器向下游被代理服務器(組)發出read請求后,等待響應的超時時間。
```
proxy_send_timeout time;
```
設置Nginx服務器向下游被代理服務器(組)發出write請求后,等待響應的超時時間。
```
proxy_http_version 1.0 | 1.1;
```
設置用于Nginx服務器提供代理服務的HTTP協議版本。
```
proxy_method POST|GET;
```
設置Nginx服務請求轉發給下游被代理服務器時的請求方法。
```
proxy_ignore_client_abort on | off;
```
設置客戶端中斷網絡請求時,Nginx服務器是否中斷對下游被代理服務器的請求。
```
proxy_ignore_header field ...;
```
設置響應頭部,Nginx得到下游被代理服務器的響應后,不會處理被設置的頭部。
```
proxy_redirect rediirect replacement;
proxy_redirect default;
proxy_redirect off;
```
用于修改下游被代理服務器響應的頭部中的Location和Refresh。配合proxy_pass使用。有興趣的同學可以自行學習下。對于中間有多個代理服務器的系統,如何將最下游服務器的Location和Refresh返回給最初請求的客戶端就需要這個指令。
```
proxy_intercept_errors on | off;
```
配置Nginx狀態是否開啟。如果開啟,下游被代理服務器返回狀態碼,Nginx則用error_page配置的狀態頁面返回給客戶。如果沒有開啟,則Nginx直接將下游被代理服務器的狀態響應給客戶。
```
proxy_headers_hash_max_size 512;
```
設置存放HTTP報文頭的哈希表容量。
```
proxy_headers_hash_bucket_size sise;
```
設置Nginx服務器申請存放HTTP報文頭的哈希表容量的單位大小。
```
proxy_next_uptream status ...;
```
可以設置如果某下游被代理服務器(組)發生異常時,將請求順次交由下一組被代理服務器組。
```
proxy_ssl_session_reuse on | off;
```
設置是否使用SSL安全協議會話連接被代理的服務器。
##### Proxy Buffer 指令
Proxy Buffer啟動后,Nginx服務器會異步地將被代理服務器的響應數據傳遞給客戶端。服務器首先會將下游被代理服務器的響應裝到Proxy Buffer里。每個Buffer裝滿后向客戶端發送響應時,都處于BUSY狀態。
```
proxy_buffering on | off;
```
設置是否打開Proxy Buffer。
```
proxy_buffers numbei size;
```
設置Proxy Buffer的個數和每個Buffer的大小。
```
proxy_buffer_size size;
```
設置Nginx服務器從下游被代理服務器獲得第一部分響應數據的大小,一般是HTTP頭部信息。
```
proxy_busy_buffers_size;
```
設置處于BUSY狀態的緩存區總大小。
```
proxy_temp_path path;
```
設置存放代理服務器大體積響應數據的數據文件路徑。
```
proxy_max_temp_file_size size;
```
該指令用于配置所有臨時空間文件的總體積大小。
```
proxy_temp_file_write_size size;
```
配置同時寫入臨時文件的數據量的總大小,避免磁盤I/O負載過重導致性能下降。
##### Proxy Cache 指令
Proxy Cache 主要提供緩存功能,用于提高I/O吞吐效率。
```
proxy_cache on | off;
```
設置是否開啟proxy_cache。
```
proxy_cache_bypass string ...;
```
設置Nginx服務器向客戶端響應時,不從緩存獲取數據的條件。
```
proxy_cache_key string;
```
設置為緩存數據建立索引時使用的關鍵字。
```
proxy_cache_lock on | off;
```
設置是否開啟緩存的鎖功能。開啟后只能有一個請求填充緩存中的某一項數據。
```
proxy_cache_lock_timeout timeout;
```
設置緩存鎖開啟后鎖的超時時間。
```
proxy_cache_min_uses;
```
設置客戶端相同請求發送最少次數,達到后做緩存。
```
proxy_cache_path [level=level] keys_zone=name:size1 .....;
```
設置Nginx服務器緩存數據的路徑以及和緩存索引相關的內容。該指令比較復雜,有興趣的同學查閱資料自行學習。
```
proxy_cache_use_stale error|timeout|invalid_header|http_404|...|off|...;
```
設置狀態,當下游被代理的服務器處于這些狀態時,以緩存響應。
```
proxy_cache_valid time;
```
針對不同的HTTP響應狀態碼設置不同的緩存時間。
```
proxy_no_cache string ...;
```
設置在什么情況下不使用Proxy Cache功能。
```
proxy_store on | off;
```
設置是否緩存下游被代理服務器的響應數據。
```
proxy_store_access users:permissions ...;
```
設置用戶組或用戶組對Proxy Store緩存的數據訪問權限。
##### 后言
后端程序是PHP的話,Nginx配置使用fastcgi_pass,而不是proxy_pass配置。因為PHP是支持FastCGI的應用程序,允許使用fastcgi_pass進行配置。當然也可以使用proxy_pass(不建議在PHP-FPM運行模式下使用這樣的配置)。