Nginx 反向代理 + 緩存 + 靜態資源服務器 + 負載均衡

nginx經常掛在嘴邊的就是反向代理,不過他還可以干很多事,我所了解的只是反向代理、靜態文件緩存、靜態資源服務器,對于負載均衡只是略有涉及。

Nginx (“engine x”) 是一個高性能的 HTTP 和 反向代理 服務器 ,也是一個 IMAP/POP3/SMTP 代理 服務器 。 Nginx 是由 Igor Sysoev 為俄羅斯訪問量第二的 Rambler.ru 站點開發的,第一個公開版本0.1.0發布于2004年10月4日。其將源代碼以類BSD許可證的形式發布,因它的穩定性、豐富的功能集、示例配置文件和低系統資源的消耗而聞名

引用一下菜鳥教程的簡介:Nginx功能豐富,可作為HTTP服務器,也可作為反向代理服務器,郵件服務器。支持FastCGI、SSL、Virtual Host、URL Rewrite、Gzip等功能。并且支持很多第三方的模塊擴展。Nginx的穩定性、功能集、示例配置文件和低系統資源的消耗讓他后來居上,在全球活躍的網站中有12.18%的使用比率,大約為2220萬個網站。

特點
(1):代理服務器,快速高效反向代理,提升網站性能。
(2):負載均衡器,內部支持Rails和PHP,也可支持HTTP代理服務器,對外進行服務。同時支持簡單容錯和利用算法進行負載均衡。
(3):性能方面,Nginx專門為性能設計,實現注重效率。采用Poll模型,可以支持更多的并發連接,并在大并發時占用很低內存。
(4):穩定性方面,采用分階段資源分配技術,使CPU資源占用率低。
(5):高可用性方面,支持熱備,啟動迅速。

nginx安裝

mac 下安裝

brew install nginx

安裝目錄為 /usr/local/Cellar/nginx/1.17.2/
配置文件目錄為 /usr/local/etc/nginx/nginx.conf
服務器默認路徑 /usr/local/var/www

常用命令

mac 下的啟動命令

  • 啟動 nginx

  • 快速停止關閉 nignx -s stop

  • 優雅的關閉 nginx -s quit

  • 承載配置文件 nginx -s reload

  • 查看nginx進程 ps -ef | grep nginx

  • 查看配置文件是否正確 nginx -t

  • 優雅的殺死nginx進程 kill -quit 進程號

  • 快速的殺死nginx進程 kill -term 進程號

nginx配置

nginx 文件的默認配置文件位置 /usr/local/etc/nginx/nginx.conf

打開 /usr/local/etc/nginx/ 目錄可以看到,里面有很多的配置文件,啟動有一個nginx.confnginx.conf.default兩個配置文件,剛開始安裝的時候,兩個文件的內容是一樣的,所以我們可以肆意的修改nginx.conf搞崩的話就直接把nginx.conf.default中的內容復制過來就行了又是一個新的nginx。

配置文件架構

// nginx全局塊
...

// events塊
events {
    ...
}

// http 塊
http {
    // http全局塊
    ...
    
    // server塊
    server {
        ...
    }
    
    // http全局塊
    ...
}

配置文件加注釋說明

# 配置nginx的用戶組 默認為nobody
#user  nobody;

# 配置nginx的主線程數量 nginx是一個主線程下面多個子線程
worker_processes  1;

# 配置nginx的錯誤日志 格式為 log路徑 log級別
# error_log 的日志級別為: debug info notice warn error crit alert emerg 緊急由低到高
# error_log的默認日志級別為error,那么就只有緊急程度大于等于error的才會記錄在日志
# error_log 的作用域為 main http mail stream server location

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

# 指定nginx進程運行文件存放地址
#pid        logs/nginx.pid;


events {
    # poll是多路復用IO中的一種方式,但是僅用于linux2.6以上內核,可以大大提高nginx的性能
    # use poll
    
    # 設置網絡的連接序列化 防止驚群現象發生 默認為 on
    # accept_mutex on;
    
    # 設置一個進程是否同時接受多個網絡連接 默認為 off
    # multi_accept off
    
    # 最大連接數 默認為 512
    worker_connections  1024;
}


