refactor balancer into more testable and extensible interface

This commit is contained in:
Elvin Efendi 2018-05-18 17:36:43 -04:00
parent 1b5db4b3b0
commit e9dc275b81
20 changed files with 368 additions and 467 deletions

View file

@ -0,0 +1,21 @@
local balancer_resty = require("balancer.resty")
local resty_chash = require("resty.chash")
local util = require("util")
local _M = balancer_resty:new({ factory = resty_chash, name = "chash" })
function _M.new(self, backend)
local nodes = util.get_nodes(backend.endpoints)
local o = { instance = self.factory:new(nodes), hash_by = backend["upstream-hash-by"] }
setmetatable(o, self)
self.__index = self
return o
end
function _M.balance(self)
local key = util.lua_ngx_var(self.hash_by)
local endpoint_string = self.instance:find(key)
return util.split_pair(endpoint_string, ":")
end
return _M

View file

@ -14,7 +14,7 @@ local PICK_SET_SIZE = 2
local ewma_lock = resty_lock:new("locks", {timeout = 0, exptime = 0.1})
local _M = {}
local _M = { name = "ewma" }
local function lock(upstream)
local _, err = ewma_lock:lock(upstream .. LOCK_KEY)
@ -117,17 +117,18 @@ local function pick_and_score(peers, k)
return peers[lowest_score_index]
end
function _M.balance(backend)
local peers = backend.endpoints
function _M.balance(self)
local peers = self.peers
if #peers == 1 then
return peers[1]
end
local k = (#peers < PICK_SET_SIZE) and #peers or PICK_SET_SIZE
local peer_copy = util.deepcopy(peers)
return pick_and_score(peer_copy, k)
local endpoint = pick_and_score(peer_copy, k)
return endpoint.address, endpoint.port
end
function _M.after_balance()
function _M.after_balance(_)
local response_time = tonumber(util.get_first_value(ngx.var.upstream_response_time)) or 0
local connect_time = tonumber(util.get_first_value(ngx.var.upstream_connect_time)) or 0
local rtt = connect_time + response_time
@ -139,10 +140,21 @@ function _M.after_balance()
get_or_update_ewma(upstream, rtt, true)
end
function _M.sync(_)
function _M.sync(self, backend)
local changed = not util.deep_compare(self.peers, backend.endpoints)
if not changed then
return
end
-- TODO: Reset state of EWMA per backend
ngx.shared.balancer_ewma:flush_all()
ngx.shared.balancer_ewma_last_touched_at:flush_all()
end
function _M.new(self, backend)
local o = { peers = backend.endpoints }
setmetatable(o, self)
self.__index = self
return o
end
return _M

View file

@ -1,142 +1,25 @@
local resty_roundrobin = require("resty.roundrobin")
local resty_chash = require("resty.chash")
local util = require("util")
local ck = require("resty.cookie")
local _M = {}
local instances = {}
local function get_resty_balancer_nodes(endpoints)
local nodes = {}
local weight = 1
for _, endpoint in pairs(endpoints) do
local endpoint_string = endpoint.address .. ":" .. endpoint.port
nodes[endpoint_string] = weight
end
return nodes
function _M.new(self, o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
local function init_resty_balancer(factory, instance, endpoints)
local nodes = get_resty_balancer_nodes(endpoints)
if instance then
instance:reinit(nodes)
else
instance = factory:new(nodes)
function _M.sync(self, backend)
local nodes = util.get_nodes(backend.endpoints)
local changed = not util.deep_compare(self.instance.nodes, nodes)
if not changed then
return
end
return instance
self.instance:reinit(nodes)
end
local function is_sticky(backend)
return backend["sessionAffinityConfig"] and backend["sessionAffinityConfig"]["name"] == "cookie"
end
local function cookie_name(backend)
return backend["sessionAffinityConfig"]["cookieSessionAffinity"]["name"] or "route"
end
local function encrypted_endpoint_string(backend, endpoint_string)
local encrypted, err
if backend["sessionAffinityConfig"]["cookieSessionAffinity"]["hash"] == "sha1" then
encrypted, err = util.sha1_digest(endpoint_string)
else
encrypted, err = util.md5_digest(endpoint_string)
end
if err ~= nil then
ngx.log(ngx.ERR, err)
end
return encrypted
end
local function set_cookie(backend, value)
local cookie, err = ck:new()
if not cookie then
ngx.log(ngx.ERR, err)
end
local ok
ok, err = cookie:set({
key = cookie_name(backend),
value = value,
path = "/",
domain = ngx.var.host,
httponly = true,
})
if not ok then
ngx.log(ngx.ERR, err)
end
end
local function pick_random(instance)
local index = math.random(instance.npoints)
return instance:next(index)
end
local function sticky_endpoint_string(instance, backend)
local cookie, err = ck:new()
if not cookie then
ngx.log(ngx.ERR, err)
return pick_random(instance)
end
local key = cookie:get(cookie_name(backend))
if not key then
local tmp_endpoint_string = pick_random(instance)
key = encrypted_endpoint_string(backend, tmp_endpoint_string)
set_cookie(backend, key)
end
return instance:find(key)
end
function _M.is_applicable(backend)
return is_sticky(backend) or backend["upstream-hash-by"] or backend["load-balance"] == "round_robin"
end
function _M.balance(backend)
local instance = instances[backend.name]
if not instance then
ngx.log(ngx.ERR, "no LB algorithm instance was found")
return nil
end
local endpoint_string
if is_sticky(backend) then
endpoint_string = sticky_endpoint_string(instance, backend)
elseif backend["upstream-hash-by"] then
local key = util.lua_ngx_var(backend["upstream-hash-by"])
endpoint_string = instance:find(key)
else
endpoint_string = instance:find()
end
local address, port = util.split_pair(endpoint_string, ":")
return { address = address, port = port }
end
function _M.sync(backend)
local instance = instances[backend.name]
local factory = resty_roundrobin
if is_sticky(backend) or backend["upstream-hash-by"] then
factory = resty_chash
end
if instance then
local mt = getmetatable(instance)
if mt.__index ~= factory then
ngx.log(ngx.INFO, "LB algorithm has been changed, resetting the instance")
instance = nil
end
end
instances[backend.name] = init_resty_balancer(factory, instance, backend.endpoints)
end
function _M.after_balance()
function _M.after_balance(_)
end
return _M

View file

@ -0,0 +1,20 @@
local balancer_resty = require("balancer.resty")
local resty_roundrobin = require("resty.roundrobin")
local util = require("util")
local _M = balancer_resty:new({ factory = resty_roundrobin, name = "round_robin" })
function _M.new(self, backend)
local nodes = util.get_nodes(backend.endpoints)
local o = { instance = self.factory:new(nodes) }
setmetatable(o, self)
self.__index = self
return o
end
function _M.balance(self)
local endpoint_string = self.instance:find()
return util.split_pair(endpoint_string, ":")
end
return _M

View file

@ -0,0 +1,80 @@
local balancer_resty = require("balancer.resty")
local resty_chash = require("resty.chash")
local util = require("util")
local ck = require("resty.cookie")
local _M = balancer_resty:new({ factory = resty_chash, name = "sticky" })
function _M.new(self, backend)
local nodes = util.get_nodes(backend.endpoints)
local digest_func = util.md5_digest
if backend["sessionAffinityConfig"]["cookieSessionAffinity"]["hash"] == "sha1" then
digest_func = util.sha1_digest
end
local o = {
instance = self.factory:new(nodes),
cookie_name = backend["sessionAffinityConfig"]["cookieSessionAffinity"]["name"] or "route",
digest_func = digest_func,
}
setmetatable(o, self)
self.__index = self
return o
end
local function encrypted_endpoint_string(self, endpoint_string)
local encrypted, err = self.digest_func(endpoint_string)
if err ~= nil then
ngx.log(ngx.ERR, err)
end
return encrypted
end
local function set_cookie(self, value)
local cookie, err = ck:new()
if not cookie then
ngx.log(ngx.ERR, err)
end
local ok
ok, err = cookie:set({
key = self.cookie_name,
value = value,
path = "/",
domain = ngx.var.host,
httponly = true,
})
if not ok then
ngx.log(ngx.ERR, err)
end
end
local function pick_random(instance)
local index = math.random(instance.npoints)
return instance:next(index)
end
local function sticky_endpoint_string(self)
local cookie, err = ck:new()
if not cookie then
ngx.log(ngx.ERR, err)
return pick_random(self.instance)
end
local key = cookie:get(self.cookie_name)
if not key then
local tmp_endpoint_string = pick_random(self.instance)
key = encrypted_endpoint_string(self, tmp_endpoint_string)
set_cookie(self, key)
end
return self.instance:find(key)
end
function _M.balance(self)
local endpoint_string = sticky_endpoint_string(self)
return util.split_pair(endpoint_string, ":")
end
return _M