1. 程式人生 > 實用技巧 >「查缺補漏」鞏固你的Nginx知識體系

「查缺補漏」鞏固你的Nginx知識體系

Nginx篇


基本介紹

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

  1. 支援高併發連線,尤其是靜態介面,官方測試Nginx能夠支撐5萬併發連線

  2. 記憶體佔用極低

  3. 配置簡單,使用靈活,可以基於自身需要增強其功能,同時支援自定義模組的開發

    使用靈活:可以根據需要,配置不同的負載均衡模式,URL地址重寫等功能

  4. 穩定性高,在進行反向代理時,宕機的概率很低

  5. 支援熱部署,應用啟動過載非常迅速

基礎使用

Windows版

安裝

檔案下載地址:http://nginx.org/en/docs/windows.html

如果下載很慢可以用該連結:

百度雲盤連結: https://pan.baidu.com/s/1r3mSEGhmz4HA46Cw9w6QTQ 提取碼: d8bi

解壓即可

基本命令

# 啟動
# 建議使用第一種,第二種會使視窗一直處於執行中,不能進行其他命令操作
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

簡單Demo

  1. 利用SwitchHost軟體編輯域名和IP的對映關係,或到目錄C:\Windows\System32\drivers\etc下,編輯hosts檔案,增加配置如下(Mac 同理)

    127.0.0.1  kerwin.demo.com
    

    PS:推薦使用軟體SwitchHost,工作時幾乎是必用的

  2. 修改配置,如圖所示:

效果如圖所示:

Nginx在架構體系中的作用

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

閘道器

閘道器:可以簡單的理解為使用者請求和伺服器響應的關口,即面向使用者的總入口

閘道器可以攔截客戶端所有請求,對該請求進行許可權控制、負載均衡、日誌管理、介面呼叫監控等,因此無論使用什麼架構體系,都可以使用Nginx作為最外層的閘道器

虛擬主機

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

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

  • 基於 IP 的虛擬主機
  • 基於域名的虛擬主機
  • 基於埠的虛擬主機

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

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

路由

Nginx的配置檔案中,我們經常可以看到這樣的配置:

location / {
	#....
}

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

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&not_self=1;
 }

效果如下:

因為路由的存在,為我們後續解決跨域問題提供了一定的思路,同時配置內容和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介面,如:

location / {
	root   html;
	index  index.html index.htm;
}

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

負載均衡

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

輪詢

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

upstream tomcatserver {
	server 192.168.0.1;
	server 192.168.0.2;
}

輪詢策略是預設的負載均衡策略

指定權重

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

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

IP Hash

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

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

fair

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

upstream tomcatserver {
	server server1;
	server server2;
	fair;
}

url_hash

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

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

Nginx的模組化設計

先來看看Nginx模組架構圖:

這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的多程序模型

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

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

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

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

這種設計帶來以下優點:

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支援這樣的配置,如下:

http {
	# 省略中間配置

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

具體檔案配置如:

# 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;
    }
}

Nginx全量配置引數說明

# 執行使用者
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;
        }
    }
}

Nginx還能做什麼

解決CORS跨域問題

思路有兩個:

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

適配 PC 或移動裝置

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

請求限流

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

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    server {
        location /search/ {
            limit_req zone=one burst=5 nodelay;
        }
    }
}      

其他技巧

如:圖片防盜鏈,請求過濾,泛域名轉發,配置HTTPS等等

可參考文章:《Nginx 從入門到實踐,萬字詳解!》

常見問題

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有幾種程序模型?

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

參考

最後

如果你覺得這篇內容對你挺有幫助的話:

  1. 當然要點贊支援一下啦~
  2. 搜尋並關注公眾號「是Kerwin啊」,一起嘮嘮嗑~
  3. 再來看看最近幾篇的「查漏補缺」系列吧,該系列會持續輸出~