負載均衡(Load balancing)是一種計算機網絡技術,用來在多個計算機(計算機集群)、網絡連接、CPU、磁盤驅動器或其他資源中分配負載,以達到最佳化資源使用、最大化吞吐率、最小化響應時間、同時避免過載的目的。
使用帶有負載均衡的多個服務器組件,取代單一的組件,可以通過冗余提高可靠性。負載均衡服務通常是由專用軟體和硬件來完成。
對於互聯網服務,負載均衡器通常是一個軟體程序,這個程序偵聽一個外部端口,互聯網用戶可以通過這個端口來訪問服務,而作為負載均衡器的軟體會將用戶的請求轉發給后台內網服務器,內網服務器將請求的響應返回給負載均衡器,負載均衡器再將響應發送到用戶,這樣就向互聯網用戶隱藏了內網結構,阻止了用戶直接訪問后台(內網)服務器,使得服務器更加安全,可以阻止對核心網絡棧和運行在其它端口服務的攻擊。
當所有后台服務器出現故障時,有些負載均衡器會提供一些特殊的功能來處理這種情況。例如轉發請求到一個備用的負載均衡器、顯示一條關於服務中斷的消息等。負載均衡器使得 IT 團隊可以顯著提高容錯能力。它可以自動提供大量的容量以處理任何應用程序流量的增加或減少。
對於核心 api,需要保證搞可靠性,那么就要對於該 api 有多個 backend service,即實際后端對該 api 有多個服務的節點;那么最好在 api-gateway 即 kong 這一層實現負載均衡。
Kong為多個后端服務提供了多種負載平衡請求方式:一種基於DNS的簡單方法,以及一種更加動態的環平衡器,該方法還允許在不需要DNS服務器的情況下進行服務注冊。
基於DNS的負載均衡
當使用基於DNS的負載均衡時,后端服務的注冊是在Kong以外完成的,而Kong僅接收來自DNS服務器的更新。 如果名稱解析為多個IP地址,並且主機名未解析為上游名稱或名稱,則每個使用包含hostname(而不是IP地址)的host定義的service都將自動使用基於DNS的負載平衡你的DNS hosts文件。 DNS記錄ttl設置(生存時間)決定信息刷新的頻率。當使用0的ttl時,每個請求都將使用自己的DNS查詢來解析。很明顯,這會導致性能下降,但更新/更改的延遲將非常低。
A記錄
A記錄包含一個或多個IP地址。因此,當主機名解析為A記錄時,每個后端服務都必須具有自己的IP地址。 由於沒有weight信息,因此所有條目在負載平衡器中將被視為具有相同的權重,並且平衡器(balancer)將進行簡單的循環。
SRV記錄
SRV記錄包含所有IP地址的權重(weight)和端口(port)信息。后端服務可以通過IP地址和端口號的唯一組合來識別。因此,單個IP地址可以在不同的端口上托管同一服務的多個實例。 由於weight信息可用,每個條目將在負載均衡器中獲得自己的權重,並執行加權循環。 同樣,任何給定的端口信息都將被來自DNS服務器的端口信息覆蓋。如果服務具有host = myhost.com和port = 123的屬性,並且myhost.com解析為具有127.0.0.1:456的SRV記錄,則該請求將被代理到http://127.0.0.1:456/somepath,因為123端口將被456覆蓋。
DNS優先級
DNS解析器將按順序解析以下記錄類型:
- 上次解析的最后一次成功類型(LAST)
- SRV記錄
- A記錄
- CNAME記錄
該順序可通過dns_order配置屬性進行配置。
解析不同記錄類型的順序。 LAST類型表示上次成功查找的類型(用於指定的名稱)。格式是一個(不區分大小寫)逗號分隔的列表。 默認值:LAST,SRV,A,CNAME
DNS警告(DNS caveats)
- 無論何時刷新DNS記錄,都會生成一個列表以正確處理權重。盡量保持權重為對方的倍數以保持算法的高效性,例如,17和31的2個權重將導致具有527個項目的結構,而權重16和32(或其最小的相對對應項1和2)將導致在只有3個條目的結構中,尤其是具有非常小(甚至0)ttl值的結構。
- 在這些情況下,某些域名服務器不會返回所有條目(由於UDP數據包的大小)(例如Consul最多返回3個),給定的Kong節點將只使用由名稱服務器提供的少數上游服務實例。在這種情況下,由於名稱服務器提供的信息有限,Kong節點實際上不了解某些實例,因此上游實例池可能會不一致地加載。為了緩解這種情況,可以使用不同的名稱服務器,使用IP地址而不是名稱,或者確保使用足夠的Kong節點來繼續使用所有上游服務。
- 當名稱服務器返回3 name error時,那么對於Kong來說這是一個有效的響應。如果這是意外,請首先驗證是否正在查詢正確的name,然后檢查您的nameserver配置。
- 從DNS記錄(A或SRV)中初始選擇IP地址不是隨機的。因此,當使用ttl為0的記錄時,nameserver應該隨機記錄條目。
環平衡器(ring-balancer)
當使用環平衡器時,添加和刪除后端服務將由Kong處理,並且不需要DNS更新。kong將擔任服務登記。節點可以通過一個HTTP請求added/deleted,並立即start/stop接收流量。 配置環平衡器是通過上游和目標實體完成的。
target:具有后端服務駐留端口號的IP地址或主機名,例如。 “192.168.100.12:80”。每個target都會得到一個額外的權重來指示它獲得的相對負載。 IP地址可以是IPv4和IPv6兩種格式。
upstream:可以在路由主機字段中使用的'virtual hostname',例如,上游命名的weather.v2.service將從具有host = weather.v2.service的服務獲得所有請求
上游(upstream)
每個upstream都有自己的環形平衡器。每個upstream可以有許多target條目附加到它,代理到'virtual hostname'的請求將在target上進行負載平衡。環形平衡器具有預定義(pre-defined)數量的槽(number of slots),並且基於目標權重,槽(slots)被分配給上游的目標。
添加和刪除目標可以通過Admin API上的簡單HTTP請求完成。這個操作相對便宜。改變上游本身更昂貴,因為例如當槽的數量改變時平衡器將需要重建。
自動重建平衡器的唯一情況是清理目標歷史記錄時;除此之外,它只會在改變時重建。
在平衡器內部有(從1到slots)的位置,它們隨機分布( randomly distributed)在環上。在運行時需要隨機性來調用環平衡器。輪子上的簡單循環(位置)將為目標提供良好的分布式加權循環,同時在插入/刪除目標時也具有廉價的操作。
每個目標使用的插槽數量(至少)應該在100個左右,以確保插槽正確分布。例如。對於預期最多8個目標,即使初始設置僅包含2個目標,上游應至少定義為slot = 800。
這里的折衷是,插槽數量越多,隨機分布越好,但更改更為昂貴(添加/刪除目標)
有關添加和操作上游的詳細信息,請參閱Admin API
目標(target)
由於上游保留了更改歷史記錄,目標只能添加,不能修改或刪除。要更改目標,只需為目標添加一個新條目,然后更改重量值。最后一個條目是將要使用的條目。因為這樣的設置權重= 0將禁用目標,有效地從平衡器中刪除它。有關添加和操作目標的詳細信息,請參閱Admin API參考的目標部分。
當活動條目比活動條目多10倍時,目標將自動清除。清潔將涉及重建平衡器,因此比添加目標條目更昂貴。 目標也可以具有主機名而不是IP地址,在這種情況下,名稱將被解析,所有找到的條目將被單獨添加到環形平衡器中,例如,添加api.host.com:123且權重= 100。名稱'api.host.com'解析為具有2個IP地址的A記錄。然后這兩個IP地址將被添加為目標,每個獲得weight = 100和端口123.注意:權重用於單個條目,而不是整個! 它是否會解析為SRV記錄,然后DNS記錄中的端口和權重字段將被拾取,並且會否定給定的端口123和權重= 100。 平衡器將遵守DNS記錄的ttl設置和重新查詢,並在平衡器到期時更新。 例外情況:當DNS記錄的ttl = 0時,主機名將被添加為具有指定權重的單個目標。在對該目標的每個代理請求時,它將再次查詢名稱服務器。
平衡算法
默認情況下,環平衡器將使用加權循環方案。另一種方法是使用基於散列的算法。散列的輸入可以是none,consumer,ip或header。如果設置為none,則將使用加權循環方案,並且散列將被禁用。
有兩種選擇,一種primary 和 一種回退(fallback),以防primary失敗(例如,如果primary設置為consumer,但沒有consumer通過驗證)
不同的散列選項:
- none:不要使用散列,而是使用weighted-round-robin(默認)。
- consumer:使用消費者ID作為散列輸入。如果沒有消費者ID可用(如果使用外部身份驗證,如ldap),此選項將回退到憑證ID。
- ip:遠程(始發)IP地址將用作輸入。在使用此設置時,查看確定真實IP的配置設置。
- header:使用指定的標題(在hash_on_header或hash_fallback_header字段中)作為散列的輸入。
哈希算法基於'一致哈希'(或'ketama原理'),它確保當平衡器通過改變目標(添加,移除,失敗或改變權重)而被修改時,只有最小數量的哈希損失發生。這將最大化上游緩存命中。
有關確切設置的更多信息,請參閱Admin API參考的上游upstream部分。
平衡警告(Balancing caveats)
環平衡器設計為既可以在單個節點上工作,也可以在群集中工作。對於加權循環算法沒有太大的區別,但是當使用基於散列的算法時,重要的是所有節點構建完全相同的環平衡器以確保它們都工作一致。要做到這一點,平衡器必須以確定性的方式構建。
不要在平衡器中使用主機名稱,因為平衡器可能會/會慢慢發生分歧,因為DNS ttl只有第二精度,更新取決於實際請求名稱的時間。最重要的是一些域名服務器沒有返回所有條目的問題,這加劇了這個問題。因此,在Kong群集中使用哈希方法時,只能通過IP地址添加目標實體,而不能通過名稱添加target實體。
當選擇你的散列輸入時,確保輸入具有足夠的方差以得到散布良好的散列。哈希將使用CRC-32摘要進行計算。例如,如果您的系統有成千上萬的用戶,但只有少數用戶(每個平台定義了3個用戶:Web,iOS和Android),那么挑選consumer散列輸入是不夠的,通過設置使用遠程IP地址對於ip的哈希將提供更多的輸入差異,從而更好地分配哈希輸出
藍綠部署(Blue-Green Deployments)
使用環形平衡器,可以輕松地為一項服務策划一個藍綠色部署。切換目標基礎架構只需要服務上的PATCH請求,即可更改其主機值。
設置“blue”環境,運行version 1 of the address service:
# create an upstream $ curl -X POST http://kong:8001/upstreams \ --data "name=address.v1.service" # add two targets to the upstream $ curl -X POST http://kong:8001/upstreams/address.v1.service/targets \ --data "target=192.168.34.15:80" --data "weight=100" $ curl -X POST http://kong:8001/upstreams/address.v1.service/targets \ --data "target=192.168.34.16:80" --data "weight=50" # create a Service targeting the Blue upstream $ curl -X POST http://kong:8001/services/ \ --data "name=address-service" \ --data "host=address.v1.service" \ --data "path=/address" # finally, add a Route as an entry-point into the Service $ curl -X POST http://kong:8001/services/address-service/routes/ \ --data "hosts[]=address.mydomain.com"
在部署version 2 of the address service之前,請設置“green”環境:
# create a new Green upstream for address service v2 $ curl -X POST http://kong:8001/upstreams \ --data "name=address.v2.service" # add targets to the upstream $ curl -X POST http://kong:8001/upstreams/address.v2.service/targets \ --data "target=192.168.34.17:80" --data "weight=100" $ curl -X POST http://kong:8001/upstreams/address.v2.service/targets \ --data "target=192.168.34.18:80" --data "weight=100"
要激活blue/green開關,現在只需要更新服務:
# Switch the Service from Blue to Green upstream, v1 -> v2 $ curl -X PATCH http://kong:8001/services/address-service \ --data "host=address.v2.service"
主機頭設置為address.mydomain.com的傳入請求現在由Kong代理到新目標; 1/2的請求將轉到http://192.168.34.17:80/address(權重= 100),另一半將轉到http://192.168.34.18:80/address(權重= 100 )。
與往常一樣,通過Kong Admin API進行的更改是動態的,並且會立即生效。不需要重新加載或重新啟動,並且沒有進行中的請求將被丟棄。
金絲雀版本(Canary Releases)
使用環形平衡器,可以精確調整目標重量,從而實現平穩.。
使用一個非常簡單的2 target示例:
# first target at 1000 $ curl -X POST http://kong:8001/upstreams/address.v2.service/targets \ --data "target=192.168.34.17:80" --data "weight=1000" # second target at 0 $ curl -X POST http://kong:8001/upstreams/address.v2.service/targets \ --data "target=192.168.34.18:80" --data "weight=0"
通過重復請求,但每次改變權重,流量將緩慢路由到另一個target。例如,將其設置為10%:
# first target at 900 $ curl -X POST http://kong:8001/upstreams/address.v2.service/targets \ --data "target=192.168.34.17:80" --data "weight=900" # second target at 100 $ curl -X POST http://kong:8001/upstreams/address.v2.service/targets \ --data "target=192.168.34.18:80" --data "weight=100"
通過Kong Admin API進行的更改是動態的,並會立即生效。不需要重新加載或重新啟動,並且沒有進行中的請求將被丟棄。
KONG負載均衡實現
實際在操作過程中,采用的是 kong 的 Ring-balancer 做負載均衡。
使用 Kong Community Edition(社區版 v0.13)來搭建一個負載均衡器,由於 Kong 是基於 Openresty 的,而 Openresty 又是 Nginx 的二次封裝,所有很多配置項和 Nginx 類似。
來看一個較為典型的 Nginx 負載均衡配置:
upstream hello { server localhost:3000 weight=100; server localhost:3001 weight=50; } server { listen 80; location /hello { proxy_pass http://hello; } }
nginx 監聽來自本地 80 端口的請求,如果路徑與 /hello 匹配,便將請求原封不動的轉發到名稱為 hello 的upstream,而該 upstream 我們配置了一個負載均衡器,會路由到本地的 3000 端口和 3001 端口。
接下來便可以針對 Kong 進行負載均衡的配置了。
配置 upstream 和 target
創建一個名稱 hello 的 upstream
curl -X POST http://localhost:8001/upstreams --data "name=hello"
為 hello 添加兩個負載均衡節點
curl -X POST http://localhost:8001/upstreams/hello/targets --data "target=localhost:3000" --data "weight=100"
curl -X POST http://localhost:8001/upstreams/hello/targets --data "target=localhost:3001" --data "weight=50"
如上的配置對應了 Nginx 的配置:
upstream hello { server localhost:3000 weight=100; server localhost:3001 weight=50; }
配置 service 和 route
使用 Kong v0.13之前版本的用戶可能會接觸過 api 這個概念,但是在 Kong v0.13.0 中,已經被廢除了,取而代之的是 service 和 route 的配置。
配置一個 service
curl -X POST http://localhost:8001/services --data "name=hello" --data "host=hello"
host 的值便對應了 upstream 的名稱,配置成功后會返回生成的 service 的 id,返回結果:8695cc65-16c1-43b1-95a1-5d30d0a50409。
為上面的 service 配置路由信息
curl -X POST http://localhost:8001/routes --data "paths[]=/hello" --data "service.id=8695cc65-16c1-43b1-95a1-5d30d0a50409"
請求路徑包含 /hello 的請求都會被轉移到對應的 service 進行處理。
如上的配置便對應了Nginx的配置:
location /hello { proxy_pass http://hello; }
測試 Kong 的負載均衡
curl http://localhost:8000/hello/hi
因為復雜均衡的原因,需要多測試幾次,多次 curl 之后結果如下:
3000 3000 3000 3000 3000 3000 3001 3001 3001 3000 3001
reference:
https://getkong.org/docs/0.13.x/loadbalancing/
https://getkong.org/docs/0.13.x/configuration/