基於 Nginx 的軟件負載均衡實現解讀 [轉自 數據庫開發]

負載均衡在服務端開發中算是一個比較重要的特性。因爲Nginx除了作爲常規的Web服務器外,還會被大規模的用於反向代理前端,因爲Nginx的異步框架可以處理很大的併發請求,把這些併發請求hold住之後就可以分發給後臺服務端(backend servers,也叫做服務池, 後面簡稱backend)來做複雜的計算、處理和響應,這種模式的好處是相當多的:隱藏業務主機更安全,節約了公網IP地址,並且在業務量增加的時候可以方便地擴容後臺服務器。


負載均衡可以分爲硬件負載均衡和軟件負載均衡,前者一般是專用的軟件和硬件相結合的設備,設備商會提供完整成熟的解決方案,通常也會更加昂貴。軟件的複雜均衡以Nginx佔據絕大多數,本文也是基於其手冊做相應的學習研究的。



一、基本簡介

  

負載均衡涉及到以下的基礎知識。
  

(1) 負載均衡算法


  a. Round Robin: 對所有的backend輪訓發送請求,算是最簡單的方式了,也是默認的分配方式;


  b. Least Connections(least_conn): 跟蹤和backend當前的活躍連接數目,最少的連接數目說明這個backend負載最輕,將請求分配給他,這種方式會考慮到配置中給每個upstream分配的weight權重信息;


  c. Least Time(least_time): 請求會分配給響應最快和活躍連接數最少的backend;


  d. IP Hash(ip_hash): 對請求來源IP地址計算hash值,IPv4會考慮前3個octet,IPv6會考慮所有的地址位,然後根據得到的hash值通過某種映射分配到backend;


  e. Generic Hash(hash): 以用戶自定義資源(比如URL)的方式計算hash值完成分配,其可選consistent關鍵字支持一致性hash特性;


(2) 會話一致性
  

用戶(瀏覽器)在和服務端交互的時候,通常會在本地保存一些信息,而整個過程叫做一個會話(Session)並用唯一的Session ID進行標識。會話的概念不僅用於購物車這種常見情況,因爲HTTP協議是無狀態的,所以任何需要邏輯上下文的情形都必須使用會話機制,此外HTTP客戶端也會額外緩存一些數據在本地,這樣就可以減少請求提高性能了。如果負載均衡可能將這個會話的請求分配到不同的後臺服務端上,這肯定是不合適的,必須通過多個backend共享這些數據,效率肯定會很低下,最簡單的情況是保證會話一致性——相同的會話每次請求都會被分配到同一個backend上去。
  

(3) 後臺服務端的動態配置
  

出問題的backend要能被及時探測並剔除出分配羣,而當業務增長的時候可以靈活的添加backend數目。此外當前風靡的Elastic Compute雲計算服務,服務商也應當根據當前負載自動添加和減少backend主機。
  

(4) 基於DNS的負載均衡
  

通常現代的網絡服務者一個域名會關連到多個主機,在進行DNS查詢的時候,默認情況下DNS服務器會以round-robin形式以不同的順序返回IP地址列表,因此天然將客戶請求分配到不同的主機上去。不過這種方式含有固有的缺陷:DNS不會檢查主機和IP地址的可訪問性,所以分配給客戶端的IP不確保是可用的(Google 404);DNS的解析結果會在客戶端、多箇中間DNS服務器不斷的緩存,所以backend的分配不會那麼的理想。


二、Nginx中的負載均衡

  

Nginx中的負載均衡配置在手冊中描述的極爲細緻,此處就不流水帳了。對於常用的HTTP負載均衡,主要先定義一個upstream作爲backend group,然後通過proxy_pass/fastcgi_pass等方式進行轉發操作,其中fastcgi_pass幾乎算是Nginx+PHP站點的標配了。


2.1 會話一致性

  

Nginx中的會話一致性是通過sticky開啓的,會話一致性和之前的負載均衡算法之間並不衝突,只是需要在第一次分配之後,該會話的所有請求都分配到那個相同的backend上面。目前支持三種模式的會話一致性:
  

(1). Cookie Insertion
  

在backend第一次response之後,會在其頭部添加一個session cookie,即由負載均衡器向客戶端植入 cookie,之後客戶端接下來的請求都會帶有這個cookie值,Nginx可以根據這個cookie判斷需要轉發給哪個backend了。


sticky cookie srv_id expires=1h domain=.example.com path=/;

  

上面的srv_id代表了cookie的名字,而後面的參數expires、domain、path都是可選的。
  

(2). Sticky Routes
  

也是在backend第一次response之後,會產生一個route信息,route信息通常會從cookie/URI信息中提取。


sticky route $route_cookie $route_uri;

  

這樣Nginx會按照順序搜索routecookie、route_uri參數並選擇第一個非空的參數用作route,而如果所有的參數都是空的,就使用上面默認的負載均衡算法決定請求分發給哪個backend。
  

(3). Learn
  

較爲的複雜也較爲的智能,Nginx會自動監測request和response中的session信息,而且通常需要回話一致性的請求、應答中都會帶有session信息,這和第一種方式相比是不用增加cookie,而是動態學習已有的session。
  

這種方式需要使用到zone結構,在Nginx中zone都是共享內存,可以在多個worker process中共享數據用的。(不過其他的會話一致性怎麼沒用到共享內存區域呢?)


sticky learn 

   create=$upstream_cookie_examplecookie

   lookup=$cookie_examplecookie

   zone=client_sessions:1m

   timeout=1h;


2.2 Session Draining


主要是有需要關閉某些backend以便維護或者升級,這些關鍵性的服務都講求gracefully處理的:就是新的請求不會發送到這個backend上面,而之前分配到這個backend的會話的後續請求還會繼續發送給他,直到這個會話最終完成。

  

讓某個backend進入draining的狀態,既可以直接修改配置文件,然後按照之前的方式通過向master process發送信號重新加載配置,也可以採用Nginx的on-the-fly配置方式。


$ curl http://localhost/upstream_conf?upstream=backend

$ curl http://localhost/upstream_conf?upstream=backend\&id=1\&drain=1


通過上面的方式,先列出各個bacnkend的ID號,然後drain指定ID的backend。通過在線觀測backend的所有session都完成後,該backend就可以下線了。


2.3 backend健康監測


backend出錯會涉及到兩個參數,max_fails=1 fail_timeout=10s;意味着只要Nginx向backend發送一個請求失敗或者沒有收到一個響應,就認爲該backend在接下來的10s是不可用的狀態。

  

通過週期性地向backend發送特殊的請求,並期盼收到特殊的響應,可以用以確認backend是健康可用的狀態。通過health_check可以做出這個配置。


match server_ok {

    status 200-399;

    header Content-Type = text/html;

    body !~ "maintenance mode";

}

server {

    location / {

        proxy_pass http://backend;

        health_check interval=10 fails=3 passes=2 match=server_ok;

    }

}


上面的health_check是必須的,後面的參數都是可選的。尤其是後面的match參數,可以自定義服務器健康的條件,包括返回狀態碼、頭部信息、返回body等,這些條件是&&與關係。默認情況下Nginx會相隔interval的間隔向backend group發送一個”/“的請求,如果超時或者返回非2xx/3xx的響應碼,則認爲對應的backend是unhealthy的,那麼Nginx會停止向其發送request直到下次改backend再次通過檢查。

  

在使用了health_check功能的時候,一般都需要在backend group開闢一個zone,在共享backend group配置的同時,所有backend的狀態就可以在所有的worker process所共享了,否則每個worker process獨立保存自己的狀態檢查計數和結果,兩種情況會有很大的差異哦。


2.4 通過DNS設置HTTP負載均衡


Nginx的backend group中的主機可以配置成域名的形式,如果在域名的後面添加resolve參數,那麼Nginx會週期性的解析這個域名,當域名解析的結果發生變化的時候會自動生效而不用重啓。


http {

    resolver 10.0.0.1 valid=300s ipv6=off;

    resolver_timeout 10s;

    server {

        location / {

            proxy_pass http://backend;

        }

    }

   

    upstream backend {

        zone backend 32k;

        least_conn;

        ...

        server backend1.example.com resolve;

        server backend2.example.com resolve;

    }

}


如果域名解析的結果含有多個IP地址,這些IP地址都會保存到配置文件中去,並且這些IP都參與到自動負載均衡。


2.5 TCP/UDP流量的負載均衡


通常,HTTP和HTTPS的負載均衡叫做七層負載均衡,而TCP和UDP協議的負載均衡叫做四層負載均衡。因爲七層負載均衡通常都是HTTP和HTTPS協議,所以這種負載均衡相當於是四層負載均衡的特例化,均衡器可以根據HTTP/HTTPS協議的頭部(User-Agent、Language等)、響應碼甚至是響應內容做額外的規則,達到特定條件特定目的的backend轉發的需求。


除了Nginx所專長的HTTP負載均衡,Nginx還支持TCP和UDP流量的負載均衡,適用於LDAP/MySQL/RTMP和DNS/syslog/RADIUS各種應用場景。這類情況的負載均衡使用stream來配置,Nginx編譯的時候需要支持–with-stream選項。查看手冊,其配置原理和參數和HTTP負載均衡差不多。

  

因爲TCP、UDP的負載均衡都是針對通用程序的,所以之前HTTP協議支持的match條件(status、header、body)是沒法使用的。TCP和UDP的程序可以根據特定的程序,採用send、expect的方式來進行動態健康檢測。


match http {

    send      "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n";

    expect ~* "200 OK";

}


2.6 其他特性


slow_start=30s:防止新添加/恢復的主機被突然增加的請求所壓垮,通過這個參數可以讓該主機的weight從0開始慢慢增加到設定值,讓其負載有一個緩慢增加的過程。

  

max_conns=30:可以設置backend的最大連接數目,當超過這個數目的時候會被放到queue隊列中,同時隊列的大小和超時參數也可以設置,當隊列中的請求數大於設定值,或者超過了timeout但是backend還不能處理請求,則客戶端將會收到一個錯誤返回。通常來說這還是一個比較重要的參數,因爲Nginx作爲反向代理的時候,通常就是用於抗住併發量的,如果給backend過多的併發請求,很可能會佔用後端過多的資源(比如線程、進程非事件驅動),最終反而會影響backend的處理能力。

發佈了21 篇原創文章 · 獲贊 142 · 訪問量 64萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章