查漏補缺!我再講一遍Nginx,務必記住這10000字內容

基本介紹

Nginx是一款輕量級的 Web服務器 / 反向代理服務器 / 電子郵件(IMAP/POP3)代理服務器,主要的優點是:

  1. 支持高併發連接,尤其是靜態界面,官方測試Nginx能夠支撐5萬併發連接
  2. 內存佔用極低
  3. 配置簡單,使用靈活,可以基於自身需要增強其功能,同時支持自定義模塊的開發使用靈活:可以根據需要,配置不同的負載均衡模式,URL地址重寫等功能
  4. 穩定性高,在進行反向代理時,宕機的概率很低
  5. 支持熱部署,應用啟動重載非常迅速

基礎使用

Windows版

基本命令

<code># 啟動
# 建議使用第一種,第二種會使窗口一直處於執行中,不能進行其他命令操作
C:\\server\\nginx-1.19.2> start nginx
C:\\server\\nginx-1.19.2> nginx.exe

# 停止
# stop是快速停止nginx,可能並不保存相關信息;quit是完整有序的停止nginx,並保存相關信息
C:\\server\\nginx-1.19.2> nginx.exe -s stop

C:\\server\\nginx-1.19.2> nginx.exe -s quit

# 重載Nginx
# 當配置信息修改,需要重新載入這些配置時使用此命令
C:\\server\\nginx-1.19.2> nginx.exe -s reload

# 重新打開日誌文件
C:\\server\\nginx-1.19.2> nginx.exe -s reopen

# 查看Nginx版本
C:\\server\\nginx-1.19.2> nginx -v

# 查看配置文件是否正確
C:\\server\\nginx-1.19.2> nginx -t

/<code>

簡單Demo

  1. 利用SwitchHost軟件編輯域名和IP的映射關係,或到目錄C:\\Windows\\System32\\drivers\\etc下,編輯hosts文件,增加配置如下(Mac 同理)127.0.0.1 kerwin.demo.com PS:推薦使用軟件SwitchHost,工作時幾乎是必用的
  2. 修改配置,如圖所示:
查漏補缺!我再講一遍Nginx,務必記住這10000字內容


效果如圖所示:

查漏補缺!我再講一遍Nginx,務必記住這10000字內容


Nginx在架構體系中的作用

  • 網關 (面向客戶的總入口)
  • 虛擬主機(為不同域名 / ip / 端口提供服務。如:VPS虛擬服務器)
  • 路由(正向代理 / 反向代理)
  • 靜態服務器
  • 負載集群(提供負載均衡)

網關

網關:可以簡單的理解為用戶請求和服務器響應的關口,即面向用戶的總入口

網關可以攔截客戶端所有請求,對該請求進行權限控制、負載均衡、日誌管理、接口調用監控等,因此無論使用什麼架構體系,都可以使用Nginx作為最外層的網關

虛擬主機

虛擬主機的定義:虛擬主機是一種特殊的軟硬件技術,它可以將網絡上的每一臺計算機分成多個虛擬主機,每個虛擬主機可以獨立對外提供 www 服務,這樣就可以實現一臺主機對外提供多個 web 服務,每個虛擬主機之間是獨立的,互不影響的。

通過 Nginx 可以實現虛擬主機的配置,Nginx 支持三種類型的虛擬主機配置

  • 基於 IP 的虛擬主機
  • 基於域名的虛擬主機
  • 基於端口的虛擬主機

表現形式其實大家多見過,即:

<code># 每個 server 就是一個虛擬主機
http {
# ...
server{
# ...
}

# ...
server{
# ...
}
}

/<code>

路由

在Nginx的配置文件中,我們經常可以看到這樣的配置:

<code>location / {
#....
}

/<code>

location在此處就起到了路由的作用,比如我們在同一個虛擬主機內定義兩個不同的路由,如下:

<code>location / {
proxy_pass https://www.baidu.com/;
}

location /api {
proxy_pass https://apinew.juejin.im/user_api/v1/user/get?aid=2608&user_id=1275089220013336¬_self=1;
}

/<code>

效果如下:


查漏補缺!我再講一遍Nginx,務必記住這10000字內容


因為路由的存在,為我們後續解決跨域問題提供了一定的思路,同時配置內容和API接口等更加方便

PS:路由的功能非常強大,支持正則匹配

正向與反向代理

此處額外解釋一下proxy_pass的含義

在Nginx中配置proxy_pass代理轉發時,如果在proxy_pass後面的url加 /,表示絕對根路徑;

如果沒有/,表示相對路徑

正向代理

  1. 代理客戶;
  2. 隱藏真實的客戶,為客戶端收發請求,使真實客戶端對服務器不可見;
  3. 一個局域網內的所有用戶可能被一臺服務器做了正向代理,由該臺服務器負責 HTTP 請求;
  4. 意味著同服務器做通信的是正向代理服務器;

反向代理

  1. 代理服務器;
  2. 隱藏了真實的服務器,為服務器收發請求,使真實服務器對客戶端不可見;
  3. 負載均衡服務器,將用戶的請求分發到空閒的服務器上;
  4. 意味著用戶和負載均衡服務器直接通信,即用戶解析服務器域名時得到的是負載均衡服務器的 IP ;

共同點

  1. 都是做為服務器和客戶端的中間層
  2. 都可以加強內網的安全性,阻止 web 攻擊
  3. 都可以做緩存機制,提高訪問速度

區別

  1. 正向代理其實是客戶端的代理,反向代理則是服務器的代理。
  2. 正向代理中,服務器並不知道真正的客戶端到底是誰;而在反向代理中,客戶端也不知道真正的服務器是誰。
  3. 作用不同。正向代理主要是用來解決訪問限制問題;而反向代理則是提供負載均衡、安全防護等作用。

靜態服務器

靜態服務器是Nginx的強項,使用非常容易,在默認配置下本身就是指向了靜態的HTML界面,如:

<code>location / {
root html;
index index.html index.htm;
}

/<code>

所以前端同學們,如果構建好了界面,可以進行相應的配置,把界面指向目標文件夾中即可,root指的是html文件夾

負載均衡

負載均衡功能是Nginx另一大殺手鐧,一共有5種方式,著重介紹一下。

輪詢

每個請求按時間順序逐一分配到不同的後端服務器,如果後端服務器down掉,能自動剔除,配置如下:

<code>upstream tomcatserver {
server 192.168.0.1;
server 192.168.0.2;
}


/<code>

輪詢策略是默認的負載均衡策略

指定權重

即在輪詢的基礎之上,增加權重的概念,weight和訪問比率成正比,用於後端服務器性能不均的情況,配置如下:

<code>upstream tomcatserver {
server 192.168.0.1 weight=1;
server 192.168.0.2 weight=10;
}

/<code>

IP Hash

每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個後端服務器,可以解決session的問題,配置如下:

<code>upstream tomcatserver {
ip_hash;
server 192.168.0.14:88;
server 192.168.0.15:80;
}

/<code>

fair

第三方提供的負載均衡策略,按後端服務器的響應時間來分配請求,響應時間短的優先分配,生產環境中有各種情況可能導致響應時間波動,需要慎用

<code>upstream tomcatserver { 

server server1;
server server2;
fair;
}

/<code>

url_hash

第三方提供的負載均衡策略,按訪問url的hash結果來分配請求,使每個url定向到同一個後端服務器

<code>upstream tomcatserver {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}

/<code>

Nginx的模塊化設計

先來看看Nginx模塊架構圖:

查漏補缺!我再講一遍Nginx,務必記住這10000字內容


這5個模塊由上到下重要性一次遞減。

(1)核心模塊;

核心模塊是Nginx服務器正常運行必不可少的模塊,如同操作系統的內核。它提供了Nginx最基本的核心服務。像進程管理、權限控制、錯誤日誌記錄等;

(2)標準HTTP模塊;

標準HTTP模塊支持標準的HTTP的功能,如:端口配置,網頁編碼設置,HTTP響應頭設置等;

(3)可選HTTP模塊;

可選HTTP模塊主要用於擴展標準的HTTP功能,讓Nginx能處理一些特殊的服務,如:解析GeoIP請求,SSL支持等;

(4)郵件服務模塊;

郵件服務模塊主要用於支持Nginx的郵件服務;

(5)第三方模塊;

第三方模塊是為了擴展Nginx服務器應用,完成開發者想要的功能,如:Lua支持,JSON支持等;

模塊化設計使得Nginx方便開發和擴展,功能很強大

Nginx的請求處理流程

基於上文中的Nginx模塊化結構,我們很容易想到,在請求的處理階段也會經歷諸多的過程,Nginx 將各功能模塊組織成一條鏈,當有請求到達的時候,請求依次經過這條鏈上的部分或者全部模塊,進行處理,每個模塊實現特定的功能。

一個 HTTP Request 的處理過程:

  • 初始化 HTTP Request
  • 處理請求頭、處理請求體
  • 如果有的話,調用與此請求(URL 或者 Location)關聯的 handler
  • 依次調用各 phase handler 進行處理
  • 輸出內容依次經過 filter 模塊處理
查漏補缺!我再講一遍Nginx,務必記住這10000字內容


Nginx的多進程模型

Nginx 在啟動後,會有一個 master 進程和多個 worker 進程。

master 進程主要用來管理worker 進程,包括接收來自外界的信號,向各 worker 進程發送信號,監控 worker 進程的運行狀態以及啟動 worker 進程。

worker 進程是用來處理來自客戶端的請求事件。多個 worker 進程之間是對等的,它們同等競爭來自客戶端的請求,各進程互相獨立,一個請求只能在一個 worker 進程中處理。worker 進程的個數是可以設置的,一般會設置與機器 CPU 核數一致,這裡面的原因與事件處理模型有關

Nginx 的進程模型,可由下圖來表示:

查漏補缺!我再講一遍Nginx,務必記住這10000字內容


這種設計帶來以下優點:

1) 利用多核系統的併發處理能力

現代操作系統已經支持多核 CPU 架構,這使得多個進程可以分別佔用不同的 CPU 核心來工作。Nginx 中所有的 worker 工作進程都是完全平等的。這提高了網絡性能、降低了請求的時延。

2) 負載均衡

多個 worker 工作進程通過進程間通信來實現負載均衡,即一個請求到來時更容易被分配到負載較輕的 worker 工作進程中處理。這也在一定程度上提高了網絡性能、降低了請求的時延。

3) 管理進程會負責監控工作進程的狀態,並負責管理其行為

管理進程不會佔用多少系統資源,它只是用來啟動、停止、監控或使用其他行為來控制工作進程。首先,這提高了系統的可靠性,當 worker 進程出現問題時,管理進程可以啟動新的工作進程來避免系統性能的下降。其次,管理進程支持 Nginx 服務運行中的程序升級、配置項修改等操作,這種設計使得動態可擴展性、動態定製性較容易實現。

Nginx如何解決驚群現象

什麼是驚群現象?

驚群效應(thundering herd)是指多進程(多線程)在同時阻塞等待同一個事件的時候(休眠狀態),如果等待的這個事件發生,那麼他就會喚醒等待的所有進程(或者線程),但是最終卻只能有一個進程(線程)獲得這個時間的“控制權”,對該事件進行處理,而其他進程(線程)獲取“控制權”失敗,只能重新進入休眠狀態,這種現象和性能浪費就叫做驚群效應。

上文中介紹了Nginx的多進程模型,而典型的多進程模型正如文中所說,多個worker進程之間是對等的,因此當一個請求到來的時候,所有進程會同時開始競爭,最終執行的又只有一個,這樣勢必會造成資源的浪費。

Nginx解決該問題的思路是:不讓多個進程在同一時間監聽接受連接的socket,而是讓每個進程輪流監聽,這樣當有連接過來的時候,就只有一個進程在監聽那肯定就沒有驚群的問題。