http {
    # 文件擴展名和文件類型映射表
    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 系統調用來傳輸文件。優點在于在兩個文件描述符之間傳遞數據(完全在內核中操作),從而避免了數據在內核緩沖區和用戶緩沖區之間的拷貝,效率高,稱之為零拷貝,這個東西有點講究,自行百度
    # sendfile 作用域 location server http
    sendfile        on;
    
    #tcp_nopush     on;
    
    # 鏈接超時時間 默認 75s 作用域 http server location
    #keepalive_timeout  0;
    keepalive_timeout  65;
    
    # 開始gzip壓縮
    #gzip  on;

    server {
        # 端口號
        listen       8080;
        # 域名或ip
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
        
        # 對請求的路由進行過濾 正則匹配
        location / {
            root   html;
            index  index.html index.htm;
        }

        ...
    }
    include servers/*;
}

nginx 日志

nginx的日志大致分為 access_logerror_log。error_log 記錄的是nginx的錯誤日志。(以下對日志的理解不是很全面,還只是基礎的)

error_log

  • 記錄nginx錯誤日志
  • 作用域為 main http mail stream server location
  • 日志級別 debug info notice warn error crit alert emerg
  • 日志級別默認為 error 當級別高于或等于指定級別時才會記錄

access_log

  • 記錄請求通過的日志
  • 作用域為 http server location limit_except
  • 日志格式默認為 combined
  • 日志格式是可以自定義的
# 定義一個為 main 的日志格式
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;

上方的 log_format 后面類似 $remote_addr 是nginx的內置變量,取值如下

$remote_addr, $http_x_forwarded_for(反向) 記錄客戶端IP地址
$remote_user 記錄客戶端用戶名稱
$request 記錄請求的URL和HTTP協議
$status 記錄請求狀態
$body_bytes_sent 發送給客戶端的字節數,不包括響應頭的大小; 該變量與Apache模塊mod_log_config里的“%B”參數兼容。
$bytes_sent 發送給客戶端的總字節數。
$connection 連接的序列號。
$connection_requests 當前通過一個連接獲得的請求數量。
$msec 日志寫入時間。單位為秒,精度是毫秒。
$pipe 如果請求是通過HTTP流水線(pipelined)發送,pipe值為“p”,否則為“.”。
$http_referer 記錄從哪個頁面鏈接訪問過來的
$http_user_agent 記錄客戶端瀏覽器相關信息
$request_length 請求的長度(包括請求行,請求頭和請求正文)。
$request_time 請求處理時間,單位為秒,精度毫秒; 從讀入客戶端的第一個字節開始,直到把最后一個字符發送給客戶端后進行日志寫入為止。
$time_iso8601 ISO8601標準格式下的本地時間。
$time_local 通用日志格式下的本地時間。

反向代理

正向代理 反向代理

  • 正向代理大概的意思就是,客戶端發送一個請求,這個請求包含服務器地址,那么代理服務器收到了請求后會將請求發送到客戶端指定的服務器,并將響應內容傳遞給客戶端,在這個過程中,客戶端是知道請求的服務器地址的,但是服務器是不知道哪個客戶端請求的。VPN做的就是這個事。
  • 反向代理大概的意思就是,客戶端發送一個請求給代理服務器,由代理服務器來決定這個請求該交給哪個服務器,這就是實現了服務器負載均衡,可以將請求轉發到比較空閑的服務器來響應,這個時候,代理服務器就是相對于客戶端的服務器,因為此時客戶端也不知道請求交給了哪個服務器。

我所理解的正向代理和反向代理就是這個意思,如有錯誤歡迎下方評論。

proxy_pass

那么nginx使用的就是proxy_pass屬性來進行反向代理的處理,使用也是很簡單。下面以nodejs開啟一個建立在 localhost:4000 的服務

const http = require('http');

http.createServer(function(req, res){
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('server 4000');
}).listen(4000);

請求 localhost:4000 可以打開我們的頁面

那么我們需要做的是將 localhost:5000的所有請求都代理到4000這個server里,這樣就會出現我們訪問5000和訪問4000一樣的效果。具體配置如下

server {
        # 監聽 localhost:5000
        listen     5000;
        server_name localhost;
        # / 表示匹配所有的請求,所有的請求都會經過這個過濾器
        location / {
            # 設定請求轉發的地址
            proxy_pass http://localhost:4000;
        }
}

這邊需要注意的是 proxy_pass 的寫法,必須是http://或者https://開頭的,http頭是不能省的。

請求5000端口效果如下:

本地代理至百度

上方的例子過于簡單,那么這一個和上面的有點類似,這次是將4000的端口號代理到www.baidu.com。修改一下proxy_pass

server {
        # 監聽 localhost:5000
        listen     5000;
        server_name localhost;
        # / 表示匹配所有的請求,所有的請求都會經過這個過濾器
        location / {
            # 設定請求轉發的地址
            proxy_pass https://www.baidu.com;
        }
}

這樣就可以了,至于這邊寫的是http 還是 https,這個倒是不影響,因為百度內部會自動將http轉成https畢竟安全嘛。

百度代理至本地

那么按照剛剛的思路就是監聽 www.baidu.com 然后設置一下 proxy_passlocalhost:4000

配置如下

server {
        listen     80;
        server_name www.baidu.com;

        location / {
            proxy_pass http://localhost:4000;
        }
}

試一下,是不是沒有用,沒有用就對了。要是這么輕松的搞定nginx還玩個蛋。那么這個里面又有點操作了,先看正確的配置,修改本地的hosts文件,mac下的文件位置為 /etc/hosts但是需要 sudo 來進行修改,畢竟這個文件比較重要嘛

sudo vim /etc/hosts

添加一句

server {
        listen     80;
        server_name baidu.com;

        location / {
            proxy_pass http://127.0.0.1:4000;
        }
}

一般在本地開發的時候我們都會修改本地的hosts文件,但是會遇到一個問題就是有的時候是有用的有的時候又沒用了,我這邊的解決辦法是,每次修改完hosts文件就清楚瀏覽器的瀏覽數據,尤其是緩存這一塊的東西。

如果遇到nginx配置完全正確hosts文件也配置了,但是還是沒有用,不妨清一下緩存,至少在我這是每次都是清完緩存才有用的。

nginx跨域

跨域的解決辦法就是在header里面加上允許跨域的源等信息

location / {  
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, 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;
    }
} 

但是在實際項目里。origin還是不要設置為*比較好,因為前端使用axios的話在獲取session這一塊會出現問題。

nginx緩存

這邊有一個需要注意的地方,nginx作為靜態資源服務器的時候是不做緩存的,只有當nginx進行反向代理的時候才具備緩存這個功能。我一開始寫了半天發現鳥用都沒有,最后才發現只有做代理的時候才具備緩存。

各大瀏覽器本身已經具有緩存了,比如說谷歌,我們可以寫一個html,然后在html引入一張圖片,我們可以看看瀏覽器是怎么對圖片這些靜態資源進行緩存的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <img src="./static/test.jpg" alt="">
</body>
</html>

這個html代碼我使用http-server部署在了http-server -a 127.0.0.1 -p 4000可以看出是在127.0.0.1 4000端口。

這是首次加載的時候的狀況

刷新一下

那么區別還是很大的

  • html文件的狀態碼從200到304,304狀態碼表明該文件是從緩存中讀取
  • jpg文件可以發現在size這一列,多了一個memory cache,這表明這個圖片是從瀏覽器緩存中讀取的
  • 各個文件的加載事件明顯減少,尤其是圖片

可能有的人會發現第一次加載的時候,顯示的是from desk cache,那么chrome瀏覽器的緩存分為兩種,一種是磁盤緩存一種是內存緩存,

官方文檔:

Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to call handlerBehaviorChanged() after registering or unregistering an event listener.

大概意思就是:chrome有兩種緩存,一種是desk cache一種是memory cache,然后memory cache的效率高于前者。

那么打開chrome devtools點開圖片可以發現

cache-control后面有一個max-age,那么具體的有關緩存的技術這邊就不說了我回頭整理一下

那么針對不同類型的文件進行緩存還是很簡單的,需要注意的在于location的正則匹配

那么第一種最簡單的緩存,就是直接設置expires 緩存時間

設置expires

location ~ .*\.(jpg|png)$ {
    proxy_pass http://127.0.0.1:4000;
    expires 3m;
}

expires是以秒為單位的,那么我們設置為3m 也就是180秒,發現確實是可以的。

proxy_cache_path 的使用

那么我們也可以指定我們的nginx緩存目錄,通過proxy_cache_path 屬性

proxy_cache_path /tmp/cache/test levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
location ~ .*\.(jpg|png)$ {
     proxy_pass http://127.0.0.1:4000;
     proxy_cache my_cache;
     proxy_cache_valid 200 304 1y;
     proxy_cache_valid any 1m;
     expires 1y;
}
  • proxy_cache_path 執行緩存文件的目錄,如果沒有的話需要提前創建,不然nginx會報錯
  • levels 采用2級目錄來存儲
  • key_zone 在共享內存中設置一塊存儲區域來存放緩存的key和metadata(類似使用次數),這樣nginx可以快速判斷一個request是否命中或者未命中緩存,1m可以存儲8000個key,10m可以存儲80000個key
  • max_size 最大cache空間,如果不指定,會使用掉所有disk space,當達到配額后,會刪除最少使用的cache文件
  • inactive 未被訪問文件在緩存中保留時間,在指定時間內未被訪問的文件會被刪除
  • use_temp_path 如果為off,則nginx會將緩存文件直接寫入指定的cache文件中,而不是使用temp_path存儲,official建議為off,避免文件在不同文件系統中不必要的拷貝;
  • proxy_cache 啟用proxy cache,對應著配置的key_zone;
  • proxy_cache_valid 根據不同的狀態碼設置不同的緩存時間

可以查看一下nginx進程,會發現這個時候是有緩存的進程在開著的。

這邊可以看到,我們的圖片的緩存時間已經被設置為1年

location匹配優先級

在緩存中需要注意的一點就是location的匹配規則和優先級

  • = 開頭表示精確匹配
  • ^~ 開頭表示uri以某個常規字符串開頭,不是正則匹配;
  • ~ 開頭表示區分大小寫的正則匹配;
  • ~* 開頭表示不區分大小寫的正則匹配;
  • / 通用匹配, 如果沒有其它匹配,任何請求都會匹配到;

upstream負載均衡

負載均衡是nginx的另一大特點,可以配置多個服務器,將請求分發到最合適的那臺服務器,避免某一臺服務器請求太多而崩潰。使用upstream屬性來配置

upstream favtomcat {
       server 192.168.1.100:4000;
       server 192.168.1.111:5000;
}
location / {
            root   html;
            index  index.html index.htm;
            # 對應上方的 favtomcat
            proxy_pass http://favtomcat;
}

這是最基礎的負載均衡配置,采用的是輪詢的策略進行負載,每個請求按時間順序逐一分配到不同的后端服務器,適用于圖片服務器集群和純靜態頁面服務器集群。
優點: 方式簡便、成本低廉
缺點: 可靠性低和負載分配不均衡

那么upstream還有其他的負載策略

weight權重

可以給每一臺服務器設置一個權重,這樣權重高的干的活也就會多一點

upstream favtomcat {
       server 192.168.1.100:4000 weight=5;
       server 192.168.1.111:5000 weight=10;
}

ip_hash

這種方式是基于客戶端的ip地址,采用hash算法計算下一個請求要選擇哪一個服務器,這樣固定的ip會訪問同一個服務器,可以解決session問題

upstream favtomcat {
       ip_hash;
       server 192.168.1.100:4000;
       server 192.168.1.111:5000;
}

least_conn最少鏈接

會將下一個請求分發到當前鏈接數最少的一臺服務器

upstream favtomcat {
       least_conn;
       server 192.168.1.100:4000;
       server 192.168.1.111:5000;
}

fair

按后端服務器的響應時間來分配請求,響應時間短的優先分配。

upstream favtomcat {
       fair;
       server 192.168.1.100:4000;
       server 192.168.1.111:5000;
}

url_hash

按訪問url的hash結果來分配請求,使每個url定向到同一個后端服務器

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

參考
https://juejin.im/post/59f94f626fb9a045023af34c
Nginx Proxy Cache原理和最佳實踐

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容