Nginx 是后端工程師和運維工程師,以及前端工程師必須要掌握的必備技能,尤其在分布式系統應用越來越廣泛的今天,Nginx 已經占據了 Web 服務器的大壁江山,并且還在不斷地增長,比如國內的 BATJ、網易、新浪等公司都可以看到它的身影。
我們本課時的面試題是,Nginx 的負載均衡模式有哪些?它的實現原理是什么?
#### 典型回答
在正式開始之前,我們先來了解一下什么是 Nginx?
Nginx 是一款開源的高性能輕量級 Web 服務器(也叫 HTTP 服務器),它主要提供的功能是:反向代理、負載均衡和HTTP 緩存。它于 2004 年首次公開發布,2011 年成立同名公司以提供支持,2019 年 3 月被 F5 Networks 以 6.7 億美元收購。
之所以需要使用負載均衡是因為,如果我們使用的是一臺服務器,那么在高峰期時很多用戶就需要排隊等待系統響應,因為一臺服務器能處理的并發數是固定的。例如,一個 Tomcat 在默認情況下只能開啟 150 個線程(Tomcat 8.5.x 版本)來處理并發任務,如果并發數超過了最大線程數,那么新來的請求就只能排隊等待處理了,如下圖所示:

然而如果有負載均衡的話,我們就可以將所有的請求分配到不同的服務器上。假如 1 臺服務器可以處理 2000 個請求,那么 5 臺服務器就可以處理 10000 個請求了,這樣就大大提高了系統處理業務的能力,如下圖所示:

知道了負載均衡的好處之后,我們來看下 Nginx 負載均衡的功能。
Nginx 主要的負載均衡策略(內置的負載均衡)有以下四種:
* 輪詢策略(默認負載均衡策略)
* 最少連接數負載均衡策略
*
ip-hash 負載均衡策略
* 權重負載均衡策略
* [ ] 1. 輪詢策略
輪詢負載策略是指每次將請求按順序輪流發送至相應的服務器上,它的配置示例如下所示:
```
http {
upstream myapp1 {
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
}
}
}
```
在以上實例中,當我們使用“ip:80/”訪問時,請求就會輪詢的發送至上面配置的三臺服務器上。
Nginx 可以實現 HTTP、HTTPS、FastCGI、uwsgi、SCGI、memcached 和 gRPC 的負載均衡。
* [ ] 2. 最少連接數負載均衡
此策略是指每次將請求分發到當前連接數最少的服務器上,也就是 Nginx 會將請求試圖轉發給相對空閑的服務器以實現負載平衡,它的配置示例如下:
```
upstream myapp1 {
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
```
* [ ] 3. 加權負載均衡
此配置方式是指每次會按照服務器配置的權重進行請求分發,權重高的服務器會收到更多的請求,這就相當于給 Nginx 在請求分發時加了一個參考的權重選項,并且這個權重值是可以人工配置的。因此我們就可以將硬件配置高,以及并發能力強的服務器的權重設置高一點,以更合理地利用服務器的資源,它配置示例如下:
```
upstream myapp1 {
server srv1.example.com weight=3;
server srv2.example.com;
server srv3.example.com;
}
```
以上配置表示,5 次請求中有 3 次請求會分發給 srv1,1 次請求會分發給 srv2,另外 1 次請求會分發給 srv3。
* [ ] 4. ip-hash 負載均衡
以上三種負載均衡的配置策略都不能保證將每個客戶端的請求固定的分配到一臺服務器上。假如用戶的登錄信息是保存在單臺服務器上的,而不是保存在類似于 Redis 這樣的第三方中間件上時,如果不能將每個客戶端的請求固定的分配到一臺服務器上,就會導致用戶的登錄信息丟失。因此用戶在每次請求服務器時都需要進行登錄驗證,這樣顯然是不合理的,也是不能被用戶所接受的,所以在特殊情況下我們就需要使用 ip-hash 的負載均衡策略。
ip-hash 負載均衡策略可以根據客戶端的 IP,將其固定的分配到相應的服務器上,它的配置示例如下:
```
upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
```
Nginx 的實現原理是,首先客戶端通過訪問域名地址發出 HTTP 請求,訪問的域名會被 DNS 服務器解析為 Nginx 的 IP 地址,然后將請求轉發至 Nginx 服務器,Nginx 接收到請求之后會通過 URL 地址和負載均衡的配置,匹配到配置的代理服務器,然后將請求轉發給代理服務器,代理服務器拿到請求之后將處理結果返回給 Nginx,Nginx 再將結果返回給客戶端,這樣就完成了一次正常的 HTTP 交互。
#### 考點分析
負載均衡和緩存功能是 Nginx 最常用的兩個功能,這兩個功能都屬于高性能的調優手段,也和后端人員的關系比較密切,只有了解并會使用它們才能更好地調試和運行自己的項目,因此 Nginx 的相關知識幾乎是面試中都會出現。
和此知識點相關的面試題還有以下這些:
* 如果代理的服務器宕機了 Nginx 會如何處理?
* Nginx 的緩存功能是如何使用的?
#### 知識擴展
* [ ] 健康檢測
被代理的服務器出現宕機的情況,如果被 Nginx 發現,那么 Nginx 就會將其自動標識為不可用,并且在一段時間內會禁止入站的請求訪問到該服務器上。
而這個發現服務器宕機的過程就是健康檢測的功能了。Nginx 的健康檢測分為兩種類型,主動檢測和被動檢測,默認的非商用 Nginx 采用的是被動檢測。
所謂的被動檢測是指只有訪問了該服務器之后發現服務器不可用了,才會將其標識為不可用,并且在一定時間內禁止請求分發到該服務器上,而不是主動以一定的頻率去檢查服務器是否可用。
健康檢測有兩個重要參數 max_fails 和 fail_timeout。
fail_timeout 定義了健康檢查的執行時長,而 max_fails 表示服務不可用的最大嘗試次數,當一定時間內(此時間由 fail_timeout 定義),發生了一定次數的服務器不響應的事件(此次數由 max_fails 定義),那么 Nginx 就會將該服務器標識為不可用的服務器,并且在一定時間內禁止請求分發到該服務器。默認情況下 max_fails 設置為 1,當它設置為 0 時表示禁用此服務器的運行狀況檢查,它的配置示例如下:
```
upstream cluster{
server srv1.example.com max_fails=2 fail_timeout=10s;
server srv2.example.com max_fails=2 fail_timeout=10s;
}
```
以上配置表示,如果 10s 內發生了兩次服務不可用的情況就會將該服務器標識為不可用的狀態。
當服務器被標識為不可用時,只有達到了 fail_timeout 定義的時間后,才會進行再一次的健康請求檢測。
而主動健康檢測的實現方案有兩種,一種是使用商用的 Nginx Plus 來配置主動健康檢測,另一種是使用開源的第三方模塊 nginx_upstream_check_module 來實現主動健康檢測。
Nginx Plus 和 nginx_upstream_check_module 模塊的主動健康檢查配置大體都是一樣的,它的配置示例如下:
```
upstream backend {
server srv1.example.com;
server srv2.example.com;
check interval=3000 rise=1 fall=3 timeout=2000 type=http;
check_http_send "HEAD /status HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
```
其中,check_http_send 表示發送請求的內容,而 check_http_expect_alive 是服務器正常情況下的響應狀態碼,如果后端服務器的響應狀態包含在此配置中,則說明是健康的狀態。
Nginx 緩存
我們可以開啟 Nginx 的靜態資源緩存,將一些不變的靜態文件,比如圖片、CSS、JS 等文件進行緩存,這樣在客戶端訪問這些資源時就不用去訪問服務器了,因此響應的速度就可以大幅提升,并且節省了寶貴的服務器資源。
Nginx 開啟緩存需要在 http 節點中配置 proxy_cache_path 信息,以及 server 節點中配置要緩存資源的后綴名,它的配置示例如下:
```
http {
// 忽略其他的配置信息......
proxy_cache_path /data/cache levels=1:2 keys_zone=nuget-cache:20m max_size=50g inactive=1d;
include nginx_proxy.conf;
server {
listen 80;
server_name srv1.example.com;
location ~ .*\.(gif|jpg|png|css|js)(.*) { # 要緩存的文件的后綴
access_log off;
add_header Cache-Control "public,max-age=24*3600";
proxy_pass http://localhost:8080;
}
}
}
```
其中,proxy_cache_path 配置的是緩存的目錄信息,以及緩存的保存時間 inactive,還有緩存的大小等信息;而“access_log off”表示關閉日志功能,proxy_pass 表示當第一次沒有緩存時的請求地址,之后便會將訪問到的資源緩存起來。
#### 小結
本課時我們介紹了 Nginx,并講了 Nginx 的四種內置負載均衡的執行策略:輪詢策略(默認負載均衡策略)、最少連接數負載均衡策略、ip-hash 負載均衡策略和權重負載均衡策略,其中 ip-hash 的負載均衡策略會將客戶端的請求固定分發到一臺服務器上。
后面我們還介紹了 Nginx 的健康檢測:主動健康檢測和被動健康檢測;最后我們還講了 Nginx 的緩存功能,它可以幫我們更快的訪問到靜態資源。
學完本課時后,相信你對 Nginx 已經有了一個大體的認識,其中面試中被問到最多的知識點是 Nginx 的四種負載均衡以及健康檢查的相關內容。
- 前言
- 開篇詞
- 開篇詞:大廠技術面試“潛規則”
- 模塊一:Java 基礎
- 第01講:String 的特點是什么?它有哪些重要的方法?
- 第02講:HashMap 底層實現原理是什么?JDK8 做了哪些優化?
- 第03講:線程的狀態有哪些?它是如何工作的?
- 第04講:詳解 ThreadPoolExecutor 的參數含義及源碼執行流程?
- 第05講:synchronized 和 ReentrantLock 的實現原理是什么?它們有什么區別?
- 第06講:談談你對鎖的理解?如何手動模擬一個死鎖?
- 第07講:深克隆和淺克隆有什么區別?它的實現方式有哪些?
- 第08講:動態代理是如何實現的?JDK Proxy 和 CGLib 有什么區別?
- 第09講:如何實現本地緩存和分布式緩存?
- 第10講:如何手寫一個消息隊列和延遲消息隊列?
- 模塊二:熱門框架
- 第11講:底層源碼分析 Spring 的核心功能和執行流程?(上)
- 第12講:底層源碼分析 Spring 的核心功能和執行流程?(下)
- 第13講:MyBatis 使用了哪些設計模式?在源碼中是如何體現的?
- 第14講:SpringBoot 有哪些優點?它和 Spring 有什么區別?
- 第15講:MQ 有什么作用?你都用過哪些 MQ 中間件?
- 模塊三:數據庫相關
- 第16講:MySQL 的運行機制是什么?它有哪些引擎?
- 第17講:MySQL 的優化方案有哪些?
- 第18講:關系型數據和文檔型數據庫有什么區別?
- 第19講:Redis 的過期策略和內存淘汰機制有什么區別?
- 第20講:Redis 怎樣實現的分布式鎖?
- 第21講:Redis 中如何實現的消息隊列?實現的方式有幾種?
- 第22講:Redis 是如何實現高可用的?
- 模塊四:Java 進階
- 第23講:說一下 JVM 的內存布局和運行原理?
- 第24講:垃圾回收算法有哪些?
- 第25講:你用過哪些垃圾回收器?它們有什么區別?
- 第26講:生產環境如何排除和優化 JVM?
- 第27講:單例的實現方式有幾種?它們有什么優缺點?
- 第28講:你知道哪些設計模式?分別對應的應用場景有哪些?
- 第29講:紅黑樹和平衡二叉樹有什么區別?
- 第30講:你知道哪些算法?講一下它的內部實現過程?
- 模塊五:加分項
- 第31講:如何保證接口的冪等性?常見的實現方案有哪些?
- 第32講:TCP 為什么需要三次握手?
- 第33講:Nginx 的負載均衡模式有哪些?它的實現原理是什么?
- 第34講:Docker 有什么優點?使用時需要注意什么問題?
- 彩蛋
- 彩蛋:如何提高面試成功率?