-------------------------------------------------------------------------- -- 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