循序漸進nginx(二):反向代理、負載均衡、緩存服務、靜態資源訪問


前置知識章節:
1.介紹、安裝、hello world、location匹配
2.▶▶反向代理、負載均衡、緩存服務、靜態資源訪問✅
3.日誌管理、http限流、https配置,http_rewrite模塊,第三方模塊安裝,結語。✅


反向代理

💡代理有正向代理和反向代理
💡正向代理:
所謂的正向,就是以請求發起為角度的,此時代理的是用戶發起的請求。
用戶A無法訪問G網,但A能訪問B網,而B網能訪問G網。那麼如果A先通過訪問B網,B網幫他訪問G網,返回數據給他,那麼此時B網就作為了一個代理服務器,而且是正向代理。此時A其實是知道它要訪問G網的,G網並不知道是A訪問它的,所以此時是代理了A,正向代理。

💡反向代理:
相對於正向代理的代理用戶發起的請求,反向代理代理的是資源服務器的請求。
用戶A訪問B網的某個資源,但B網沒有,然後它發給C網,C網返回給B網,B網返回給C網。此時用戶A不知道它訪問的是C網,所以此時代理的是C網,反向代理。
場景一般是,A能訪問B,但不能訪問C,B能訪問C。我們使用B這台機來對外提供服務,讓關於C的請求都先經過B,B再請求C來返回結果。
反向代理有時候用在內網中,用來做請求轉發到內網,這樣一定程度的保護了內網的資源和利用上了內網的服務器。當然對於我們業務來說,服務器有可能並不是部署在內網中的,也可以用在外網服務器代理上。

💡反向代理是一個非常重要的功能,可以說nginx你最常用的功能或許就是反向代理了,動靜分離、負載均衡和緩存有時候你可能用不上。

使用

1.創建代理目標服務端:

我們首先需要創建一個web服務端,由於我是主攻java的,所以我這裡部署一個java的web服務器,並讓他監聽8081端口。(192.168.48.131是我的虛擬機的IP,nginx和這個java web服務端都部署在這裡。)
20200614012418
訪問一下這個要被代理的服務端的接口//192.168.48.131:8081/user/list,發現調用成功。
20200614001834

2.配置nginx反向代理目標服務端:

💡2.1 修改server:
因為此時是作為一個代理服務器,所以我們要新建一個server塊來充當代理服務器。

❓有人有點疑惑,上面的例子只配了location,我們為什麼要配server呢?
其實這裡是從業務需求來做區分的。因為我們現在的目標是弄一個代理服務器,當然了你也可以不創建,然後把下面的location放到之前的server下即可。什麼時候是必須創建的呢?當server_name不一樣的時候,但這個也是需要你根據業務來判斷的,比如你原本使用80端口接收發過來的請求,而現在使用8080端口接收發過來的請求的時候就需要一個新的server_name。
這裡新建一個server其實也有介紹server_name的用法的意思。

修改server_name:server_name是當前服務端監聽的地址的意思,
server_name支持幾種語法:

  • 基於確切的域名,域名可以一個或多個,多個使用空格隔開server_name example.com www.example.com
  • 支持通配符的方式,通配符*只能使用在開頭或者結尾,不能使用在中間。當有多個匹配結果的時候,會選擇最長的匹配結果server_name *.example.com www.example.*
  • 基於正則表達式,當使用正則表達式的時候,開頭必須加上一個~server_name ~^www\d+\.example\.com$;
  • 正則表達式支持<>來獲取一個變量到後面使用以實現二級域名的功能,這裡不講,有興趣自查。
  • 上面幾個語法的優先級是:
    • 精確域名 > 通配符在前 > 通配符在後 > 正則表達式

