199 lines
5.6 KiB
Lua
199 lines
5.6 KiB
Lua
--------------------------------------------------------------------------
|
|
-- Least-connections balancer.
|
|
--
|
|
-- This balancer implements a least-connections algorithm. The balancer will
|
|
-- honour the weights.
|
|
--
|
|
-- @author Thijs Schreijer
|
|
-- @copyright 2016-2020 Kong Inc. All rights reserved.
|
|
-- @license Apache 2.0
|
|
|
|
|
|
local balancers = require "kong.runloop.balancer.balancers"
|
|
local binaryHeap = require "binaryheap"
|
|
local ngx_log = ngx.log
|
|
local ngx_DEBUG = ngx.DEBUG
|
|
|
|
local EMPTY = require("kong.tools.table").EMPTY
|
|
|
|
|
|
local lc = {}
|
|
lc.__index = lc
|
|
|
|
local function insertAddr(bh, addr)
|
|
addr.connectionCount = addr.connectionCount or 0
|
|
|
|
if addr.available then
|
|
bh:insert((addr.connectionCount + 1) / addr.weight, addr)
|
|
end
|
|
end
|
|
|
|
-- @param delta number (+1 or -1) to update the connection count
|
|
local function updateConnectionCount(bh, addr, delta)
|
|
addr.connectionCount = addr.connectionCount + delta
|
|
|
|
if not addr.available or not bh then
|
|
return
|
|
end
|
|
|
|
-- NOTE: we use `connectionCount + 1` this ensures that even on a balancer
|
|
-- with 0 connections the heighest weighted entry is picked first. If we'd
|
|
-- not add the `+1` then any target with 0 connections would always be the
|
|
-- first to be picked (even if it has a very low eight)
|
|
bh:update(addr, (addr.connectionCount + 1) / addr.weight)
|
|
end
|
|
|
|
local function releaseHandleAddress(handle)
|
|
if handle.address then
|
|
updateConnectionCount(handle.binaryHeap, handle.address, -1)
|
|
handle.address = nil
|
|
end
|
|
end
|
|
|
|
function lc:getPeer(cacheOnly, handle, hashValue)
|
|
if handle then
|
|
-- existing handle, so it's a retry
|
|
handle.retryCount = handle.retryCount + 1
|
|
|
|
-- keep track of failed addresses
|
|
handle.failedAddresses = handle.failedAddresses or setmetatable({}, {__mode = "k"})
|
|
handle.failedAddresses[handle.address] = true
|
|
-- let go of previous connection
|
|
releaseHandleAddress(handle)
|
|
else
|
|
-- no handle, so this is a first try
|
|
handle = {
|
|
retryCount = 0,
|
|
binaryHeap = self.binaryHeap,
|
|
release = releaseHandleAddress,
|
|
}
|
|
end
|
|
|
|
local address, ip, port, host
|
|
local balancer = self.balancer
|
|
while true do
|
|
if not balancer.healthy then
|
|
-- Balancer unhealthy, nothing we can do.
|
|
-- This check must be inside the loop, since calling getPeer could
|
|
-- cause a DNS update.
|
|
ip, port, host = nil, balancers.errors.ERR_BALANCER_UNHEALTHY, nil
|
|
break
|
|
end
|
|
|
|
|
|
-- go and find the next `address` object according to the LB policy
|
|
do
|
|
local reinsert
|
|
repeat
|
|
if address then
|
|
-- this address we failed before, so temp store it and pop it from
|
|
-- the tree. When we're done we'll reinsert them.
|
|
reinsert = reinsert or {}
|
|
reinsert[#reinsert + 1] = address
|
|
self.binaryHeap:pop()
|
|
end
|
|
address = self.binaryHeap:peek()
|
|
until address == nil or not (handle.failedAddresses or EMPTY)[address]
|
|
|
|
if address == nil and handle.failedAddresses then
|
|
-- we failed all addresses, so drop the list of failed ones, we are trying
|
|
-- again, so we restart using the ones that previously failed us. Until
|
|
-- eventually we hit the limit of retries (but that's up to the user).
|
|
handle.failedAddresses = nil
|
|
address = reinsert[1] -- the address to use is the first one, top of the heap
|
|
end
|
|
|
|
if reinsert then
|
|
-- reinsert the ones we temporarily popped
|
|
for i = 1, #reinsert do
|
|
local addr = reinsert[i]
|
|
insertAddr(self.binaryHeap, addr)
|
|
end
|
|
reinsert = nil -- luacheck: ignore
|
|
end
|
|
end
|
|
|
|
|
|
-- check the address returned, and get an IP
|
|
|
|
if address == nil then
|
|
-- No peers are available
|
|
ip, port, host = nil, balancers.errors.ERR_NO_PEERS_AVAILABLE, nil
|
|
break
|
|
end
|
|
|
|
ip, port, host = balancers.getAddressPeer(address, cacheOnly)
|
|
if ip then
|
|
-- success, exit
|
|
handle.address = address
|
|
updateConnectionCount(self.binaryHeap, address, 1)
|
|
break
|
|
end
|
|
|
|
if port ~= balancers.errors.ERR_DNS_UPDATED then
|
|
-- an unknown error
|
|
break
|
|
end
|
|
|
|
-- if here, we're going to retry because we already tried this address,
|
|
-- or because of a dns update
|
|
end
|
|
|
|
if ip then
|
|
return ip, port, host, handle
|
|
else
|
|
releaseHandleAddress(handle)
|
|
return nil, port
|
|
end
|
|
end
|
|
|
|
|
|
local function clearHeap(bh)
|
|
bh.payloads = {}
|
|
bh.reverse = {}
|
|
bh.values = {}
|
|
end
|
|
|
|
function lc:afterHostUpdate()
|
|
clearHeap(self.binaryHeap)
|
|
|
|
for _, target in ipairs(self.balancer.targets) do
|
|
for _, address in ipairs(target.addresses) do
|
|
insertAddr(self.binaryHeap, address)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Creates a new balancer. The balancer is based on a binary heap tracking
|
|
-- the number of active connections. The number of connections
|
|
-- assigned will be relative to the weight.
|
|
--
|
|
-- The options table has the following fields, additional to the ones from
|
|
-- the `balancer_base`;
|
|
--
|
|
-- - `hosts` (optional) containing hostnames, ports and weights. If omitted,
|
|
-- ports and weights default respectively to 80 and 10.
|
|
-- @param opts table with options
|
|
-- @return new balancer object or nil+error
|
|
-- @usage -- hosts example
|
|
-- local hosts = {
|
|
-- "konghq.com", -- name only, as string
|
|
-- { name = "github.com" }, -- name only, as table
|
|
-- { name = "getkong.org", port = 80, weight = 25 }, -- fully specified, as table
|
|
-- }
|
|
function lc.new(opts)
|
|
--printf("new")
|
|
local self = setmetatable({
|
|
binaryHeap = binaryHeap.minUnique(),
|
|
balancer = opts.balancer
|
|
}, lc)
|
|
|
|
self:afterHostUpdate()
|
|
|
|
ngx_log(ngx_DEBUG, "least-connections balancer created")
|
|
|
|
return self
|
|
end
|
|
|
|
return lc
|