系列目錄:
- 微服務 - 概念 · 應用 · 通訊 · 授權 · 跨域 · 限流
- 微服務 - 叢集化 · 服務註冊 · 健康檢測 · 服務發現 · 負載均衡
- 微服務 - Redis快取 · 資料結構 · 持久化 · 分散式 · 高併發
- 微服務 - Nginx閘道器 · 程式機制 · 限流熔斷 · 效能最佳化 · 動態負載 · 高可用
本文的前提需要了解一些 Linux 知識。
以下圍繞 Nginx 1.23 的閘道器應用;參考官網:http://nginx.org/
本文沒有對概念性方面做深入的闡述,常見的是一筆帶過,而更多的...是對配置項的解釋。
為什麼需要閘道器
通常後臺提供了不同的應用服務,甚至是叢集,每種服務每個服務,需要維護不同的請求地址,甚至服務認證、跨域等動作,管理起來比較麻煩。因此,需要一個閘道器,介於客戶端和應用服務之間,所有的外部請求都會先經過閘道器,閘道器再把請求分發到目標服務。閘道器對外提供唯一請求入口,作為對外聯絡的視窗,易於管理和維護請求。
作者:[Sol·wang] - 部落格園,原文出處:https://www.cnblogs.com/Sol-wang/
一、Nginx 概述
Nginx 不僅是一個高效能的Web伺服器,還具備訪問代理、負載均衡、內容快取等功能,用於客戶端訪問流量到後臺應用伺服器負載均衡和請求轉發。其基於模組化的程式碼架構及可與其它有效整合的可程式設計特性,使其具有強大的擴充套件能力。Nginx以資源消耗低、高穩定、高效能的併發處理能力著稱。
1.1 Nginx 特性
訪問代理:
Nginx 可以透過訪問路徑、URL 關鍵字、客戶端 IP等多種手段實現訪問路由分配。
反向代理:
將接收到的請求再轉到後端的目標應用伺服器,並把響應資料返回給客戶端。支援目前絕大多數的網路協議:HTTP/FastCGI/RPC/UDP/TCP等。
負載均衡:
透過自身的 upstream 模組支援多種負載均衡演算法,使後端伺服器可以非常方便地進行橫向擴充套件,以應對高併發。
內容快取:
Nginx支援靜態站點和後端分離,可以把靜態內容快取起來,也可以將後端變化不大的響應結果快取起來,使整體實現了更高速的相應能力。
可擴充套件性:
可定製的模組化架構方式,更多的語言(C/Perl/JavaScript/Lua)支援開發第三方模組並引入,增強可程式設計及擴充套件能力。
1.2 Nginx 程式
首先,程式是CPU管理的執行單元,CPU的單個核心也可以執行多個程式,只不過是交替執行著各個程式,稱為時間片,這種方式速度很快,以至於看上去像在同時執行;多核CPU就能同時執行更多的程式。
Nginx是由多個程式執行,一個主程式Master和多個子程式Worker,主程式負責管理子程式,如:重啟/過載/建立/銷燬等,子程式負責處理具體的請求等業務功能。程式間共享記憶體資料,更多的程式帶來更好的處理能力。
Nginx 程式執行示意圖:
1.3 Nginx 過載
Nginx支援配置資訊的過載,並以最新的配置內容執行,當Nginx在高速執行的時候,如何做到平穩過渡呢?
相關命令:nginx -s reload
過載過程:
Nginx Master process 負責 fork 出一個新的 Worker process,最新的Worker使用新的配置資訊執行,這時候就銷燬一箇舊的worker,此時,Worker有新舊之分,新Worker用新配置執行,舊Worker依然用舊配置執行,Master繼續fork出新的Worker。。。以同樣的方式持續替換舊Worker,直到全部替換完成。整個過程中,Nginx 並沒有停止執行,絲滑過渡。
二、安裝配置
2.1 編譯安裝
安裝前提:
yum install gcc -y # C語言編譯器
yum install pcre pcre-devel -y # PCRE Library
yum install zlib zlib-devel -y # zlib Library
編譯安裝:
# 進入解壓後的目錄中 編譯安裝 [指定使用者/組] [--with-追加自帶模組名稱]
./configure --prefix=/usr/local/nginx [--user=www --group=www] [--with-http_gzip_static_module]
make && make install
2.2 啟動例項
進入主程式目錄:/usr/local/nginx/bin
nginx # 啟動
nginx -s stop # 停止,立即
nginx -s quit # 退出,處理完現有任務後
nginx -s reopen # 重啟
nginx -s reload # 過載配置,交替更新工作程式
docker 執行 nginx 很簡單:
拉取映象:docker pull nginx
啟動容器:docker run -d --name=ngx-a -p 80:80 nginx
瀏覽器開啟主機IP顯示 NGINX 歡迎頁面。
影響 Nginx 的系統關聯項
Firewall/UFW 防火牆:埠的開放
SELinux 許可權的限制:請求後端的許可權
2.3 配置檔案結構
nginx 的配置檔案預設存於 /etc/nginx/nginx.conf,其中透過 include 引入其它目錄子配置檔案。
- 全域性塊:針對 Nginx 例項的設定,資源及事件的設定。
- HTTP:從 Client 到 Nginx 的請求設定,請求過程中要處理的各項配置;
- Upstream:代理轉發的下個目的地列表,後端服務組地址列表,連線與負載均衡的設定。
- Server:從 Nginx 到 Service 的設定,通常對應前後端某種服務或組;限制設定,代理設定,錯誤機制等。
- Location:路由匹配轉發通訊,重定向等。
配置模板示例
###### 全域性塊
worker_processes auto; # 工作程式數
error_log /var/log/nginx/error.log notice; # 錯誤級別記錄
events {
worker_connections 1024; # 單個工作程式,可承載的最大連線數
}
http {
###### MIME 配置
include /etc/nginx/mime.types; # 副檔名與檔案型別對映表
default_type application/octet-stream; # mime.types 不包含時的預設設定
###### 請求日誌配置
log_format log-format-a '$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 log-format-a; # 訪問日誌路徑 及引用格式
###### 後端可用服務配置
upstream backend-server-name {
server 192.168.1.101:80;
server 192.168.1.102:80;
}
server {
###### 請求匹配
listen 80; # 客戶端請求的埠
server_name _; # 客戶端請求的域名;區分不同服務(可多個空格區分,支援正則)
location / {
###### 重寫配置
rewrite ^<規則>$ <目的地> break;
###### 轉發到後端配置
proxy_pass http://backend-server-name$request_uri; # 轉發到後臺服務地址,來自於 upstream 項
proxy_http_version 1.1; # 指明版本(1.1預設為keep-alive長連線,1.0預設為短連線)
proxy_set_header Host $host; # 保持原來的請求域名
proxy_ignore_client_abort on; # 客戶端斷網時,是否中斷對後端的請求
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 從遠端客戶端IP到服務端的層層代理轉發IP,多IP追加空格分隔
###### Cookie 域名/路徑
proxy_cookie_domain {backend-domain} {request-domain};
proxy_cookie_path {backend-path} {request-path};
}
###### 指定檔案拒絕所有訪問
location ~ ^/(\.user.ini|\.ht|\.git|\.svn|\.project|LICENSE|README.md){
deny all;
}
###### 限制的客戶端
location \ {
deny 172.18.0.101; # 拒絕的ip
allow 172.18.1.10; # 允許的ip
}
###### 客戶端快取配置
location ~* \.(js|css|jpg|svg|gif|png)$ {
if (-f $request_filename) { # -f:只能是檔案,因為這用-f判斷了
expires 30d; # 快取有效時長 30 天
break;
}
}
###### 防盜鏈配置
location ~* \.(gif|jpg|png|bmp)$ { # 指定格式禁止的請求來源:google/baidu
valid_referers none blocked *.ttlsa.com server_names ~\.google\. ~\.baidu\.;
if ($invalid_referer) {
return 403; # 狀態碼
#rewrite ^/ http://www.ttlsa.com/403.jpg;
}
}
###### (前端) 錯誤機制
error_page 404 /404.html; # 錯誤碼 轉向的 錯誤頁
error_page 500 502 503 504 /50x.html; # 錯誤碼 轉向的 錯誤頁
location = /50x.html { # 錯誤頁 指向的 靜態頁面
root /usr/share/nginx/html;
}
}
}
2.4 前後端分離
把前端站點部署到 Nginx:
server {
listen 80;
server_name xxx.com;
# 前端配置
location / {
# 前端站點路徑
root /home/vue/dist;
index index.html
}
# 後端配置
location /api {
proxy_set_header host $HOST;
proxy_pass http://192.168.1.101:8081;
}
}
2.5 負載均衡模式
在配置項 upstream 中,負責提供可用的服務地址列表,並可指定負載均衡的實現方式。
輪詢 Round-Robin:將訪問按序依次請求到後端各個伺服器上,能確保平均負載
upstream backend-a {
server 192.168.1.101:80;
server 192.168.1.102:80;
}
權重 Weight:按百分比請求到後端伺服器上,常用到硬體配置不同的場景
upstream backend-b {
server 192.168.1.101:80 weight=3;
server 192.168.1.102:80 weight=7;
}
最少連線 Least-Connect:處理請求少的後端服務優先接收新請求
upstream backend-c {
least_conn;
server 192.168.1.101:80;
server 192.168.1.102:80;
}
IP-Hash:相同的訪問IP落到後端同個伺服器上,所以支援會話保持,但不是絕對平均負載
upstream backend-d {
ip_hash;
server 192.168.1.101:80;
server 192.168.1.102:80;
}
第三方的會話保持 sticky_cookie_insert,同時支援負載均衡。
2.6 限流與熔斷
限流:透過對併發/請求進行限速來保護系統,防止系統過載癱瘓而不能提供服務;為了更好控制整個系統的負載情況,即使阻止了某些請求,系統繼續提供服務。
http_limit_conn:單個IP同時允許的連線限制
http {
# 連線限流定義
# - $binary_remote_addr:限制物件(客戶端)
# - zone:限制自定義名稱
# - 10:記憶體中用10兆空間儲存連線記錄
limit_conn_zone $binary_remote_addr zone={limits-name}:10m;
server {
location /search/ {
# 單個IP同時允許建立多少連線(併發限制)
limit_conn {limits-name} 1;
}
}
}
http_limit_req:單個IP請求頻率的限制;次/每秒;
http {
# 請求限流定義
# - $binary_remote_addr:限制物件(客戶端)
# - zone:定義限制(策略)名稱
# - 10m:用十兆空間記錄訪問次數
# - rate:每秒10次的請求處理速率
limit_req_zone $binary_remote_addr zone={limits-name}:10m rate=1r/s;
# 請求限流定義
# - $server_name:限制物件,對指定伺服器請求的限制
limit_req_zone $server_name zone={limits-name}:10m rate=10r/s;
server {
location /search/ {
# 引用以上定義的限流策略,做以下設定(漏桶方式)
# - burst:最多接收5個排隊使用者IP,處於等待處理狀態(容量)
# - nodelay:超出排隊之外的更多請求,拒絕並返回503(溢位)
limit_req zone={limits-name} [burst=5] [nodelay];
}
}
}
http_limit_rate:向客戶端傳輸響應的速率限制;位元組/每秒/每連線;0不限制
http {
server {
location /download/ {
# 頻寬限制
limit_rate_after 5m; # 初始限速5m
limit_rate 500k; # 超出後限速500k
}
}
}
熔斷:當後端服務發生指定頻率錯誤後,Nginx觸發熔斷措施,不再請求此後端服務,直接返回預設內容到使用者端。
upstream http_backend {
# 10s內出現3次錯誤,該伺服器將被視為不可用(熔斷)
server 192.168.1.101:8080 max_fails=3 fail_timeout=10s;
server 192.168.1.102:8080 max_fails=3 fail_timeout=10s;
}
當然也有容錯機制,Nginx 預設自動轉向其它服務再請求,相關配置:proxy_next_upstream
三、效能最佳化
3.1 全域性最佳化
# 工作程式數
worker_processes auto; # 建議 CPU核心數|CPU執行緒數
# 最大支援的連線(open-file)數量;最大值受限於 Linux open files (ulimit -n)
# 建議公式:worker_rlimit_nofile > worker_processes * worker_connections
worker_rlimit_nofile 65535;
events {
use epoll; # 高效的IO多路複用(RedHat6+ 都支援 epoll)
multi_accept on; # 設定一個程式是否同時接受多個網路連線
worker_connections 10240; # 單個工作程式,可承載的最大連線數;
}
3.2 與客戶端之間的最佳化
http {
###### 零複製技術
sendfile on; # 開啟不讀到(應用本身)記憶體,直接透過系統發出資料
#sendfile_max_chunk 2m; # 每個程式每次呼叫傳輸數量不能大於設定的值,預設0為無上限。
###### 網路傳輸
# on:累積到一定容量的資料再發,傳送次數少
# off:有資料就發,傳送次數多,佔用網路資源
tcp_nopush on;
###### 長連線;使用者端比較分散,keepalive 預設值已經足夠,個人不建議重設
###### 響應資料 開啟壓縮模式
gzip on;
gzip_vary on; # 為相容老瀏覽器,追加到Header的壓縮標註
gzip_proxied any; # 所有代理請求都壓縮,也可指定型別
gzip_comp_level 2; # 壓縮等級1-9(比例)等級越大 壓縮越高 越耗CPU
gzip_min_length 128; # 壓縮前提,當返回內容大於指定時再壓縮
gzip_types text/css text/xml image/svg+xml; # 指定壓縮的檔案型別;更多壓縮項可參考 mime.types
}
3.3 與後端服務之間的最佳化
http {
upstream backend_A {
# ...
# 長連線;所有請求匯聚到後端伺服器,併發時是有必要在此基礎上重配 keepalive 的
# 以下 keepalive 需要 http 1.1 版本,並且 header connection close
keepalive 100; # 每個Worker與後端的連線池中,保持指定量的空閒連線數(QPS的10%)
keepalive_time 1h; # 每個長連線的(忙碌+空閒)總時長,超出後強制失效
keepalive_timeout 60s; # 每個長連線,最大空閒時長,超出後自動關閉長連線
keepalive_requests 1000; # 每次長連線支援的 Request 次數,超出後自動關閉
}
server {
location \ {
proxy_pass http://backend_A;
proxy_http_version 1.1; # 1.1 支援 keep-live
proxy_set_header connection ""; # 覆蓋客戶端的連線頭
}
}
}
3.4 擴充套件最佳化
多級快取:透過 Nginx 實現各種快取的配置,瀏覽器快取、CDN快取、Nginx記憶體、代理快取、後端服務應用快取等。
資源靜態化 ssi 模組:請求結果生成靜態頁,Nginx設定攔截,更多的請求直接讀靜態頁後返回;減少後端請求,定時生成靜態頁。
靜態資源同步 rsync:每臺服務都安裝,監控目錄變化,把生成的靜態頁推送到多臺伺服器。
合併請求 concat 第三方模組:將多個靜態資原始檔 合併為一次請求載入完成,減少併發量;淘寶示例:??xxx.js,yyy.js,zzz.js
四、動態負載
自動更新 upstream 上的可用服務地址,Nginx 的商業版才提供,開源的可去選擇第三方模組;這裡後端叢集管理用的是 Consul;所以這裡使用 Consul 提供的配套工具 Consul-Template。
當後端叢集 consul 上的應用服務有變動時,工具 consul-template 負責拉取 consul 最新的健康應用服務列表,並生成 nginx conf,最主要的是更新了 nginx upstream 中的可用服務地址,最後再過載 Nginx。
1、下載部署 consul-template
# 從官網 https://releases.hashicorp.com/consul-template/ 下載軟體包
curl -O https://releases.hashicorp.com/consul-template/0.31.0/consul-template_0.31.0_linux_amd64.zip
unzip consul-template_0.31.0_linux_amd64.zip consul-template # unzip解壓軟體包並重新命名
mv consul-template /usr/local/bin/ # 移動到特定目錄
/usr/local/bin/consul-template -v # 驗證安裝效果,顯示版本號
2、建立生成 nginx conf 的模板檔案 ngx-server-conf.tmp,用於生成指定格式的 nginx.conf 檔案;內容示例如下:
為便於集中管理配置檔案,存放於 nginx 預設的配置目錄下 /etc/nginx/conf.d/ngx-server-conf.tmp
########## 自動生成多個 upstream
{{range services}} {{$name := .Name}} {{$service := service .Name}}
upstream {{$name}} {
least_conn;
{{range $service}}server {{.Address}}:{{.Port}};
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
} {{end}}
########## upstream end ##########
server {
listen 80;
server_name xxx.com;
location / {
root /usr/share/nginx/html/;
index index.html;
}
########## 自動生成多個 locattion
{{range services}} {{$name := .Name}}
location /api/{{$name}} {
proxy_pass http://{{$name}};
proxy_http_version 1.1;
}
{{end}} ########## location end ##########
}
更多的模板檔案語法可參考官網說明:consul-template templating language
3、建立 consul-template 的配置檔案 ctmp.hcl,用以設定執行引數。內容示例如下:
為便於集中管理配置檔案,存放於 nginx 容器內的預設配置目錄下 /etc/nginx/conf.d/ctmp.hcl
# 連線 consul 的配置
consul {
address = "172.18.0.3:8500" # consul node
}
# 生成 nginx config file 的配置
template {
source = "/etc/nginx/conf.d/ngx-server-conf.tmp" # consul-template 的配置模板檔案
destination = "/etc/nginx/conf.d/default.conf" # 生成的 nginx-server 配置檔案
command = ["nginx", "-s", "reload"] # 執行的命令,以過載 Nginx 配置
}
# 總結步驟:從 address 拉資料,透過格式 source 生成 destination 配置,最後 command 過載Nginx。
以上更多的配置項參考官網說明:consul-template configuration options
4、啟動執行 consul-template
# nginx 容器中啟動 consul-template
/usr/local/bin/consul-template -config /etc/nginx/conf.d/ctmp.hcl
# 驗證效果:可檢視生成的 nginx conf
cat /etc/nginx/conf.d/default.conf
效果:先檢視當前 nginx default.conf;之後停掉後端一個應用服務,對比 nginx 前後兩個 default.conf 的內容變化。
五、高可用
高可用 - 雙機熱備:主機和從機透過TCP/IP網路連線,正常情況下主機處於工作狀態,從機處於監視狀態,一旦從機發現主機異常,從機將會在很短的時間之內代替主機,完全實現主機的功能。
工具 Keepalived,安裝到內網中每個裝有 Nginx 的系統上:
多個 Keepalived 例項形成一個組,組成員有 Master/slave 之分,時時監控組內所有 Keepalived 例項的執行情況;
Keepalived 透過一個虛擬IP加入到 Master 網路卡上,所以透過虛擬IP能夠直接連線到 Master上,也就是其中一個 Nginx;
一個 Keepalived 成員當機後,從 Slave 中選舉出新的 Master,也就是把虛擬IP自動加入到新的 Master上,持續提供服務;
對外僅透過內網的虛擬IP完成與 Nginx 的連線,而不關心使用的 Nginx 在哪臺機上。
5.1 配置執行 Keepalived
安裝 Keepalived:yum install -y keepalived
配置檔案:/etc/keepalived/keepalived.conf
配置模板:可僅留 global_defs,vrrp_instance,virtual_ipaddress
global_defs {
router_id <key> # 同組不重複的唯一標識
}
vrrp_instance <Instance-Name> {
state MASTER # MASTER/BACKUP;有主優先執行
interface <net-card-name> # 指定虛擬IP寄存的網路卡
priority 100 # 相同角色的優先順序,越大越優先
advert_int 1 # 間隔秒檢測一次成員的執行狀況
authentication { # 成員間的通訊憑證
auth_type PASS # 同組相同的方式
auth_pass 1111 # 同組相同的編碼,保持成員互通
}
virtual_ipaddress { # 追加新IP,對外提供的通訊入口
192.168.17.200 # 同網段的、未被使用的、虛擬新IP
}
}
啟動後,可在 Master 中的指定網路卡中看到已追加的虛擬IP;不妨 ping 下你的虛擬IP...
再配置一臺 Keepalived,注意這裡配置的不同項為:router_id / state / priority
瀏覽器中訪問虛擬IP,就直接訪問了 Master 上的 Nginx;
關掉Master伺服器後,Keepalived 將虛擬IP又新增到了備用伺服器上了;
虛擬IP繼續提供正常的 Nginx 服務,瀏覽器正常訪問虛擬IP地址;
當 Master 修復啟動後,Keepalived 又將虛擬IP自動切換到 Master 上,始終以 Master 優先使用。
注意
Keepalived 僅檢測自己主程式的執行狀況,並不是檢測 Nginx 的執行狀況;
所以:當 Nginx 錯誤,而 Keepalived 執行正常時,並不能達到 Master 轉移的效果;
方案:用指令碼定時檢測 Nginx 的主程式,Nginx發生錯誤時主動 Kill Keepalived,達到 Master 轉移的效果。
5.2 指令碼檢測 Nginx 服務
建立檢測指令碼檔案 /etc/keepalived/check_nginx.sh 內容示例:
#!/bin/bash
# 檢測 Nginx worker process 執行的個數
ckn=$(ps -C nginx --no-heading | wc -l)
# 當沒有 Nginx worker process 執行時
# 製造keepalived異常,使得啟用備用伺服器
if [ $ckn -eq 0 ]; then
killall -9 keepalived
fi
並賦予使用者可執行許可權;如:chmod +x /etc/keepalived/check_nginx.sh
Keepalived 指令碼定時檢測配置內容示例:
# global_defs ...
vrrp_script check_nginx_status { ### 定義檢測策略
user root # 負責檢測的使用者
interval 1 # 檢測間隔秒
script /etc/keepalived/check_nginx.sh # 檢測的可執行檔案
}
vrrp_instance <Instance-Name> {
# ...
track_script {
check_nginx_status # 引用檢測策略
}
# ...
}
在 Master 上停止 Nginx 執行 試試看...??
以上是一個聯動停止執行的方案,那是不是需要一個聯動啟動也更為方便,自行考量...