diff options
Diffstat (limited to 'openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua')
-rw-r--r-- | openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua new file mode 100644 index 0000000..b3cd46e --- /dev/null +++ b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua @@ -0,0 +1,135 @@ +--[[ + + Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--]] + +local _M = {} +_M._VERSION = '1.0.0' + +local floor = math.floor +local str_byte = string.byte +local tab_sort = table.sort +local tab_insert = table.insert + +local MOD = 2 ^ 32 +local REPLICAS = 20 +local LUCKY_NUM = 13 + + +local tbl_util = require('lib.utils.table_util') +local tbl_isempty = tbl_util.isempty +local tbl_isequal = require('pl.tablex') +local peerwatcher = require "core.peerwatcher" +local ngx_var = ngx.var +local hash_data = {} + +local function hash_string(str) + local key = 0 + for i = 1, #str do + key = (key * 31 + str_byte(str, i)) % MOD + end + return key +end + + +local function init_consistent_hash_state(servers) + local weight_sum = 0 + local weight = 1 + for _, srv in ipairs(servers) do + if srv.weight and srv.weight ~= 0 then + weight = srv.weight + end + weight_sum = weight_sum + weight + end + + local circle, members = {}, 0 + for index, srv in ipairs(servers) do + local key = ("%s:%s"):format(srv.ip, srv.port) + local base_hash = hash_string(key) + for c = 1, REPLICAS * weight_sum do + local hash = (base_hash * c * LUCKY_NUM) % MOD + tab_insert(circle, { hash, index }) + end + + members = members + 1 + end + tab_sort(circle, function(a, b) return a[1] < b[1] end) + return { circle = circle, members = members } +end + +local function update_consistent_hash_state(hash_data,servers,svckey) + -- compare servers in ctx with servers in cache + -- update the hash data if changes occur + local serverscache = hash_data[svckey].servers + tab_sort(serverscache, function(a, b) return a.ip < b.ip end) + tab_sort(servers, function(a, b) return a.ip < b.ip end) + if not tbl_isequal.deepcompare(serverscache, servers, false) then + local tmp_chash = init_consistent_hash_state(servers) + hash_data[svckey].servers =servers + hash_data[svckey].chash = tmp_chash + end +end + +local function binary_search(circle, key) + local size = #circle + local st, ed, mid = 1, size + + while st <= ed do + mid = floor((st + ed) / 2) + if circle[mid][1] < key then + st = mid + 1 + else + ed = mid - 1 + end + end + + return st == size + 1 and 1 or st +end + + +function _M.select_backserver(servers,svckey) + + if hash_data[svckey] == nil then + local tbl = {} + tbl['servers'] = {} + tbl['chash'] = {} + hash_data[svckey] = tbl + end + + if tbl_isempty(hash_data[svckey].servers) then + local tmp_chash = init_consistent_hash_state(servers) + hash_data[svckey].servers = servers + hash_data[svckey].chash = tmp_chash + else + update_consistent_hash_state(hash_data,servers,svckey) + end + + local chash = hash_data[svckey].chash + local circle = chash.circle + local hash_key = ngx_var.remote_addr + local st = binary_search(circle, hash_string(hash_key)) + local size = #circle + local ed = st + size - 1 + for i = st, ed do + local idx = circle[(i - 1) % size + 1][2] + if peerwatcher.is_server_ok(svckey,hash_data[svckey].servers[idx]) then + return hash_data[svckey].servers[idx] + end + end + return nil, "consistent hash: no servers available" +end + +return _M |