具體做法是:利用一把進程間鎖,每個進程中都

嘗試獲得這把鎖,如果獲取成功將監聽socket加入wait集合中,並設置超時等待連接到來,沒有獲得鎖的進程則將監聽socket從wait集合去除。

事件驅動模型和異步非阻塞IO

承接上文,我們知道了Nginx的多進程模型後瞭解到,其工作進程實際上只有幾個,但為什麼依然能獲得如此高的併發性能,當然與其採用的事件驅動模型和異步非阻塞IO的方式來處理請求有關。

Nginx服務器響應和處理Web請求的過程,是基於事件驅動模型的,它包含事件收集器、事件發送器和事件處理器等三部分基本單元,著重關注事件處理器,而一般情況下事件處理器有這麼幾種辦法:

  • 事件發送器每傳遞過來一個請求,目標對象就創建一個新的進程
  • 事件發送器每傳遞過來一個請求,目標對象就創建一個新的線程,來進行處理
  • 事件發送器每傳遞過來一個請求,目標對象就將其放入一個待處理事件的列表,使用非阻塞I/O方式調用

第三種方式,在編寫程序代碼時,邏輯比前面兩種都複雜。大多數網絡服務器採用了第三種方式,逐漸形成了所謂的事件驅動處理庫。

事件驅動處理庫又被稱為多路IO複用方法,最常見的包括以下三種:select模型,poll模型和epoll模型。

其中Nginx就默認使用的是epoll模型,同時也支持其他事件模型。

epoll的幫助就在於其提供了一種機制,可以讓進程同時處理多個併發請求,不用關心IO調用的具體狀態。IO調用完全由事件驅動模型來管理,這樣一來,當某個工作進程接收到客戶端的請求以後,調用IO進行處理,如果不能立即得到結果,就去處理其他的請求;而工作進程在此期間也無需等待響應,可以去處理其他事情;當IO返回時,epoll就會通知此工作進程;該進程得到通知後,會來繼續處理未完的請求

Nginx配置的最佳實踐

在生產環境或者開發環境中Nginx一般會代理多個虛擬主機,如果把所有的配置文件寫在默認的nginx.conf中,看起來會非常臃腫,因此建議將每一個虛擬文件單獨放置一個文件夾,Nginx支持這樣的配置,如下:

