負載均衡,英文名稱為Load Balance,其含義就是指將負載(工作任務)進行平衡、分攤到多個操作單元上進行執行
以下為幾種負載均衡策略介紹
大家很多時候說到隨機的負載均衡都會想到 Round Robin, 其實 Round Robin並非隨機,
Random 這種是真正意義上隨機,根據隨機演演算法隨意分配請求到伺服器。
優點:
缺點:
如上述,其實輪詢是平均策略,並非隨機策略,
它的具體策略內容如下:
負載均衡負責者有一份 伺服器列表,
它會將其做排序,形成固定的 1 到 N 的順序列表排隊,
每次請求都會佇列依次選擇一位沒有輪到的伺服器同志接受 請求任務,
當整個佇列都接受過任務後,就會從頭開始新一輪的任務排隊。
而大家為什麼很多時候又說它是隨機策略呢?
主要是對請求的client 來說, 這一次和下一次請求的伺服器並不一定是同一個伺服器,所以像是隨機。
優點:
缺點:
從名字我們就能很輕鬆明白了,
它的策略非常簡單: 就是每次取連線計數最小的那個伺服器使用
優點:
缺點:
其他的負載均衡策略都適合於無狀態服務,
只有 Hash 是專門解決有狀態服務的負載均衡問題的。
它的具體策略就以其中簡單的做法作為說明:
比如 ip 或者 url hash, 會用 ip 或者 url 的string 根據 hash 演演算法 算出固定的整型數值,
然後用該整型數值 根據 伺服器數量 取模運算 得出對應哪一臺機器,
從而形成 粘機 的效果
優點:
缺點:
印象中好像該方式最早見於 Finagle(Twitter的使用者端RPC庫) 中。
理論上來說伺服器 在cpu 算力不足,網路卡負荷過大,埠不足等等各種情況下,響應的時間都會存在明顯變長的情況
那麼響應的延遲變化就可以一定程度上用來評價伺服器的負載以及伺服器自身情況,
EWMA 的思想就是衡量請求延遲變化來動態優化負載均衡效果。
簡單來說,EWMA就是 保持每個伺服器請求的往返時間的移動平均值,以未完成請求的數量加權,並將流量分配給成本函數最小的伺服器。
一般來說,還會使用P2C策略結合 EWMA使用,以避免同一時間集中命中同一臺伺服器。 (P2C 就是隨機選取兩臺伺服器,比較他們倆的EWMA值,取最小的那一個)
linkerd 做過一個負載均衡的測驗,其結果 (當然並不一定代表實際效果)
優點:
缺點:
嚴格來說,權重很少作為單獨的負載均衡策略,
一般都是與上述各種負載均衡策略進行組合。
權重的目的主要是解決 我們在已知或者能預估出伺服器的負載能力的情況下, 我們如何更好的預設資源的分配。
所以現在一般這些負載均衡演演算法都會提供 權重引數以便大家預設負載比例,
甚至一些還嘗試用機器學習等手段動態調整權重引數等,以便更快調整資源負載情況
篇幅關係,這裡不解釋每一個怎麼實現了,只介紹 輪循(Round Robin)
以下內容更新到 openresty-dev-1.rockspec
-- 依賴包
dependencies = {
"lua-resty-balancer >= 0.04",
}
然後執行
luarocks install openresty-dev-1.rockspec --tree=deps --only-deps --local
具體demo 程式碼如下:
worker_processes 1; #nginx worker 數量
error_log logs/error.log; #指定錯誤紀錄檔檔案路徑
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr [$time_local] $status $request_time $upstream_status $upstream_addr $upstream_response_time';
access_log logs/access.log main buffer=16384 flush=3; #access_log 檔案設定
lua_package_path "$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;$prefix/?.lua;$prefix/?/init.lua;;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;";
lua_package_cpath "$prefix/deps/lib64/lua/5.1/?.so;$prefix/deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";
# 開啟 lua code 快取
lua_code_cache on;
upstream nature_upstream {
server 127.0.0.1:6699; #upstream 設定為 hello world 服務
# 一樣的balancer
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local upstream = ngx.ctx.api_ctx.upstream
local ok, err = balancer.set_current_peer(upstream.host, upstream.port)
if not ok then
ngx.log(ngx.ERR, "failed to set the current peer: ", err)
return ngx.exit(ngx.ERROR)
end
}
}
init_by_lua_block {
-- 初始化 lb
local roundrobin = require("resty.roundrobin")
local nodes = {k1 = {host = '127.0.0.1', port = 6698}, k2 = {host = '127.0.0.1', port = 6699}}
local ns = {}
for k, v in pairs(nodes) do
-- 初始化 weight
ns[k] = 1
end
local picker = roundrobin:new(ns)
-- 初始化路由
local radix = require("resty.radixtree")
local r = radix.new({
{paths = {'/aa/d'}, metadata = picker},
})
-- 匹配路由
router_match = function()
local p, err = r:match(ngx.var.uri, {})
if err then
log.error(err)
end
-- 執行 roundrobin lb 選擇
local k, err = p:find()
if not k then
return nil, err
end
return nodes[k]
end
}
server {
#監聽埠,若你的8699埠已經被佔用,則需要修改
listen 8699 reuseport;
location / {
# 在access階段匹配路由
access_by_lua_block {
local upstream = router_match()
if upstream then
ngx.ctx.api_ctx = { upstream = upstream }
else
ngx.exit(404)
end
}
proxy_http_version 1.1;
proxy_pass http://nature_upstream; #轉發到 upstream
}
}
#為了大家方便理解和測試,我們引入一個hello world 服務
server {
#監聽埠,若你的6699埠已經被佔用,則需要修改
listen 6699;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("HelloWorld")
}
}
}
}
啟動服務並測試
$ openresty -p ~/openresty-test -c openresty.conf #啟動
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第一次
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第二次
HelloWorld
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第三次
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>
$ curl --request GET 'http://127.0.0.1:8699/aa/d' #第四次
HelloWorld
可以看到 一次失敗一次成功輪著來,證明 lb 起效
所有這裡介紹的lb實現都可以參考 nature 中的例子