1. 簡介
Nginx (engine x) 是一個高效能的HTTP和反向代理web伺服器,同時也提供了IMAP/POP3/SMTP服務。
Nginx是一款輕量級的Web 伺服器/反向代理伺服器及電子郵件(IMAP/POP3)代理伺服器,在BSD-like 協議下發行。其特點是佔有記憶體少,併發能力強,事實上nginx的併發能力在同型別的網頁伺服器中表現較好,中國大陸使用nginx網站使用者有:百度、京東、新浪、網易、騰訊、淘寶等。
1.1 正向代理
如圖所示:
當我們想訪問YouTube
網站時,由於防火牆的原因,我們並不能直接訪問。此時我們就需要藉助VPN
,當我們訪問目標伺服器時,通過VPN Client
將我們的請求代理到VPN Server
,VPN Server
接收到請求後直接訪問目標地址,最後將訪問到的資訊返回給使用者。
正向代理:“代理”的是客戶端。
1.2 反向代理
如圖所示:
反向代理,其實客戶端對代理是無感知的,因為客戶端不需要任何配置就可以訪問,我們只
需要將請求傳送到反向代理伺服器,由反向代理伺服器去選擇目標伺服器獲取資料後,在返
回給客戶端,此時反向代理伺服器和目標伺服器對外就是一個伺服器,暴露的是代理伺服器
地址,隱藏了真實伺服器 IP 地址。
反向代理:“代理”的是服務端。
1.3 負載均衡
如圖所示:
客戶端傳送多個請求到伺服器,伺服器處理請求,有一些可能要與資料庫進行互動,伺服器處理完畢後,再將結果返回給客戶端。
這種架構模式對於早期的系統相對單一,併發請求相對較少的情況下是比較適合的,成本也低。但是隨著資訊數量的不斷增長,訪問量和資料量的飛速增長,以及系統業務的複雜度增加,這種架構會造成伺服器相應客戶端的請求日益緩慢,併發量特別大的時候,還容易造成伺服器直接崩潰。很明顯這是由於伺服器效能的瓶頸造成的問題,那麼如何解決這種情況呢?
我們首先想到的可能是升級伺服器的配置,比如提高 CPU 執行頻率,加大記憶體等提高機器的物理效能來解決此問題,但是我們知道摩爾定律的日益失效,硬體的效能提升已經不能滿足日益提升的需求了。最明顯的一個例子,天貓雙十一當天,某個熱銷商品的瞬時訪問量是極其龐大的,那麼類似上面的系統架構,將機器都增加到現有的頂級物理配置,都是不能夠滿足需求的。那麼怎麼呢?
上面的分析我們去掉了增加伺服器物理配置來解決問題的辦法,也就是說縱向解決問題的辦法行不通了,那麼橫向增加伺服器的數量呢?這時候叢集的概念產生了,單個伺服器解決不了,我們增加伺服器的數量,然後將請求分發到各個伺服器上,將原先請求集中到單個伺服器上的情況改為將請求分發到多個伺服器上,將負載分發到不同的伺服器,也就是我們所說的負載均衡。
1.4 動靜分離
如圖所示:
我們將請求的資源分為靜態和動態資源,其中靜態資源指的就是前端資源(html、css、js、img等),我們將這些靜態的資源剝離出來,單獨部署到靜態資原始檔伺服器(直接通過nginx代理即可),而動態資源(介面請求的資料)通過nginx負載到後端的叢集機器上。
這樣做可以把動態頁面和靜態頁面由不同的伺服器來解析,加快解析速度。降低原來單個伺服器的壓力。
2. 安裝
2.1 官方
我們可以參照官方安裝文件實現基於下載源的方式安裝。
2.2 docker-compose
推薦使用docker-compose的方式安裝nginx
小插曲:
因為需要將容器中的配置檔案掛載到宿主機中,然而當我們通過volumes
掛載的時候,docker其自身的掛載機制是將宿主機中的檔案掛載到容器中,所以會導致宿主機將容器中的配置檔案給覆蓋掉了(丟失了),所以我們還有些前置的工作。
# 先啟動一個零時的nginx容器
docker run --rm -d --name=tempnginx nginx
mkdir conf
# 然後將零時容器中的配置檔案拷貝到宿主機上
# 拷貝nginx.conf配置檔案
docker cp tempnginx:/etc/nginx/nginx.conf ./conf/
# 拷貝conf.d資料夾
docker cp tempnginx:/etc/nginx/conf.d/ ./conf/
# 停止零時容器
docker stop tempnginx
docker-compose.yaml
配置資訊如下
version: '3'
services:
nginx:
# 容器名稱
container_name: nginx
# 預設nginx:latest
image: nginx
# 自啟動
restart: always
# 宿主機埠80:容器預設埠80
ports:
- 80:80
# 檔案對映
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./conf/conf.d/:/etc/nginx/conf.d/
- ./html/:/usr/share/nginx/html/
- ./logs/:/var/log/nginx/
# 時區設定
environment:
- TZ=Asia/Shanghai
3. 常用命令
進入nginx的sbin目錄中
./nginx -v
:檢視nginx版本號./nginx
: 啟動nginx./nginx -s stop
: 停止nginx./nginx -s quit
: 優雅的停止服務./nginx -s reload
:重新載入nginx(不是重啟)nginx -t <指定配置檔案路徑>
: 檢視配置檔案書寫是否正確
4. 配置檔案
4.1 結構
-
全域性塊:配置影響nginx全域性的指令。一般有執行nginx伺服器的使用者組,nginx程式pid存放路徑,日誌存放路徑,配置檔案引入,允許生成worker process數等。
-
events塊:配置影響nginx伺服器或與使用者的網路連線。有每個程式的最大連線數,選取哪種事件驅動模型處理連線請求,是否允許同時接受多個網路連線,開啟多個網路連線序列化等。
-
http塊:可以巢狀多個server,配置代理,快取,日誌定義等絕大多數功能和第三方模組的配置。如檔案引入,mime-type定義,日誌自定義,是否使用sendfile傳輸檔案,連線超時時間,單連線請求數等。
-
server塊:配置虛擬主機的相關引數,一個http中可以有多個server。
-
location塊:配置請求的路由,以及各種頁面的處理情況,一個 server 塊可以配置多個 location 塊。
#全域性塊
...
#events塊
events {...}
#http塊
http {
#http全域性塊
...
#server塊
server {
#server全域性塊
...
#location塊
location [PATTERN] {...}
location [PATTERN] {...}
}
server {...}
#http全域性塊
...
}
4.2 配置資訊
4.2.1 nginx.conf
/etc/nginx/nginx.conf
user nginx; # nginx 會使用這個指定的使用者啟動工作程式( worker process)
worker_processes 1; # 允許生成的worker process數,worker_processes 值越大,可以支援的併發處理量也越多,但是會受到硬體、軟體等裝置的制約
error_log /var/log/nginx/error.log warn; # 錯誤日誌存放路徑
pid /var/run/nginx.pid; # 程式PID存放路徑
events {
worker_connections 1024; # 每個worker process可以同時支援的最大連線數
}
http {
include /etc/nginx/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 /var/log/nginx/access.log main; # 訪問日誌儲存路徑 並指定了日誌格式為main
sendfile on; # 允許sendfile方式傳輸檔案,可以在http塊,server塊,location塊
keepalive_timeout 65; # 連線超時時間
include /etc/nginx/conf.d/*.conf; # 引入/etc/nginx/conf.d/ 下的所有配置檔案,使其生效
}
4.2.2 default.conf
/etc/nginx/conf.d/default.conf
server {
# 監聽所有的ipv4的地址 埠80
listen 80;
# 監聽所有的ipv6的地址 埠80
listen [::]:80;
# 本虛擬主機的名稱或 IP 配置
server_name localhost;
# 字符集
#charset koi8-r;
# 訪問日誌地址及日誌格式
#access_log /var/log/nginx/host.access.log main;
# 匹配所有請求
location / {
# 訪問的根目錄
root /usr/share/nginx/html;
# 訪問預設頁
index index.html index.htm;
}
# 重定向訪問系統404頁面至 /404.html;
#error_page 404 /404.html;
# 重定向系統錯誤頁面到指定的靜態頁面 /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
4.3 location 語法
該指令用於匹配URL
語法如下
location [ = | ~ | ~* | ^~] /uri/ {...}
- = : 用於不含正規表示式的 uri 前,要求請求字串與 uri
嚴格匹配
,如果匹配成功,就停止繼續向下搜尋並立即處理該請求 - ~ : 用於表示 uri
包含正規表示式
,並且區分大小寫
- ~* : 用於表示 uri
包含正規表示式
,並且不區分大小寫
- !~ : 用於表示 uri
包含正規表示式
, 並且區分大小寫不匹配
- !~* : 用於表示 uri
包含正規表示式
,並且不區分大小寫不匹配
- ^~ : 用於
不含正規表示式
的 uri 前,要求 Nginx 伺服器找到標識 uri 和請求字串匹配度最高的 location 後,立即使用此 location 處理請求,而不再使用 location 塊中的正則 uri 和請求字串做匹配
注意: 如果 uri 包含正規表示式,則必須要有 ~ 或者 ~* 標識
5. 反向代理
實現效果: 使用者訪問nginx服務,通過nginx將請求代理到後臺服務中。
5.1 例項1
-
啟動一個後臺服務
student
其埠為8080
,專案啟動後直接在位址列上訪問localhost:8080
,返回I am student
-
修改本地
hosts
檔案,修改完成後,我們便可以通過www.ldx.com:8080
訪問到第一步的初始化頁面。那麼如何只需要輸入www.ldx.com
便可以跳轉到第一步的初始介面呢?便用到 nginx的反向代理。# nginx test 127.0.0.1 www.ldx.com
-
在
default.conf
配置檔案中增加如下配置監聽
80
埠,訪問域名為www.ldx.com
,訪問該域名時會被nginx代理到192.168.0.107:8080
路徑上。server { listen 80; listen [::]:80; server_name www.ldx.com; location / { # 將真實的客戶端資訊轉發到服務 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.0.107:8080; } }
-
訪問結果
5.2 例項2
-
我們再起一個後臺服務
teacher
,其埠為8081且上下文地址為teacher
,啟動後直接在位址列上訪問localhost:8081/teacher
,返回I am teacher
-
student
服務新增上下文地址student
,啟動後的訪問地址為localhost:8080/student
-
在
default.conf
配置檔案中增加如下配置server { listen 80; listen [::]:80; server_name www.ldx.com; location ^~ /student/ { # 將真實的客戶端資訊轉發到服務 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.0.107:8080; } location ^~ /teacher/ { # 將真實的客戶端資訊轉發到服務 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.0.107:8081; } }
-
訪問結果
6. 負載均衡
實現效果:使用者通過nginx訪問student叢集服務,通過負載策略將請求分發到不同的服務上。
6.1 例項
-
通過idea設計器實現copy兩個啟動類的配置,並且將埠設定為動態傳入,通過啟動配置的環境變數傳入
8081 和 8083
兩個埠,實現student服務的偽叢集進行測試。 -
修改
default.conf
配置檔案通過新增
upstream
模組實現負載功能。upstream stu-server { server 192.168.0.107:8081; server 192.168.0.107:8083; } server { listen 80; listen [::]:80; server_name www.ldx.com; location ^~ /student/ { # 將真實的客戶端資訊轉發到服務 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://stu-server; } }
-
訪問結果
6.2 負載策略
1. Round Robin (default)
輪詢 - 請求在伺服器之間平均分配,同時考慮了伺服器權重。預設情況下使用此方法(沒有啟用它的指令)
upstream backend {
server backend1.example.com;
server backend2.example.com;
}
2. Least Connections
最少連線 - 將請求傳送到具有最少活動連線數的伺服器,同時還要考慮伺服器權重:
upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
}
3. IP Hash
IP雜湊 -從客戶端IP地址確定向其傳送請求的伺服器。在這種情況下,可以使用IPv4地址的前三個八位位元組或整個IPv6地址來計算雜湊值。該方法保證了來自相同地址的請求將到達同一伺服器,除非該請求不可用。
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
}
如果其中一臺伺服器需要暫時從負載平衡迴圈中刪除,則可以使用down引數對其進行標記,以保留客戶端IP地址的當前雜湊值。該伺服器要處理的請求將自動傳送到組中的下一個伺服器:
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
}
4. Generic Hash(plugin)
通用雜湊 –將請求傳送到的伺服器是根據使用者定義的鍵確定的,該鍵可以是文字字串,變數或組合。例如,金鑰可以是成對的源IP地址和埠,或者是本示例中的URI:
upstream backend {
hash $request_uri consistent;
server backend1.example.com;
server backend2.example.com;
}
指令的可選consistent引數hash
啟用ketama一致性雜湊負載平衡。根據使用者定義的雜湊鍵值,請求在所有上游伺服器上平均分配。如果將上游伺服器新增到上游組中或從上游組中刪除,則只有少數幾個鍵會被重新對映,從而在負載平衡快取伺服器或其他累積狀態的應用程式的情況下最大程度地減少快取丟失。
5. Fair(plugin)
按後端伺服器的響應時間來分配請求,響應時間短的優先分配。
upstream backend {
fair;
server backend1.example.com;
server backend2.example.com;
}
6. weight
預設情況下,NGINX使用Round Robin方法根據請求的權重在組中的伺服器之間分配請求。weight引數設定伺服器的權重;預設值為1。
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com;
server 192.0.0.1 backup;
}
在示例中,backend1.example.com
具有weight 5
;其他兩臺伺服器的預設權重(1
),但具有IP地址的192.0.0.1
一臺backup
伺服器被標記為備份伺服器,除非其他兩臺伺服器均不可用,否則不會接收請求。權重的這種配置,每6
個請求,5
個傳送到backend1.example.com
和1
個backend2.example.com
。
7. 動靜分離
Nginx 動靜分離簡單來說就是把動態跟靜態請求分開,不能理解成只是單純的把動態頁面和靜態頁面物理分離。嚴格意義上說應該是動態請求跟靜態請求分開,可以理解成使用 Nginx(ngxin 本身就是個http伺服器,可以直接使用nginx部署靜態頁面)處理靜態頁面,Tomcat 處理動態頁面。動靜分離從目前實現角度來講大致分為兩種:
1. 一種是純粹把靜態檔案獨立成單獨的域名,放在獨立的伺服器上,也是目前主流推崇的方案;
2. 另外一種方法就是動態跟靜態檔案混合在一起釋出,通過 nginx 來分開。通過 location 指定不同的字尾名實現不同的請求轉發。通過 expires 引數設定,可以使瀏覽器快取過期時間,減少與伺服器之前的請求和流量。具體 Expires 定義:是給一個資源設定一個過期時間,也就是說無需去服務端驗證,直接通過瀏覽器自身確認是否過期即可,所以不會產生額外的流量。此種方法非常適合不經常變動的資源。(如果經常更新的檔案,不建議使用Expires 來快取),我這裡設定 3d,表示在這 3 天之內訪問這個 URL,傳送一個請求,比對伺服器該檔案最後更新時間沒有變化,則不會從伺服器抓取,返回狀態碼304,如果有修改,則直接從伺服器重新下載,返回狀態碼 200。
7.1 部署靜態資源
7.1.1 root
-
準備靜態資源,放入
/usr/share/nginx/html/
用於訪問。當前測試的靜態資源是在
gitee
上找的純前端專案:專案地址 -
在
default.conf
配置檔案中增加如下配置使用者直接訪問
/
就會匹配到當前location,並直接訪問/usr/share/nginx/html/hotel/
目錄下的檔案,預設訪問index.html
頁面。- 訪問
www.ldx.com
實際請求地址就為www.ldx.com/index.html
,路徑為/usr/share/nginx/html/hotel/index.html
- 訪問
www.ldx.com/discount.html
,訪問路徑為/usr/share/nginx/html/hotel/discount.html
server { listen 80; listen [::]:80; server_name www.ldx.com; location / { root /usr/share/nginx/html/hotel/; index index.html; } }
- 訪問
-
訪問結果
7.1.2 alias
如果想給請求地址新增個上下文標識,可以使用alias
(起個別名)
-
在
default.conf
配置檔案中增加如下配置server { listen 80; listen [::]:80; server_name www.ldx.com; location /ldx-hotel { alias /usr/share/nginx/html/hotel/; index index.html; } }
-
訪問結果
-
但此時使用
root
方式就不行了,因為root
方式會將location地址的匹配字串拼接到訪問目錄地址中。錯誤日誌如下訪問的真實路徑變成了
/usr/share/nginx/html/hotel/ldx-hotel/index.html
,將訪問標識拼接上去了2021/08/05 14:39:20 [error] 23#23: *2 "/usr/share/nginx/html/hotel/ldx-hotel/index.html" is not found (2: No such file or directory), client: 172.24.0.1, server: www.ldx.com, request: "GET /ldx-hotel/ HTTP/1.1", host: "localhost" 2021/08/05 14:39:34 [error] 23#23: *2 open() "/usr/share/nginx/html/hotel/ldx-hotel/discount.html" failed (2: No such file or directory), client: 172.24.0.1, server: www.ldx.com, request: "GET /ldx-hotel/discount.html HTTP/1.1", host: "localhost"
7.1.3 小節
root : 會將location表示式中的訪問標識拼接到真實請求的路徑中
alias : 可以自定義訪問標識,其標識不會拼接到真實的請求路徑中
7.2 靜態資源索引
可以使用nginx搭建一個靜態資源索引系統,可以用來檢視附件索引並且可以使用其下載附件。
-
準備資原始檔
在
/usr/share/nginx/html/
目錄中新增如下檔案├── ldx │ └── 大帥比.txt ├── word.docx ├── 圖片.jpg ├── 測試md.md └── 測試檔案.txt 1 directory, 5 files
-
在
default.conf
配置檔案中增加如下配置server { listen 80; listen [::]:80; server_name www.ldx.com; location /file { # 防止檔名中文亂碼 charset utf-8,gbk; # 顯示目錄 autoindex on; # 顯示檔案大小 autoindex_exact_size off; # 顯示檔案時間 autoindex_localtime off; # 設定檔案格式,防止瀏覽器直接開啟檔案(直接下載檔案) if ($request_filename ~* ^.*?\.(txt|doc|pdf|rar|gz|zip|docx|exe|xlsx|ppt|pptx|crx)$){ add_header Content-Disposition attachment; } root /usr/share/nginx/html/; } }
-
訪問結果
點選下載
/ldx/大帥比.txt
檔案
8. 處理跨域請求
當出現403跨域錯誤的時候 No 'Access-Control-Allow-Origin' header is present on the requested resource
,需要給Nginx伺服器配置響應的header引數:
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
Access-Control-Allow-Origin
伺服器預設是不被允許跨域的。給Nginx伺服器配置`Access-Control-Allow-Origin *`後,表示伺服器可以接受所有的請求源(Origin),即接受所有跨域的請求。
Access-Control-Allow-Headers 是為了防止出現以下錯誤:
Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
這個錯誤表示當前請求Content-Type的值不被支援。其實是我們發起了"application/json"的型別請求導致的。這裡涉及到一個概念:預檢請求(preflight request)
,請看下面"預檢請求"的介紹。
Access-Control-Allow-Methods 是為了防止出現以下錯誤:
Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
給
OPTIONS
新增204
的返回,是為了處理在傳送POST請求時Nginx依然拒絕訪問的錯誤
傳送"預檢請求"時,需要用到方法
OPTIONS
,所以伺服器需要允許該方法。
9. 配置 https
9.1 搞到ssl證書
注意: 如果你的證書是pfx格式的,需要將pfx格式的證書解析成pem格式,建議使用linux環境解析,本人試了windows環境解析後的檔案一直有問題,強烈介意使用linux解析
linux環境本身就有openssl工具
-
將pfx證書上傳到linux環境
-
執行此命令
openssl pkcs12 -in your.pfx -nodes -out your.pem
-
輸入pfx密碼,得到pem檔案
-
開啟pem檔案,內容中包含(取出多餘的資訊,只保留標籤內的資訊)
-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----
-
將
PRIVATE KEY
內容取出放入到test.key
檔案 -
處理後的檔案長這樣
9.2 配置nginx
-
在nginx目錄下建立cert資料夾並將證書檔案放進去
-
修改nginx配置檔案
以下ssl配置都是基於nginx server的(都是配置在server標籤內的),當然也可以配置到http中(http標籤內,server標籤外),配置到不同的位置,作用域不同
-
新增ssl證書資訊
ssl_certificate ../cert/test3.pem; ssl_certificate_key ../cert/test.key;
-
限制對檔案的訪問,僅使用SSL / TLS的強版本和密碼(保護證書)
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 從1.9.1版開始,NGINX使用以下預設值 ssl_ciphers HIGH:!aNULL:!MD5;
-
https 伺服器優化
SSL操作會消耗額外的CPU資源。最耗CPU的操作是SSL握手。有兩種方法可以最大程度地減少每個客戶端執行這些操作的次數:
- 啟用保持連線以通過一個連線傳送多個請求
- 重用SSL會話引數以避免並行和後續連線的SSL握手
會話儲存在工作程式之間共享的SSL會話快取中,並由
ssl_session_cache
偽指令配置。一兆位元組的快取包含大約4000個會話。預設的快取超時為5分鐘。可以使用ssl_session_timeout
指令增加此超時。以下是針對具有10 MB共享會話快取的多核系統進行優化的示例配置:ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m;
-
配置同時支援兩種協議(http/https)
listen 80; listen 443 ssl;
-
-
拿來主義
upstream test_name{ ip_hash; server 192.168.3.66:8888; server 192.168.3.66:8080; } server { # listen 80; listen 443 ssl; # 避免中文亂碼 charset utf-8; server_name ludangxin.club; ssl_certificate ../cert/test3.pem; ssl_certificate_key ../cert/test.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; location / { proxy_pass http://test_name; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
-
雖然可以配置成兩種協議都支援,但是大多數情況下希望使用者訪問
http 80
埠的時候被轉發到https 443
server { listen 80; server_name ludangxin.club; rewrite ^/(.*)$ https://ludangxin.club:443/$1 permanent; }