<code>http {
# 省略中間配置

# 引用該目錄下以 .conf 文件結尾的配置
include /etc/nginx/conf.d/*.conf;
}

/<code>

具體文件配置如:

<code># Demo
upstream web_pro_testin {
server 10.42.46.70:6003 max_fails=3 fail_timeout=20s;
ip_hash;
}

server {
listen 80;
server_name web.pro.testin.cn;
location / {
proxy_pass http://web_pro_testin;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

location ~ ^/(WEB-INF)/ {
deny all;
}
}

/<code>

Nginx全量配置參數說明

<code># 運行用戶
user www-data;

# 啟動進程,通常設置成和cpu的數量相等
worker_processes 6;

# 全局錯誤日誌定義類型,[debug | info | notice | warn | error | crit]
error_log logs/error.log;

error_log logs/error.log notice;
error_log logs/error.log info;

# 進程pid文件
pid /var/run/nginx.pid;

# 工作模式及連接數上限
events {
# 僅用於linux2.6以上內核,可以大大提高nginx的性能
use epoll;

# 單個後臺worker process進程的最大併發鏈接數
worker_connections 1024;

# 客戶端請求頭部的緩衝區大小
client_header_buffer_size 4k;

# keepalive 超時時間
keepalive_timeout 60;

# 告訴nginx收到一個新連接通知後接受盡可能多的連接
# multi_accept on;
}

#設定http服務器,利用它的反向代理功能提供負載均衡支持
http {
# 文件擴展名與文件類型映射表義
include /etc/nginx/mime.types;

# 默認文件類型
default_type application/octet-stream;

# 默認編碼
charset utf-8;

# 服務器名字的hash表大小
server_names_hash_bucket_size 128;


# 客戶端請求頭部的緩衝區大小
client_header_buffer_size 32k;

# 客戶請求頭緩衝大小
large_client_header_buffers 4 64k;

# 設定通過nginx上傳文件的大小
client_max_body_size 8m;

# 開啟目錄列表訪問,合適下載服務器,默認關閉。
autoindex on;

# sendfile 指令指定 nginx 是否調用 sendfile 函數(zero copy 方式)來輸出文件,對於普通應用,
# 必須設為 on,如果用來進行下載等應用磁盤IO重負載應用,可設置為 off,以平衡磁盤與網絡I/O處理速度
sendfile on;

# 此選項允許或禁止使用socke的TCP_CORK的選項,此選項僅在使用sendfile的時候使用
#tcp_nopush on;

# 連接超時時間(單秒為秒)
keepalive_timeout 65;

# gzip模塊設置
gzip on; #開啟gzip壓縮輸出
gzip_min_length 1k; #最小壓縮文件大小
gzip_buffers 4 16k; #壓縮緩衝區
gzip_http_version 1.0; #壓縮版本(默認1.1,前端如果是squid2.5請使用1.0)
gzip_comp_level 2; #壓縮等級
gzip_types text/plain application/x-javascript text/css application/xml;

gzip_vary on;

# 開啟限制IP連接數的時候需要使用
#limit_zone crawler $binary_remote_addr 10m;

# 指定虛擬主機的配置文件,方便管理
include /etc/nginx/conf.d/*.conf;

# 負載均衡配置
upstream mysvr {
# 請見上文中的五種配置
}

# 虛擬主機的配置
server {

# 監聽端口
listen 80;

# 域名可以有多個,用空格隔開
server_name www.jd.com jd.com;

# 默認入口文件名稱
index index.html index.htm index.php;
root /data/www/jd;

# 圖片緩存時間設置
location ~ .*.(gif|jpg|jpeg|png|bmp|swf)${
expires 10d;
}

#JS和CSS緩存時間設置
location ~ .*.(js|css)?${
expires 1h;
}

# 日誌格式設定
#$remote_addr與$http_x_forwarded_for用以記錄客戶端的ip地址;
#$remote_user:用來記錄客戶端用戶名稱;

#$time_local: 用來記錄訪問時間與時區;
#$request: 用來記錄請求的url與http協議;
#$status: 用來記錄請求狀態;成功是200,
#$body_bytes_sent :記錄發送給客戶端文件主體內容大小;
#$http_referer:用來記錄從那個頁面鏈接訪問過來的;
log_format access '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';

# 定義本虛擬主機的訪問日誌
access_log /usr/local/nginx/logs/host.access.log main;
access_log /usr/local/nginx/logs/host.access.404.log log404;

# 對具體路由進行反向代理
location /connect-controller {

proxy_pass http://127.0.0.1:88;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;

# 後端的Web服務器可以通過X-Forwarded-For獲取用戶真實IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;

# 允許客戶端請求的最大單文件字節數
client_max_body_size 10m;

# 緩衝區代理緩衝用戶端請求的最大字節數,
client_body_buffer_size 128k;

# 表示使nginx阻止HTTP應答代碼為400或者更高的應答。
proxy_intercept_errors on;

# nginx跟後端服務器連接超時時間(代理連接超時)

proxy_connect_timeout 90;

# 後端服務器數據回傳時間_就是在規定時間之內後端服務器必須傳完所有的數據
proxy_send_timeout 90;

# 連接成功後,後端服務器響應的超時時間
proxy_read_timeout 90;

# 設置代理服務器(nginx)保存用戶頭信息的緩衝區大小
proxy_buffer_size 4k;

# 設置用於讀取應答的緩衝區數目和大小,默認情況也為分頁大小,根據操作系統的不同可能是4k或者8k
proxy_buffers 4 32k;

# 高負荷下緩衝大小(proxy_buffers*2)
proxy_busy_buffers_size 64k;

# 設置在寫入proxy_temp_path時數據的大小,預防一個工作進程在傳遞文件時阻塞太長
# 設定緩存文件夾大小,大於這個值,將從upstream服務器傳
proxy_temp_file_write_size 64k;
}

# 動靜分離反向代理配置(多路由指向不同的服務端或界面)
location ~ .(jsp|jspx|do)?$ {
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://127.0.0.1:8080;
}
}

}

/<code>

Nginx還能做什麼

解決CORS跨域問題

思路有兩個:

  • 基於多路由,把跨域的兩個請求發到各自的服務器,然後統一訪問入口即可避免該問題
  • 利用Nginx配置Headerd的功能,為其附上相應的請求頭

適配 PC 或移動設備

根據用戶設備不同返回不同樣式的站點,以前經常使用的是純前端的自適應佈局,但無論是複雜性和易用性上面還是不如分開編寫的好,比如我們常見的淘寶、京東......這些大型網站就都沒有采用自適應,而是用分開製作的方式,根據用戶請求的 user-agent 來判斷是返回 PC 還是 H5 站點

請求限流

Nginx按請求速率限速模塊使用的是漏桶算法,即能夠強行保證請求的實時處理速度不會超過設置的閾值,如:

<code>http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

server {
location /search/ {
limit_req zone=one burst=5 nodelay;
}
}
}

/<code>

常見問題

Q1:Nginx一般用作什麼?

見上文中Nginx在架構體系中的作用,配合Nginx還能做什麼作答即可

Q2:為什麼要用Nginx?

理解網關的必要性,以及Nginx保證高可用,負載均衡的能力

Q3:為什麼Nginx這麼快?

如果一個server採用一個進程負責一個request的方式,那麼進程數就是併發數。那麼顯而易見的,就是會有很多進程在等待中。等什麼?最多的應該是等待網絡傳輸。

而nginx 的異步非阻塞工作方式正是利用了這點等待的時間。在需要等待的時候,這些進程就空閒出來待命了。因此表現為少數幾個進程就解決了大量的併發問題。

nginx是如何利用的呢,簡單來說:同樣的4個進程,如果採用一個進程負責一個request的方式,那麼,同時進來4個request之後,每個進程就負責其中一個,直至會話關閉。期間,如果有第5個request進來了。就無法及時反應了,因為4個進程都沒幹完活呢,因此,一般有個調度進程,每當新進來了一個request,就新開個進程來處理。

nginx不這樣,每進來一個request,會有一個worker進程去處理。但不是全程的處理,處理到什麼程度呢?處理到可能發生阻塞的地方,比如向上遊(後端)服務器轉發request,並等待請求返回。那麼,這個處理的worker不會這麼傻等著,他會在發送完請求後,註冊一個事件:“如果upstream返回了,告訴我一聲,我再接著幹”。於是他就休息去了。此時,如果再有request 進來,他就可以很快再按這種方式處理。而一旦上游服務器返回了,就會觸發這個事件,worker才會來接手,這個request才會接著往下走。

由於web server的工作性質決定了每個request的大部份生命都是在網絡傳輸中,實際上花費在server機器上的時間片不多。這是幾個進程就解決高併發的秘密所在。

總結:事件模型,異步非阻塞,多進程模型加上細節優化的共同作用

Q4:什麼是正向代理和反向代理

見上文

Q5:Nginx負載均衡的算法有哪些?

見上文

Q6:Nginx如何解決的驚群現象?

見上文

Q7:Nginx為什麼不用多線程模型?

深入理解多進程模型加上異步非阻塞IO的好處以及多線程模型中上下文切換的劣勢

Q8:Nginx壓縮功能有什麼壞處嗎?

非常耗費服務器的CPU

Q9:Nginx有幾種進程模型?

實際上有兩種,多進程和單進程,但是實際工作中都是多進程的

最後

大家看完有什麼不懂的可以在下方留言討論.
謝謝你的觀看。
覺得文章對你有幫助的話記得關注我點個贊支持一下!

作者:不假
作者:Kerwin_
鏈接:https://juejin.im/post/6870264679063617550


分享到:


相關文章: