├── README.md └── chash.lua /README.md: -------------------------------------------------------------------------------- 1 | lua-consistent-hash 2 | =================== 3 | 4 | A reimplementation of consistent hash in lua based on yaoweibin's fork of consistent hash (https://github.com/yaoweibin/ngx_http_consistent_hash) 5 | 6 | Usage 7 | ----- 8 | 9 | ```lua 10 | local chash = require "chash" 11 | 12 | chash.add_upstream("192.168.0.251") 13 | chash.add_upstream("192.168.0.252") 14 | chash.add_upstream("192.168.0.253") 15 | 16 | local upstream = chash.get_upstream("my_hash_key") 17 | ``` -------------------------------------------------------------------------------- /chash.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | M.initialized = false 3 | 4 | local MMC_CONSISTENT_BUCKETS = 65536 5 | 6 | local HASH_PEERS = {} 7 | local CONTINUUM = {} 8 | local BUCKETS = {} 9 | 10 | local function hash_fn(key) 11 | local md5 = ngx.md5_bin(key) --nginx only 12 | return ngx.crc32_long(md5) --nginx only 13 | end 14 | 15 | --in-place quicksort 16 | local function quicksort(t, start, endi) 17 | start, endi = start or 1, endi or #t 18 | --partition w.r.t. first element 19 | if(endi - start < 2) then return t end 20 | local pivot = start 21 | for i = start + 1, endi do 22 | if t[i][2] < t[pivot][2] then 23 | local temp = t[pivot + 1] 24 | t[pivot + 1] = t[pivot] 25 | if(i == pivot + 1) then 26 | t[pivot] = temp 27 | else 28 | t[pivot] = t[i] 29 | t[i] = temp 30 | end 31 | pivot = pivot + 1 32 | end 33 | end 34 | t = quicksort(t, start, pivot - 1) 35 | return quicksort(t, pivot + 1, endi) 36 | end 37 | 38 | local function chash_find(point) 39 | local mid, lo, hi = 1, 1, #CONTINUUM 40 | while 1 do 41 | if point <= CONTINUUM[lo][2] or point > CONTINUUM[hi][2] then 42 | return CONTINUUM[lo] 43 | end 44 | 45 | mid = math.floor(lo + (hi-lo)/2) 46 | if point <= CONTINUUM[mid][2] and point > (mid and CONTINUUM[mid-1][2] or 0) then 47 | return CONTINUUM[mid] 48 | end 49 | 50 | if CONTINUUM[mid][2] < point then 51 | lo = mid + 1 52 | else 53 | hi = mid - 1 54 | end 55 | end 56 | end 57 | 58 | local function chash_init() 59 | 60 | local n = #HASH_PEERS 61 | 62 | local ppn = math.floor(MMC_CONSISTENT_BUCKETS / n) 63 | if ppn == 0 then 64 | ppn = 1 65 | end 66 | 67 | local C = {} 68 | for i,peer in ipairs(HASH_PEERS) do 69 | for k=1, math.floor(ppn * peer[1]) do 70 | local hash_data = peer[2] .. "-"..tostring(math.floor(k - 1)) 71 | table.insert(C, {peer[2], hash_fn(hash_data)}) 72 | end 73 | end 74 | 75 | CONTINUUM = quicksort(C, 1, #C) 76 | 77 | local step = math.floor(0xFFFFFFFF / MMC_CONSISTENT_BUCKETS) 78 | 79 | BUCKETS = {} 80 | for i=1, MMC_CONSISTENT_BUCKETS do 81 | table.insert(BUCKETS, i, chash_find(math.floor(step * (i - 1)))) 82 | end 83 | 84 | M.initialized = true 85 | end 86 | 87 | local function chash_get_upstream(key) 88 | if not M.initialized then 89 | chash_init() 90 | end 91 | 92 | local point = math.floor(ngx.crc32_long(key)) --nginx only 93 | 94 | local tries = #HASH_PEERS 95 | point = point + (89 * tries) 96 | local bucket_idx = point % MMC_CONSISTENT_BUCKETS 97 | if bucket_idx == 0 then 98 | bucket_idx = 1 99 | end 100 | return BUCKETS[bucket_idx][1] 101 | end 102 | M.get_upstream = chash_get_upstream 103 | 104 | local function chash_add_upstream(upstream, weight) 105 | M.initialized = false 106 | 107 | weight = weight or 1 108 | table.insert(HASH_PEERS, {weight, upstream}) 109 | end 110 | M.add_upstream = chash_add_upstream 111 | 112 | return M 113 | --------------------------------------------------------------------------------