💡2.2 修改location
🔵proxy_pass用於設置被代理服務器的地址,可以是主機名稱(//www.baidu.com這樣的)、IP地址(域名加端口號)的形式。
🔵下面的這個location的意思是,如果請求路徑開頭是/api的,那麼都代理到proxy_pass指定的地址,比如訪問了/api/user/list,那麼得到的結果是//localhost:8081/user/list的結果。

server {
  listen 8080;

  location /api/ {
    proxy_pass //localhost:8081/;
  }
}

🔵在lcoation都是location /api/時,proxy_pass不同,請求的資源也是不一樣的:

  • proxy_pass //localhost:8081;:請求nginx主機IP:8080/api/user/list,nginx會將該請求代理轉發到//locahost:8081/api/user/list
  • proxy_pass //localhost:8081/;:請求nginx主機IP:8080/api/user/list,nginx會將該請求代理轉發到//locahost:8081/user/list
  • proxy_pass //localhost:8081/test;請求nginx主機IP:8080/api/user/list,nginx會將該請求代理轉發到//locahost:8081/testuser/list
  • proxy_pass //localhost:8081/test/;請求nginx主機IP:8080/api/user/list,nginx會將該請求代理轉發到//locahost:8081/test/user/list

3.測試使用:

在前面,我創建了一個8081的web服務端,而且//nginx服務器IP:8081/user/list是有一個接口的.
20200614001834
我們試一下使用8080來代理一下這個8081這個服務端:
注意下面的這個server如果寫在default.conf的時候,是與其它server塊同級的
20200614002940

如果我們能夠通過8080來訪問到//192.168.48.131:8081/user/list這個接口,那麼就說明了我們的反向代理成功了。此時發向8080端口的,以/api開頭的請求都會代理到8081中。

20200614003048

## 其他反向代理指令
下面的其他反向代理指令我解釋了一下用途,有需要就自己去了解一下吧。
* proxy_set_header:在將客戶端請求發送給後端服務器之前,更改來自客戶端的請求頭信息。
* proxy_connect_timout:配置nginx與後端服務器嘗試建立連接的超時時間。
* proxy_read_timeout:定義用於從代理服務器讀取響應的超時。
* proxy_send_timeout:設置用於將請求傳輸到代理服務器的超時。
* proxy_redirect:用於修改後端服務器返回的響應頭中的Location和Refresh。[proxy_redirect](//nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect)


負載均衡

負載均衡其實也算是基於反向代理的。

上面的反向代理提到一點反向代理可以為我們的服務端進行代理,你有時候可能是多個服務端提供同一功能的,為了讓他們能夠平攤壓力,那麼你可能需要負載均衡功能。

使用

1.準備服務端

🔴準備負載均衡用的業務服務端,我這裡給的是一個java Spring Boot端的簡單代碼,我部署成多個服務端的時候,由於端口不同,訪問info接口返回的數據也不同,這樣就可以測試是否達到了負載均衡。
20200615152747

🔴部署服務端,使用--server.port來部署到不同的端口,例如java -jar a-simple-web-0.0.2-SNAPSHOT.jar --server.port=8082就把我的jar程序部署到了8082端口。

2.修改nginx配置

我們這裡新建一個conf.d/loadbalance.conf,由於nginx.conf內部有一個include /etc/nginx/conf.d/*.conf;可以把conf.d下的配置文件都導入到nginx.conf的http塊中,所以我們新建的這個conf也是可以導入到nginx.conf中的。
20200615155759

3.測試

訪問//192.168.31.128:9001/user/info,看是否是輪詢的分發請求,如果響應的時候返回了不同的端口,那麼就證明了是輪詢的分發請求。

負載均衡策略

💡默認輪詢負載均衡:在不指定負載均衡策略的時候,默認的策略是按順序給負載均衡服務端發送請求,並且一次順序中每個服務端處理兩次請求,比如兩個服務端,那麼就是AABBAABB這樣循環下去。 如果服務端有宕機的,會從負載均衡順序中去除。等到宕機的服務端可用後,會在30S(好像是)之後加入到可用負載均衡服務端列表中。

💡加權輪詢負載均衡:在輪詢的基礎上加上權重的考慮,假如服務端A的權重是1,服務端B的權重是2,那麼6個請求中,服務端A會收到2個請求,服務端B會收到4個請求。
20200615155954

💡ip_hash負載均衡:每個請求按ip的哈希結果分配到指定的負載服務端響應,(原理類似求餘數,把服務端排序之後,根據哈希處理再處理之後得到的餘數來選取服務端),這樣同一個ip響應的服務端是固定的。可以在一定程度解決服務端Session共享的問題。但這樣可能負載壓力就分配的不平均了。
語法例子:

upstream ip-hash {
  ip_hash;
  server localhost:8081;
  server localhost:8082;
}

💡也可以使用第三方模塊來負載均衡。但由於我們上面沒有講過第三方模塊,引入前置知識需要佔用大量篇幅,所以這裡只引出一下,有需要的自查吧。

  • 第三方模塊fair:可以基於響應時間分配請求,優先分配到響應時間短的服務端上。
  • 第三方模塊url_hash:可以基於url的hash結果來分配請求。

負載均衡的額外參數

上面介紹了使用weight參數來實現加權輪詢負載均衡,其實還有一些其他的參數。

  • max_fails:允許請求失敗的次數。默認值是1。
  • fail_timeout:重新檢測服務的時間,當服務端請求失敗後,會在fail_timeout時間內標記成不可用,fail_timeout時間之後再次檢測是否可用,不行就再等fail_timeout時間之後再次檢測是否可用。默認是10S.
  • backup:預留的備份服務端。只有當其他服務端都宕機或者處於忙碌狀態時,才會分發請求給backuo標註的服務端。
  • down:暫時不參與負載均衡的服務端,只有其他服務端都宕機的時候,這個服務端才參與負載均衡。
  • max_conns:限制接收的最大的連接數。
upstream web-server {
  server localhost:8081 max_fails=1 fail_timeout=10;
  server localhost:8082;
  server localhost:8083 backup;
  server localhost:8084 down;
}

💡使用ip_hash時,不能使用weight和backup。


緩存服務

💡在瀏覽器訪問某個網站的資源的時候,會看瀏覽器是否緩存了這個數據。如果本地有這個緩存數據,那麼就會直接從本地中獲取了,不會再請求網絡了。這是客戶端緩存。nginx傳遞的響應的某些數據會影響瀏覽器是否緩存數據以及緩存多久。
💡除了客戶端緩存,還有一種代理緩存nginx的緩存是代理緩存,因為它其實是將代理的服務端的結果緩存了。代理緩存使用ngx_http_proxy_module的指令來配置。
💡(除了這兩種,還有(後端)服務端緩存,也就是使用redis等技術進行的後端服務端緩存。)。

代理緩存

語法介紹

💡proxy_cache_path path:用來定義緩存文件路徑。只能用在http塊。
語法:

proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
  • path是緩存文件存放路徑
  • levels用於定義文件存放層級,可以有一層(1),兩層(1:2),三層(1:2:2)。目錄會根據請求URL地址的哈希結果來創建(從末尾開始截取,),假如哈希結果是9cad383e7b0ee3d1d4b7099aace20b3f,那麼levels=1:2代表第一層目錄為長度為一的字符f,第二層是長度為2的字符b3
  • keys_zone用來定義當前這個緩存存放空間的名字,10m是一個大小
  • max_size用來定義目錄最大的大小。
  • inactive用來定義不活躍的緩存多久清除。
  • use_temp_path:緩存臨時目錄。一般可以不定義。

💡proxy_cache zone|off:可以用在http, server, location。zone是proxy_cache_path的keys_zone.
💡proxy_cache_valid [code...] time:緩存過期周期,過期之後就不返回代理緩存了。 code是http狀態碼。
💡proxy_cache_key string:配置緩存的標識,比如說請求不一樣的話那肯定不返回同樣的代理緩存了,這就是是否返回同一個緩存的區分標識。默認值是proxy_cache_key $scheme$proxy_host$request_uri;,可以用在http, server, location。其他例子:proxy_cache_key $scheme$proxy_host$uri$args;

使用例子

下面來做一個實驗:嘗試使用nginx的代理緩存作為代理的響應。

1.我們新建一個後端接口。這個後端接口能在每一次訪問的時候都在控制台打印出一個消息(下面是一個Spring Boot例子):
20200622222735
2.配置反向代理:
20200622223500

3.一開始先測試一下//192.168.31.128/api/user/info,看每一次訪問是否都向後端發起了請求,確實是每一次都發請求的:
20200622223617

4.然後配置代理緩存:
20200626003102

5.再次測試訪問//192.168.31.128/api/user/info是否每一次訪問都向後端發起了請求,結果應該是後端服務端只會打出一次,或者你可以嘗試關閉後端服務端,然後訪問,如果還是能夠訪問,那麼應該是已經代理緩存成功了。

代理緩存補充:

💡永久緩存:上面使用proxy_cache配置的其實是臨時緩存。也就是說一定時間後會自動過期。如果你的數據在很長很長時間都不會過期,那麼可以考慮使用proxy_store.
💡如果你想讓部分請求不要緩存,可以使用proxy_no_cache [string...],string可以是變量,如果存在某個string值不為空也不為0,那麼此請求不會被緩存。
💡緩存清理:incative配置會幫我們清除過期的緩存文件,但還沒過期的不會清除,需要我們手動清除(場景是比如說你更新了大量數據,此時緩存中的數據很多都錯誤了,此時需要清除所有緩存。),如果你需要清除的話,那麼一種方法是手動定義一個Linux腳本來清除緩存;一種方法是使用模塊nginx-cache-purge。這些由於篇幅問題,不講述。

瀏覽器緩存

瀏覽器是怎麼判斷緩存是否需要使用本地緩存以及緩存是否過期的呢?它通過響應頭中的expirecache-controlLast-ModifiedEtag等頭信息來判斷的。

  • 用於本地校驗是否過期的頭:expireCache-control(max-age),如果有本地緩存,那麼會使用expire來判斷是否過期,不過期,直接使用本地緩存,本地過期之後,再進行遠程校驗。
  • 用於遠程校驗的Last-Modified頭信息:用於遠程文件修改校驗的,是一個GMT時間,例如Thu, 02 Jul 2020 01:05:03 GMT,如果校驗時間不一致,那麼不使用本地緩存,一致則使用本地緩存。
  • 用於遠程校驗的的Etag頭信息:用於遠程文件修改校驗的,是一個類時間戳的數據,例如:"5efd32bf-3fa8e",如果校驗時間不一致,那麼不使用本地緩存,一致則使用本地緩存。Etag與Last-Modified的區別是,Etag更精確,所以會優先判斷Etag,然後再判斷Last-Modified。
  • 相關nginx模塊:ngx_http_headers_module

❗瀏覽器緩存機制你可以自己了解一下:博客園-HTTP緩存機制

💡Expire

  • 語法:expires [modified] time;
    • modified用於執行修改後過期,比如expires modified +24h;就代表修改後24小時內不過期。
    • time:過期時間,例如有expires 24h;24天不過期,expires 30d;30天不過期,expires -1;代表永不過期

測試

測試之前我們先要提幾點:

  • 瀏覽器有默認的緩存策略,Etag和Last-Modified默認是自帶的,會有基於對文件修改的緩存,第一次響應200之後,第二次響應為304的時候,瀏覽器還是會發請求,但此時發請求用於測試文件是否過期,不過期則不會傳輸文件
  • 當配置了expires的時候怎麼判斷它生效了呢?響應碼為200,並且不對nginx發起請求。
  • expires並不會在任何情況都生效,比如說F5刷新Ctrl+F5刷新就無效,此時測試應該使用在鏈接欄按回車發請求來測試。expires可用於什麼情況可以參考博客園-HTTP緩存機制

1.我們先在/usr/share/nginx/html下面存儲一張圖片,後面會通過訪問這種圖片,來測試客戶端緩存。

2.我們一開始先不要配置expires:

server {
    listen       80;
    server_name  127.0.0.1;
    location / {
      root /usr/share/nginx/html;
    }
}

3.多次訪問一下//192.168.31.128/a.jpg看看nginx的訪問日誌/var/log/nginx/access.log是否有添加
此時要多留意每次請求的響應碼,應當有以下幾個情況:

  • 如果你使用F5刷新,那麼第一次響應碼為200,後面都是304,第一個響應碼為200的時候用於獲取文件,後面的304會檢查文件是否修改,不修改則使用緩存的文件,此時每次刷新都應該發了一次請求,access.log中可以觀察到。
  • 如果你使用在鏈接欄按回車發請求來測試,那麼響應碼應當都是200,是每一次都會去請求文件,此時每次按回車都應該發了一次請求,access.log中可以觀察到。

4.加了expires之後看看,我們簡單的使用兩分鐘看看。

server {
    listen       80;
    server_name  127.0.0.1;
    location / {
      expires 2m;
      root /usr/share/nginx/html;
    }
}

5.多次訪問一下//192.168.31.128/a.jpg看看nginx的訪問日誌是否有添加

  • 如果你使用F5刷新,由於expires不會在F5刷新時生效,所以效果應該和未配置之前是一樣的。
  • 如果你使用在鏈接欄按回車發請求來測試,那麼一開始響應碼應當都是200但不會真正的發起請求,而是使用緩存中的數據,access.log中不會看到請求。(因為我上面定義緩存是兩分鐘)兩分鐘之後第一次請求是304,用於校驗文件是否修改,如果沒修改,那麼後面的兩分鐘之內的響應又是不發請求的200。


靜態資源訪問

💡nginx可以對外接收靜態資源訪問的請求。

💡在上面的location的內容的時候,其實有講到返迴文件資源的知識。
比如location ~* \.(gif|jpg|jpeg)$匹配任何以.gif、.jpg 或 .jpeg 結尾的請求,然後你發的請求匹配成功的時候,會使用root+location得到的路徑的資源作為響應。其實這些也就是靜態資源了,所以其實也可以通過nginx來達到靜態資源的訪問。

💡在前後端分離之後,前端作為靜態資源訪問,應該部署到哪裡呢?因為我們此時是不應該把前端靜態資源部署到後端服務器上的。那麼這時候放到nginx服務端上也是可以的。利用nginx的靜態資源訪問作為前端的服務端。

💡或者你也可以把nginx作為文件資源訪問的服務端。


Tags: