Nginx 負載均衡

前言

對於電商平臺而言,隨著業務的不斷髮展壯大,網站訪問量和數據量也隨之急劇增長,該情況的產生給服務器帶來了一定的負擔。從用戶體驗層面而言,由於服務器端數據處理帶來的時延,往往導致頁面的響應速度過慢、操作流暢性受阻等問題。這在某種程度上甚至會潛在影響平臺的成交量。提供高效率,高質量的服務成為亟待解決的問題。負載均衡策略的出現和發展成為緩解上述問題的有效途徑。本文將帶你瞭解基於 Nginx 實現的負載均衡。

什麼是負載均衡

負載均衡(Load Balance),它在網絡現有結構之上可以提供一種廉價、有效、透明的方法來擴展網絡設備和服務器的帶寬,並可以在一定程度上增加吞吐量、加強網絡數據處理能力、提高網絡的靈活性和可用性等。用官網的話說,它充當著網絡流中“交通指揮官”的角色,“站在”服務器前處理所有服務器端和客戶端之間的請求,從而最大程度地提高響應速率和容量利用率,同時確保任何服務器都沒有超負荷工作。如果單個服務器出現故障,負載均衡的方法會將流量重定向到其餘的集群服務器,以保證服務的穩定性。當新的服務器添加到服務器組後,也可通過負載均衡的方法使其開始自動處理客戶端發來的請求。

簡言之,負載均衡實際上就是將大量請求進行分佈式處理的策略

什麼是 Nginx 負載均衡

通過上文簡單的概念介紹,你可能對負載均衡有了一個初步的瞭解,知道它是一種調度策略。那麼問題來了,Nginx 又是什麼呢?Nginx 如何實現負載均衡?這就要從正向代理和反向代理說起了。

  • 正向代理 正向代理(Forward Proxy)最大的特點是,客戶端非常明確要訪問的服務器地址,它代理客戶端,替客戶端發出請求。比如:科學上網,俗稱翻牆(警告⚠️:翻牆操作違反相關法律規定,本文只是為了解釋正向代理向讀者舉個例子,僅供學習參考,切勿盲目翻牆)。 假設客戶端想要訪問 Google,它明確知道待訪問的服務器地址是 www.google.com/,但由於條件限制,它找… Google 的”朋友”:代理服務器。客戶端把請求發給代理服務器,由代理服務器代替它請求 Google,最終再將響應返回給客戶端。這便是一次正向代理的過程,該過程中服務器並不知道真正發出請求的是誰。
  • 反向代理 那麼,隨著請求量的爆發式增長,服務器覺得自己一個人始終是應付不過來,需要兄弟服務器們幫忙,於是它喊來了自己的兄弟以及代理服務器朋友。 此時,來自不同客戶端的所有請求實際上都發到了代理服務器處,再由代理服務器按照一定的規則將請求分發給各個服務器。 這就是反向代理(Reverse Proxy),反向代理隱藏了服務器的信息,它代理的是服務器端,代其接收請求。換句話說,反向代理的過程中,客戶端並不知道具體是哪臺服務器處理了自己的請求。如此一來,既提高了訪問速度,又為安全性提供了保證。 在這之中,反向代理需要考慮的問題是,如何進行均衡分工,控制流量,避免出現局部節點負載過大的問題。通俗的講,就是如何為每臺服務器合理的分配請求,使其整體具有更高的工作效率和資源利用率。
  • Nginx 是什麼? Nginx 作為一個基於 C 實現的高性能 Web 服務器,可以通過系列算法解決上述的負載均衡問題。並且由於它具有高併發、高可靠性、高擴展性、開源等特點,成為開發人員常用的反向代理工具。

負載均衡常用算法

1. 輪詢 (round-robin)

輪詢為負載均衡中較為基礎也較為簡單的算法,它不需要配置額外參數。假設配置文件中共有 M 臺服務器,該算法遍歷服務器節點列表,並按節點次序每輪選擇一臺服務器處理請求。當所有節點均被調用過一次後,該算法將從第一個節點開始重新一輪遍歷。

特點:由於該算法中每個請求按時間順序逐一分配到不同的服務器處理,因此適用於服務器性能相近的集群情況,其中每個服務器承載相同的負載。但對於服務器性能不同的集群而言,該算法容易引發資源分配不合理等問題。

2、加權輪詢

為了避免普通輪詢帶來的弊端,加權輪詢應運而生。在加權輪詢中,每個服務器會有各自的 weight。一般情況下,weight 的值越大意味著該服務器的性能越好,可以承載更多的請求。該算法中,客戶端的請求按權值比例分配,當一個請求到達時,優先為其分配權值最大的服務器。

特點:加權輪詢可以應用於服務器性能不等的集群中,使資源分配更加合理化。

Nginx 加權輪詢源碼可見:ngx_http_upstream_round_robin.c,源碼分析可參考:關於輪詢策略原理的自我理解。其核心思想是,遍歷各服務器節點,並計算節點權值,計算規則為 current_weight 與其對應的 effective_weight 之和,每輪遍歷中選出權值最大的節點作為最優服務器節點。其中 effective_weight 會在算法的執行過程中隨資源情況和響應情況而改變。較為核心的部分如下:

<code>for (peer = rrp->peers->peer, i = 0;
\tpeer; \t/* peer 為當前遍歷的服務器結點*/
peer = peer->next, i++)
{
...

\t/* 每輪遍歷會更新 peer 當前的權值*/
\tpeer->current_weight += peer->effective_weight;


...

\t/* best 為當前服務器中的最優節點,即本輪中選中的服務器節點*/
\tif (best == NULL || peer->current_weight > best->current_weight) {
\t\tbest = peer;
\tp = i;
\t}

...
}/<code>

3. IP 哈希(IP hash)

ip_hash 依據發出請求的客戶端 IP 的 hash 值來分配服務器,該算法可以保證同 IP 發出的請求映射到同一服務器,或者具有相同 hash 值的不同 IP 映射到同一服務器。

特點:該算法在一定程度上解決了集群部署環境下 Session 不共享的問題。

Session 不共享問題是說,假設用戶已經登錄過,此時發出的請求被分配到了 A 服務器,但 A 服務器突然宕機,用戶的請求則會被轉發到 B 服務器。但由於 Session 不共享,B 無法直接讀取用戶的登錄信息來繼續執行其他操作。

實際應用中,我們可以利用 ip_hash,將一部分 IP 下的請求轉發到運行新版本服務的服務器,另一部分轉發到舊版本服務器上,實現灰度發佈。再者,如遇到文件過大導致請求超時的情況,也可以利用 ip_hash 進行文件的分片上傳,它可以保證同客戶端發出的文件切片轉發到同一服務器,利於其接收切片以及後續的文件合併操作。

4、其他算法

  • URL hash url_hash 是根據請求的 URL 的 hash 值來分配服務器。該算法的特點是,相同 URL 的請求會分配給固定的服務器,當存在緩存的時候,效率一般較高。然而 Nginx 默認不支持這種負載均衡算法,需要依賴第三方庫。
  • 最小連接數(Least Connections) 假設共有 臺服務器,當有新的請求出現時,遍歷服務器節點列表並選取其中連接數最小的一臺服務器來響應當前請求。連接數可以理解為當前處理的請求數。

應用場景

說了這麼多理論,究竟基於 Nginx 的負載均衡要怎麼用呢?接下來,將以加權輪詢算法為例,帶大家嘗試通過自己的一臺筆記本 + Nginx + Node 測試一下負載均衡。由於沒有多臺服務器,於是通過自己筆記本的多個不同端口來模擬不同的服務器。

Step 1:確保自己的電腦中,Nginx 已安裝並能夠成功啟動(以 Mac 為例)

如果你也遇到了像我一樣由於端口占用導致 Nginx 啟動失敗的問題,可以嘗試下述步驟修改配置文件中的端口號

  • 相關文件路徑 /usr/local/etc/nginx/nginx.conf (配置文件路徑) /usr/local/var/www (服務器默認路徑) /usr/local/Cellar/nginx/1.8.0 (安裝路徑)
  • 修改 nginx.conf 文件中的端口 server { # listen 8080; listen 8086; server_name localhost; } 複製代碼Nginx 配置文件 nginx.conf 中主要包含以下幾個部分: server:主機服務相關設置,主要用於指定虛擬主機域名、IP 和端口 location:URL 匹配特定位置後的設置,反向代理設置 upstream:負載均衡相關配置
  • 暫停 Nginx 並重啟 // 暫停 Nginx 服務 sudo nginx -s stop // 啟動 Nginx 服務 nginx 複製代碼
  • 打開 http://localhost:8086/ 測試是否成功,如果顯示下圖,則證明啟動成功~

Step 2:基於 Node + Express 框架來搭建簡單的服務器

Express 是一個簡潔而靈活的輕量級 node.js Web 應用框架(詳情可瞭解 Express),如果第一次使用,請先安裝。

  • 安裝 Express npm i express 複製代碼
  • 新建 index.js 文件,並寫入代碼 const express = require('express'); const app = express(); // 定義要監聽的端口號 const listenedPort = '8087'; app.get('/', (req, res) => res.send(`Hello World! I am port ${listenedPort}~`)); // 監聽端口 app.listen(listenedPort, () => console.log(`success: ${listenedPort}`)); 複製代碼
  • 啟動服務器 node index.js 複製代碼

此處可以多起幾個服務,分別讓 Node 監聽 8087,8088,8089 端口,每個服務中通過 send 不同的文案用以區分不同的 Server。

Step 3:在 nginx.conf 文件中配置好需要輪詢的服務器和代理

  • 輪詢的服務器,寫在 http 中的 upstream 對象裡:
<code>upstream testServer {
server localhost:8087 weight=10;
server localhost:8088 weight=2;
server localhost:8089;
}/<code>
  • 代理地址,寫在 http 中的 server 對象裡:
<code>location / {
root html;
index index.html index.htm;
proxy_pass http://testServer; // testServer 為自己定義的服務器集群
}\t/<code>

Step 4:查看結果

  • 重啟 Nginx 服務
  • 再次打開 http://localhost:8086/

通過多次刷新可以發現,由於設置了不同的 weight,端口號為 8087 的服務器出現的次數最多,同時證實了權值越高,服務器處理請求幾率越大的規則。

總結

Nginx 作為一款優秀的反向代理服務器,可以通過不同的負載均衡算法來解決請求量過大情況下的服務器資源分配問題。較為常見的負載均衡算法有輪詢、加權輪詢、IP 哈希等等,可分別應對不同的請求場景。如果有興趣可以去 Github 理解下大神的源碼,有問題也歡迎一起來探討~



作者:政採雲前端團隊
鏈接:https://juejin.im/post/5e806d84e51d4546b659b370


分享到:


相關文章: