From 9486b67f40d9bef4cd5a28e9028f4f8d4e89f641 Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Fri, 10 Oct 2014 14:53:54 -0400 Subject: [PATCH 1/9] refactor fetch_server to use sorted sets and support for least-connetions --- conf/config.moon | 6 +- lua/conf/config.lua | 3 +- lua/src/bin/init_redx.lua | 3 + lua/src/lib/library.lua | 7 +++ lua/src/lib/redis.lua | 117 +++++++++++++++++++++++++++++++++++--- src/bin/init_redx.moon | 4 ++ src/lib/library.moon | 6 ++ src/lib/redis.moon | 67 ++++++++++++++++++++-- 8 files changed, 197 insertions(+), 16 deletions(-) diff --git a/conf/config.moon b/conf/config.moon index 1ae8f86..ca2f2c0 100644 --- a/conf/config.moon +++ b/conf/config.moon @@ -28,5 +28,9 @@ M.max_path_length = 1 -- Stickiness -- Amount of time (in seconds) you wish the session to be sticky -- Set to 0 if you want to disable stickiness -M.stickiness = 900 +M.stickiness = 0 + +-- Load Balancing Algorithm +-- "random" or "least-connections" +M.balance_algorithm = 'least-connections' return M diff --git a/lua/conf/config.lua b/lua/conf/config.lua index 4593522..22ccc67 100644 --- a/lua/conf/config.lua +++ b/lua/conf/config.lua @@ -6,5 +6,6 @@ M.redis_timeout = 5000 M.redis_keepalive_pool_size = 0 M.redis_keepalive_max_idle_timeout = 10000 M.max_path_length = 1 -M.stickiness = 900 +M.stickiness = 0 +M.balance_algorithm = 'least-connections' return M diff --git a/lua/src/bin/init_redx.lua b/lua/src/bin/init_redx.lua index eec91b0..e46b9c1 100644 --- a/lua/src/bin/init_redx.lua +++ b/lua/src/bin/init_redx.lua @@ -2,4 +2,7 @@ redis = require('redis') config = require('config') library = require('library') inspect = require('inspect') +socket = require('socket') +math.randomseed(socket.gettime() * 1000) +library.log(socket.gettime() * 1000) return library.log('Redis host: ' .. config.redis_host .. ':' .. config.redis_port) diff --git a/lua/src/lib/library.lua b/lua/src/lib/library.lua index f7329f7..f290fe5 100644 --- a/lua/src/lib/library.lua +++ b/lua/src/lib/library.lua @@ -20,6 +20,13 @@ M.split = function(str, delim) end return _accum_0 end +M.length = function(dict) + local count = 0 + for k, v in pairs(dict) do + count = count + 1 + end + return count +end M.Set = function(list) local set = { } for _, l in ipairs(list) do diff --git a/lua/src/lib/redis.lua b/lua/src/lib/redis.lua index e7233f9..ef8e92f 100644 --- a/lua/src/lib/redis.lua +++ b/lua/src/lib/redis.lua @@ -347,19 +347,120 @@ M.fetch_server = function(self, backend_key) return nil end if config.stickiness > 0 and backend_cookie ~= nil and backend_cookie ~= '' then - local resp, err = red:sismember('backend:' .. backend_key, backend_cookie) - if resp == 1 then - upstream = backend_cookie - else + local resp, err = red:zscore('backend:' .. backend_key, backend_cookie) + if resp == nil then self.session.backend = nil upstream = nil + else + upstream = backend_cookie end end if upstream == nil then - local err - upstream, err = red:srandmember('backend:' .. backend_key) - if not (err == nil) then - library.log('Failed getting backend: ' .. err) + local rawdata, err = red:zrangebyscore('backend:' .. backend_key, '-inf', '+inf', 'withscores') + local data = { } + do + local _tbl_0 = { } + for i, item in ipairs(rawdata) do + if i % 2 > 0 then + _tbl_0[item] = rawdata[i + 1] + end + end + data = _tbl_0 + end + local upstreams = { } + do + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(data) do + if k:sub(1, 1) ~= "_" then + _accum_0[_len_0] = { + backend = k, + connections = tonumber(v) + } + _len_0 = _len_0 + 1 + end + end + upstreams = _accum_0 + end + local backend_config = { } + do + local _tbl_0 = { } + for k, v in pairs(data) do + if k:sub(1, 1) == "_" then + _tbl_0[k] = v + end + end + backend_config = _tbl_0 + end + if #upstreams == 1 then + library.log_err('Only one backend, choosing it') + upstream = upstreams[1]['backend'] + else + if config.balance_algorithm == 'least-connections' then + if #upstreams == 2 then + local max_connections = tonumber(backend_config['_max_connections']) + if not (max_connections == nil) then + local available_connections = 0 + for _index_0 = 1, #upstreams do + local x = upstreams[_index_0] + available_connections = available_connections + (max_connections - x['connections']) + end + local rand = math.random(1, available_connections) + if rand <= (max_connections - upstreams[1]['connections']) then + upstream = upstreams[1]['backend'] + else + upstream = upstreams[2]['backend'] + end + end + else + local most_connections = nil + local least_connections = nil + for _index_0 = 1, #upstreams do + local up = upstreams[_index_0] + if most_connections == nil or up['connections'] > most_connections then + most_connections = up['connections'] + end + if least_connections == nil or up['connections'] < least_connections then + least_connections = up['connections'] + end + end + local available_upstreams + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #upstreams do + local up = upstreams[_index_0] + if up['connections'] < most_connections then + _accum_0[_len_0] = up + _len_0 = _len_0 + 1 + end + end + available_upstreams = _accum_0 + end + if #available_upstreams > 0 then + local available_connections = 0 + for _index_0 = 1, #available_upstreams do + local x = available_upstreams[_index_0] + available_connections = available_connections + (most_connections - x['connections']) + end + local rand = math.random(available_connections) + local offset = 0 + for _index_0 = 1, #available_upstreams do + local up = available_upstreams[_index_0] + if rand <= (most_connections - up['connections'] + offset) then + upstream = up['backend'] + break + end + offset = offset + (most_connections - up['connections']) + end + end + end + if upstream == nil and #upstreams > 0 then + upstream = upstreams[math.random(#upstreams)]['backend'] + end + else + upstream = upstreams[math.random(#upstreams)]['backend'] + end end end M.finish(red) diff --git a/src/bin/init_redx.moon b/src/bin/init_redx.moon index bf6ddff..06fa1a5 100644 --- a/src/bin/init_redx.moon +++ b/src/bin/init_redx.moon @@ -2,5 +2,9 @@ export redis = require 'redis' export config = require 'config' export library = require 'library' export inspect = require 'inspect' +export socket = require 'socket' +-- seed math.random +math.randomseed(socket.gettime! * 1000) +library.log(socket.gettime! * 1000) library.log('Redis host: ' .. config.redis_host .. ':' .. config.redis_port) diff --git a/src/lib/library.moon b/src/lib/library.moon index cac7814..94d32ec 100644 --- a/src/lib/library.moon +++ b/src/lib/library.moon @@ -12,6 +12,12 @@ M.split = (str, delim using nil) -> str ..= delim [part for part in str\gmatch "(.-)" .. escape_pattern delim] +M.length = (dict) -> + count = 0 + for k,v in pairs dict + count += 1 + return count + M.Set = (list) -> set = {} for _, l in ipairs(list) do diff --git a/src/lib/redis.moon b/src/lib/redis.moon index 9e7f6ff..0bc8687 100644 --- a/src/lib/redis.moon +++ b/src/lib/redis.moon @@ -225,16 +225,71 @@ M.fetch_server = (@, backend_key) -> red = M.connect(@) return nil if red == nil if config.stickiness > 0 and backend_cookie != nil and backend_cookie != '' - resp, err = red\sismember('backend:' .. backend_key, backend_cookie) - if resp == 1 - export upstream = backend_cookie - else + resp, err = red\zscore('backend:' .. backend_key, backend_cookie) + if resp == nil -- clear cookie by setting to nil @session.backend = nil export upstream = nil + else + export upstream = backend_cookie if upstream == nil - upstream, err = red\srandmember('backend:' .. backend_key) - library.log('Failed getting backend: ' .. err) unless err == nil + rawdata, err = red\zrangebyscore('backend:' .. backend_key, '-inf', '+inf', 'withscores') + data = {} + data = {item,rawdata[i+1] for i, item in ipairs rawdata when i % 2 > 0} + --split backends from config data + upstreams = {} + upstreams = [{ backend:k, connections: tonumber(v)} for k,v in pairs data when k\sub(1,1) != "_"] + backend_config = {} + backend_config = {k,v for k,v in pairs data when k\sub(1,1) == "_"} + if #upstreams == 1 + -- only one backend available + library.log_err('Only one backend, choosing it') + upstream = upstreams[1]['backend'] + else + if config.balance_algorithm == 'least-connections' + -- get least connection probability + if #upstreams == 2 + -- get least connection probability relative to max connections + max_connections = tonumber(backend_config['_max_connections']) + unless max_connections == nil + -- get total number of available connections + available_connections = 0 + for x in *upstreams + available_connections += (max_connections - x['connections']) + -- pick random number within total available connections + rand = math.random( 1, available_connections ) + if rand <= (max_connections - upstreams[1]['connections']) + upstream = upstreams[1]['backend'] + else + upstream = upstreams[2]['backend'] + else + -- get least connection probability relative to larger connections + -- get largest and least number of connections + most_connections = nil + least_connections = nil + for up in *upstreams + if most_connections == nil or up['connections'] > most_connections + most_connections = up['connections'] + if least_connections == nil or up['connections'] < least_connections + least_connections = up['connections'] + available_upstreams = [ up for up in *upstreams when up['connections'] < most_connections] + if #available_upstreams > 0 + available_connections = 0 -- available connections to match highest connection count + for x in *available_upstreams + available_connections += (most_connections - x['connections']) + rand = math.random( available_connections ) + offset = 0 + for up in *available_upstreams + if rand <= (most_connections - up['connections'] + offset) + upstream = up['backend'] + break + offset += (most_connections - up['connections']) + if upstream == nil and #upstreams > 0 + -- if least-connections fails to find a backend fallback to pick one randomly + upstream = upstreams[ math.random( #upstreams ) ]['backend'] + else + -- choose random upstream + upstream = upstreams[ math.random( #upstreams ) ]['backend'] M.finish(red) if type(upstream) == 'string' if config.stickiness > 0 From b4c7f7ba83681a7bf28dc35ef054d187a11844ee Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Fri, 10 Oct 2014 16:19:22 -0400 Subject: [PATCH 2/9] refactor backend datastore to use sorted sets --- lua/src/bin/init_redx.lua | 1 - lua/src/lib/redis.lua | 34 +++++++++++++++++++++++++++++----- src/bin/init_redx.moon | 2 +- src/lib/redis.moon | 15 ++++++++++----- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lua/src/bin/init_redx.lua b/lua/src/bin/init_redx.lua index e46b9c1..5945075 100644 --- a/lua/src/bin/init_redx.lua +++ b/lua/src/bin/init_redx.lua @@ -4,5 +4,4 @@ library = require('library') inspect = require('inspect') socket = require('socket') math.randomseed(socket.gettime() * 1000) -library.log(socket.gettime() * 1000) return library.log('Redis host: ' .. config.redis_host .. ':' .. config.redis_port) diff --git a/lua/src/lib/redis.lua b/lua/src/lib/redis.lua index ef8e92f..ca68193 100644 --- a/lua/src/lib/redis.lua +++ b/lua/src/lib/redis.lua @@ -98,7 +98,31 @@ M.get_data = function(self, asset_type, asset_name) self.resp = nil end elseif 'backends' == _exp_0 then - self.resp, self.msg = red:smembers('backend:' .. asset_name) + local rawdata + rawdata, self.msg = red:zrangebyscore('backend:' .. asset_name, '-inf', '+inf', 'withscores') + local data = { } + self.resp = { } + do + local _tbl_0 = { } + for i, item in ipairs(rawdata) do + if i % 2 > 0 then + _tbl_0[item] = rawdata[i + 1] + end + end + data = _tbl_0 + end + do + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(data) do + if k:sub(1, 1) ~= "_" then + _accum_0[_len_0] = k + _len_0 = _len_0 + 1 + end + end + self.resp = _accum_0 + end + library.log_err(inspect(data)) if type(self.resp) == 'table' and table.getn(self.resp) == 0 then self.resp = nil end @@ -143,7 +167,7 @@ M.save_data = function(self, asset_type, asset_name, asset_value, overwrite) if overwrite then red:del('backend:' .. asset_name) end - local ok, err = red:sadd('backend:' .. asset_name, asset_value) + local ok, err = red:zadd('backend:' .. asset_name, 0, asset_value) if overwrite then M.commit(self, red, "Failed to save backend: ") end @@ -187,7 +211,7 @@ M.delete_data = function(self, asset_type, asset_name, asset_value) resp, self.msg = red:del('backend:' .. asset_name) else local resp - resp, self.msg = red:srem('backend:' .. asset_name, asset_value) + resp, self.msg = red:zrem('backend:' .. asset_name, asset_value) end else self.status = 400 @@ -251,7 +275,7 @@ M.save_batch_data = function(self, data, overwrite) local server = _list_1[_index_1] if not (server == nil) then library.log('adding backend: ' .. backend["name"] .. ' ' .. server) - red:sadd('backend:' .. backend["name"], server) + red:zadd('backend:' .. backend["name"], 0, server) end end end @@ -291,7 +315,7 @@ M.delete_batch_data = function(self, data) local server = _list_1[_index_1] if not (server == nil) then library.log('deleting backend: ' .. backend["name"] .. ' ' .. server) - red:srem('backend:' .. backend["name"], server) + red:zrem('backend:' .. backend["name"], server) end end end diff --git a/src/bin/init_redx.moon b/src/bin/init_redx.moon index 06fa1a5..966f3c4 100644 --- a/src/bin/init_redx.moon +++ b/src/bin/init_redx.moon @@ -6,5 +6,5 @@ export socket = require 'socket' -- seed math.random math.randomseed(socket.gettime! * 1000) -library.log(socket.gettime! * 1000) + library.log('Redis host: ' .. config.redis_host .. ':' .. config.redis_port) diff --git a/src/lib/redis.moon b/src/lib/redis.moon index 0bc8687..8b190b7 100644 --- a/src/lib/redis.moon +++ b/src/lib/redis.moon @@ -84,7 +84,12 @@ M.get_data = (@, asset_type, asset_name) -> if getmetatable(@resp) == nil @resp = nil when 'backends' - @resp, @msg = red\smembers('backend:' .. asset_name) + rawdata, @msg = red\zrangebyscore('backend:' .. asset_name, '-inf', '+inf', 'withscores') + data = {} + @resp = {} + data = {item,rawdata[i+1] for i, item in ipairs rawdata when i % 2 > 0} + @resp = [ k for k,v in pairs data when k\sub(1,1) != "_"] + library.log_err(inspect(data)) @resp = nil if type(@resp) == 'table' and table.getn(@resp) == 0 else @status = 400 @@ -111,7 +116,7 @@ M.save_data = (@, asset_type, asset_name, asset_value, overwrite=false) -> red = M.connect(@) red\init_pipeline() if overwrite red\del('backend:' .. asset_name) if overwrite - ok, err = red\sadd('backend:' .. asset_name, asset_value) + ok, err = red\zadd('backend:' .. asset_name, 0, asset_value) M.commit(@, red, "Failed to save backend: ") if overwrite else ok = false @@ -138,7 +143,7 @@ M.delete_data = (@, asset_type, asset_name, asset_value=nil) -> if asset_value == nil resp, @msg = red\del('backend:' .. asset_name) else - resp, @msg = red\srem('backend:' .. asset_name, asset_value) + resp, @msg = red\zrem('backend:' .. asset_name, asset_value) else @status = 400 @msg = 'Bad asset type. Must be "frontends" or "backends"' @@ -170,7 +175,7 @@ M.save_batch_data = (@, data, overwrite=false) -> for server in *backend['servers'] unless server == nil library.log('adding backend: ' .. backend["name"] .. ' ' .. server) - red\sadd('backend:' .. backend["name"], server) + red\zadd('backend:' .. backend["name"], 0, server) M.commit(@, red, "failed to save data: ") M.finish(red) @@ -191,7 +196,7 @@ M.delete_batch_data = (@, data) -> for server in *backend['servers'] unless server == nil library.log('deleting backend: ' .. backend["name"] .. ' ' .. server) - red\srem('backend:' .. backend["name"], server) + red\zrem('backend:' .. backend["name"], server) M.commit(@, red, "failed to save data: ") M.finish(red) From 5c90084e795937503a46a328e7e4e6ac206b0724 Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Fri, 10 Oct 2014 18:23:32 -0400 Subject: [PATCH 3/9] get/set backend configs and set backend connections --- README.md | 27 +++++++++++++++++ lua/spec/api_spec.lua | 11 ++++++- lua/src/bin/api.lua | 31 ++++++++++++++++++-- lua/src/lib/library.lua | 5 ++++ lua/src/lib/redis.lua | 64 +++++++++++++++++++++++++++++++++++++++-- spec/api_spec.moon | 8 ++++++ src/bin/api.moon | 22 ++++++++++++-- src/lib/library.moon | 3 ++ src/lib/redis.moon | 48 +++++++++++++++++++++++++++---- 9 files changed, 206 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 80eff4c..0219adf 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,33 @@ curl -X DELETE localhost:8081/backends/mybackend curl -X DELETE localhost:8081/backends/mybackend/google.com%3A80 ``` +### (PUT) /backends/\/\/connections/\ + +The `backend connections` endpoint allows you to update the number of connections a backend has. This data is used by the `least-connections` load balancing algorithm to probabilistically send incoming requests to the most probably backend with the least connections. + +#### Examples + +##### `PUT` example +``` +curl -X PUT localhost:8081/backends/mybackend/google.com%3A80/31 +``` + +### (GET|PUT) /backends/\/\/config/\/\ + +The `backend configuration` endpoint allows you to get, update, or replace a backend config. Be sure to character escape as needed. + +#### Examples + +##### `GET` example +``` +curl localhost:8081/backends/mybackend/config/_max_connections/30 +``` + +##### `PUT` example +``` +curl -X POST localhost:8081/backends/mybackend/config/_max_connections +``` + ### (DELETE) /flush Flush clears the redis database of all data. Its literally runs the [`FLUSHDB`](http://redis.io/commands/flushdb) command within redis. diff --git a/lua/spec/api_spec.lua b/lua/spec/api_spec.lua index 1e15d5b..db405cb 100644 --- a/lua/spec/api_spec.lua +++ b/lua/spec/api_spec.lua @@ -283,7 +283,7 @@ return describe("redx_api", function() } }) end) - return it("should delete orphans #orphans_api", function() + it("should delete orphans #orphans_api", function() local response, code, headers = make_json_request("/backends/5555/" .. tostring(escape('rstudio.com:80')), "POST") assert.same(200, code) response, code, headers = make_json_request("/backends/5555/" .. tostring(escape('rstudio.com:80')), "POST") @@ -309,4 +309,13 @@ return describe("redx_api", function() response, code, headers = make_json_request("/frontends/" .. tostring(escape('foobar.com/path')), "GET") return assert.same(404, code) end) + return it("should create backend config #config_api", function() + local response, code, headers = make_json_request("/backends/5555/config/limit/5", "PUT") + assert.same(200, code) + response, code, headers = make_json_request("/backends/5555/config/limit", "GET") + assert.same(200, code) + return assert.same(response['data'], { + limit = '5' + }) + end) end) diff --git a/lua/src/bin/api.lua b/lua/src/bin/api.lua index dc1eb75..bc84ff7 100644 --- a/lua/src/bin/api.lua +++ b/lua/src/bin/api.lua @@ -114,16 +114,34 @@ do } end }), + ['/backends/:name/config/:config'] = respond_to({ + GET = function(self) + redis.get_config(self, unescape(self.params.name), unescape(self.params.config)) + return { + status = self.status, + json = json_response(self) + } + end + }), + ['/backends/:name/config/:config/:value'] = respond_to({ + PUT = function(self) + redis.set_config(self, unescape(self.params.name), unescape(self.params.config), unescape(self.params.value)) + return { + status = self.status, + json = json_response(self) + } + end + }), ['/:type/:name/:value'] = respond_to({ POST = function(self) - redis.save_data(self, self.params.type, unescape(self.params.name), unescape(self.params.value), false) + redis.save_data(self, self.params.type, unescape(self.params.name), unescape(self.params.value), 0, false) return { status = self.status, json = json_response(self) } end, PUT = function(self) - redis.save_data(self, self.params.type, unescape(self.params.name), unescape(self.params.value), true) + redis.save_data(self, self.params.type, unescape(self.params.name), unescape(self.params.value), 0, true) return { status = self.status, json = json_response(self) @@ -136,6 +154,15 @@ do json = json_response(self) } end + }), + ['/:type/:name/:value/connections/:connections'] = respond_to({ + PUT = function(self) + redis.save_data(self, self.params.type, unescape(self.params.name), unescape(self.params.value), unescape(self.params.connections), true) + return { + status = self.status, + json = json_response(self) + } + end }) } _base_0.__index = _base_0 diff --git a/lua/src/lib/library.lua b/lua/src/lib/library.lua index f290fe5..0ec8495 100644 --- a/lua/src/lib/library.lua +++ b/lua/src/lib/library.lua @@ -34,4 +34,9 @@ M.Set = function(list) end return set end +M.multirequest = function(reqs) + return { + ngx.location.capture_multi(reqs) + } +end return M diff --git a/lua/src/lib/redis.lua b/lua/src/lib/redis.lua index ca68193..200a231 100644 --- a/lua/src/lib/redis.lua +++ b/lua/src/lib/redis.lua @@ -83,6 +83,62 @@ M.flush = function(self) end return M.finish(red) end +M.get_config = function(self, asset_name, config) + local red = M.connect(self) + if red == nil then + return nil + end + local config_value + config_value, self.msg = red:zscore('backend:' .. asset_name, '_' .. config) + if config_value == nil then + self.resp = nil + else + self.resp = { + [config] = config_value + } + end + if self.resp then + self.status = 200 + self.msg = "OK" + end + if self.resp == nil then + self.status = 404 + self.msg = "Entry does not exist" + else + if not (self.status) then + self.status = 500 + end + if not (self.msg) then + self.msg = 'Unknown failutre' + end + library.log(self.msg) + end + return M.finish(red) +end +M.set_config = function(self, asset_name, config, value) + local red = M.connect(self) + if red == nil then + return nil + end + library.log(asset_name) + library.log(config) + library.log(value) + local ok, err = red:zadd('backend:' .. asset_name, value, '_' .. config) + library.log(ok) + library.log(err) + if ok >= 0 then + self.status = 200 + self.msg = "OK" + else + self.status = 500 + if err == nil then + err = "unknown" + end + self.msg = "Failed to save backend config: " .. err + library.log_err(self.msg) + end + return M.finish(red) +end M.get_data = function(self, asset_type, asset_name) local red = M.connect(self) if red == nil then @@ -122,7 +178,6 @@ M.get_data = function(self, asset_type, asset_name) end self.resp = _accum_0 end - library.log_err(inspect(data)) if type(self.resp) == 'table' and table.getn(self.resp) == 0 then self.resp = nil end @@ -148,7 +203,10 @@ M.get_data = function(self, asset_type, asset_name) end return M.finish(red) end -M.save_data = function(self, asset_type, asset_name, asset_value, overwrite) +M.save_data = function(self, asset_type, asset_name, asset_value, connections, overwrite) + if connections == nil then + connections = 0 + end if overwrite == nil then overwrite = false end @@ -167,7 +225,7 @@ M.save_data = function(self, asset_type, asset_name, asset_value, overwrite) if overwrite then red:del('backend:' .. asset_name) end - local ok, err = red:zadd('backend:' .. asset_name, 0, asset_value) + local ok, err = red:zadd('backend:' .. asset_name, connections, asset_value) if overwrite then M.commit(self, red, "Failed to save backend: ") end diff --git a/spec/api_spec.moon b/spec/api_spec.moon index 9e22003..4338075 100644 --- a/spec/api_spec.moon +++ b/spec/api_spec.moon @@ -262,3 +262,11 @@ describe "redx_api", -> assert.same 404, code response, code, headers = make_json_request("/frontends/#{escape('foobar.com/path')}", "GET") assert.same 404, code + + it "should create backend config #config_api", -> + response, code, headers = make_json_request("/backends/5555/config/limit/5", "PUT") + assert.same 200, code + + response, code, headers = make_json_request("/backends/5555/config/limit", "GET") + assert.same 200, code + assert.same response['data'], { limit: '5' } diff --git a/src/bin/api.moon b/src/bin/api.moon index 4c99e80..cd2734a 100644 --- a/src/bin/api.moon +++ b/src/bin/api.moon @@ -59,16 +59,34 @@ webserver = class extends lapis.Application status: @status, json: json_response(@) } + '/backends/:name/config/:config': respond_to { + GET: => + redis.get_config(@, unescape(@params.name), unescape(@params.config)) + status: @status, json: json_response(@) + } + + '/backends/:name/config/:config/:value': respond_to { + PUT: => + redis.set_config(@, unescape(@params.name), unescape(@params.config), unescape(@params.value)) + status: @status, json: json_response(@) + } + '/:type/:name/:value': respond_to { POST: => - redis.save_data(@, @params.type, unescape(@params.name), unescape(@params.value), false) + redis.save_data(@, @params.type, unescape(@params.name), unescape(@params.value), 0, false) status: @status, json: json_response(@) PUT: => - redis.save_data(@, @params.type, unescape(@params.name), unescape(@params.value), true) + redis.save_data(@, @params.type, unescape(@params.name), unescape(@params.value), 0, true) status: @status, json: json_response(@) DELETE: => redis.delete_data(@, @params.type, unescape(@params.name), unescape(@params.value)) status: @status, json: json_response(@) } + '/:type/:name/:value/connections/:connections': respond_to { + PUT: => + redis.save_data(@, @params.type, unescape(@params.name), unescape(@params.value), unescape(@params.connections), true) + status: @status, json: json_response(@) + } + lapis.serve(webserver) diff --git a/src/lib/library.moon b/src/lib/library.moon index 94d32ec..d3c8f7e 100644 --- a/src/lib/library.moon +++ b/src/lib/library.moon @@ -24,4 +24,7 @@ M.Set = (list) -> set[l] = true return set +M.multirequest = (reqs) -> + return { ngx.location.capture_multi(reqs) } + return M diff --git a/src/lib/redis.moon b/src/lib/redis.moon index 8b190b7..9508286 100644 --- a/src/lib/redis.moon +++ b/src/lib/redis.moon @@ -73,7 +73,46 @@ M.flush = (@) -> @msg = err library.log_err(err) M.finish(red) - + +M.get_config = (@, asset_name, config) -> + red = M.connect(@) + return nil if red == nil + config_value, @msg = red\zscore('backend:' .. asset_name, '_' .. config) + if config_value == nil + @resp = nil + else + @resp = { [config]: config_value } + if @resp + @status = 200 + @msg = "OK" + if @resp == nil + @status = 404 + @msg = "Entry does not exist" + else + @status = 500 unless @status + @msg = 'Unknown failutre' unless @msg + library.log(@msg) + M.finish(red) + +M.set_config = (@, asset_name, config, value) -> + red = M.connect(@) + return nil if red == nil + library.log(asset_name) + library.log(config) + library.log(value) + ok, err = red\zadd('backend:' .. asset_name, value, '_' .. config) + library.log(ok) + library.log(err) + if ok >= 0 + @status = 200 + @msg = "OK" + else + @status = 500 + err = "unknown" if err == nil + @msg = "Failed to save backend config: " .. err + library.log_err(@msg) + M.finish(red) + M.get_data = (@, asset_type, asset_name) -> red = M.connect(@) return nil if red == nil @@ -89,7 +128,6 @@ M.get_data = (@, asset_type, asset_name) -> @resp = {} data = {item,rawdata[i+1] for i, item in ipairs rawdata when i % 2 > 0} @resp = [ k for k,v in pairs data when k\sub(1,1) != "_"] - library.log_err(inspect(data)) @resp = nil if type(@resp) == 'table' and table.getn(@resp) == 0 else @status = 400 @@ -106,7 +144,7 @@ M.get_data = (@, asset_type, asset_name) -> library.log(@msg) M.finish(red) -M.save_data = (@, asset_type, asset_name, asset_value, overwrite=false) -> +M.save_data = (@, asset_type, asset_name, asset_value, connections=0, overwrite=false) -> red = M.connect(@) return nil if red == nil switch asset_type @@ -116,7 +154,7 @@ M.save_data = (@, asset_type, asset_name, asset_value, overwrite=false) -> red = M.connect(@) red\init_pipeline() if overwrite red\del('backend:' .. asset_name) if overwrite - ok, err = red\zadd('backend:' .. asset_name, 0, asset_value) + ok, err = red\zadd('backend:' .. asset_name, connections, asset_value) M.commit(@, red, "Failed to save backend: ") if overwrite else ok = false @@ -290,7 +328,7 @@ M.fetch_server = (@, backend_key) -> break offset += (most_connections - up['connections']) if upstream == nil and #upstreams > 0 - -- if least-connections fails to find a backend fallback to pick one randomly + -- if least-connections fails to find a backend, fallback to pick one randomly upstream = upstreams[ math.random( #upstreams ) ]['backend'] else -- choose random upstream From e84b8f9a44303c3bfdcad45e4884416be558c7d1 Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Tue, 14 Oct 2014 11:38:42 -0400 Subject: [PATCH 4/9] Refactor to use score instead of specifically connections --- README.md | 19 +++++-- conf/config.moon | 4 +- lua/conf/config.lua | 2 +- lua/spec/api_spec.lua | 6 ++- lua/src/bin/api.lua | 18 +++---- lua/src/lib/redis.lua | 116 ++++++++++++++++++++++++++++-------------- spec/api_spec.moon | 4 ++ src/bin/api.moon | 12 ++--- src/lib/redis.moon | 83 +++++++++++++++++++----------- 9 files changed, 171 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 0219adf..8a08b93 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,17 @@ Currently max_path_length must be a minimum of 1, but that will change in the fu ##### stickiness The amount of time (in seconds) you wish the session to be "sticky", and consistently use the same upstream server. If you wish to disable "stickiness", set value to 0 (zero). +##### balance\_algorithm +The load balancing algorithm you want to use to balance traffic to your backends. The options are `least-score`, `most-score`, and `random`. `Random` is the default. + +Load Balancing Algorithms +========================= + +Redx has a few options for how to load balance to various backends. The default, is `random` which works exactly how you would imagine. The other options are `least-score` and `most-score`. +For `least-score` and `most-score`, associated with each backend, is a score. This score number is arbitrary, and can be whatever you want it to be. It can represent the number of connections a backend has, the amount of cpu or memory a backend is using, or something custom to your application like number of threads. +Each set of backends can be configured to have a maximum score (ie max number of connections, CPU usage, max number of threads, etc). This maximum value is used in evaluating which backend traffic is sent to. It is important to note, that due to the score values are assumed not to be realtime, we use a probabilistic approach to routing traffic. This is so we don't send all traffic to a single backend in between each update of the score value. So efforts to balance traffic is "best efforts" and are **NOT** guarenteed. Similar to a casino, while you may statistically loose some money sometimes, eventually the house always wins. +Load balancing does **NOT** override stickiness. If you have stickiness enabled, it is honored while a stickiness session exists. But new traffic, aka traffic that doesn't have an active stickiness session, are load balanced according to the algorithm chosen. + API === @@ -134,9 +145,9 @@ curl -X DELETE localhost:8081/backends/mybackend curl -X DELETE localhost:8081/backends/mybackend/google.com%3A80 ``` -### (PUT) /backends/\/\/connections/\ +### (PUT) /backends/\/\/score/\ -The `backend connections` endpoint allows you to update the number of connections a backend has. This data is used by the `least-connections` load balancing algorithm to probabilistically send incoming requests to the most probably backend with the least connections. +The `backend score` endpoint allows you to update the score a backend has. This score is used by the `least-score` and `most-score` load balancing algorithm to probabilistically send incoming requests to the most probably backend with the least or most score. #### Examples @@ -153,12 +164,12 @@ The `backend configuration` endpoint allows you to get, update, or replace a bac ##### `GET` example ``` -curl localhost:8081/backends/mybackend/config/_max_connections/30 +curl localhost:8081/backends/mybackend/config/_max_score/30 ``` ##### `PUT` example ``` -curl -X POST localhost:8081/backends/mybackend/config/_max_connections +curl -X POST localhost:8081/backends/mybackend/config/_max_score ``` ### (DELETE) /flush diff --git a/conf/config.moon b/conf/config.moon index ca2f2c0..ed26c92 100644 --- a/conf/config.moon +++ b/conf/config.moon @@ -31,6 +31,6 @@ M.max_path_length = 1 M.stickiness = 0 -- Load Balancing Algorithm --- "random" or "least-connections" -M.balance_algorithm = 'least-connections' +-- "random" or "least-score" or "most-score" +M.balance_algorithm = 'least-score' return M diff --git a/lua/conf/config.lua b/lua/conf/config.lua index 22ccc67..f1b150e 100644 --- a/lua/conf/config.lua +++ b/lua/conf/config.lua @@ -7,5 +7,5 @@ M.redis_keepalive_pool_size = 0 M.redis_keepalive_max_idle_timeout = 10000 M.max_path_length = 1 M.stickiness = 0 -M.balance_algorithm = 'least-connections' +M.balance_algorithm = 'least-score' return M diff --git a/lua/spec/api_spec.lua b/lua/spec/api_spec.lua index db405cb..3f06372 100644 --- a/lua/spec/api_spec.lua +++ b/lua/spec/api_spec.lua @@ -309,7 +309,7 @@ return describe("redx_api", function() response, code, headers = make_json_request("/frontends/" .. tostring(escape('foobar.com/path')), "GET") return assert.same(404, code) end) - return it("should create backend config #config_api", function() + it("should create backend config #config_api", function() local response, code, headers = make_json_request("/backends/5555/config/limit/5", "PUT") assert.same(200, code) response, code, headers = make_json_request("/backends/5555/config/limit", "GET") @@ -318,4 +318,8 @@ return describe("redx_api", function() limit = '5' }) end) + return it("Create backend and set score #score_api", function() + local response, code, headers = make_json_request("/backends/5555/" .. tostring(escape('rstudio.com:80')) .. "/score/30", "PUT") + return assert.same(200, code) + end) end) diff --git a/lua/src/bin/api.lua b/lua/src/bin/api.lua index bc84ff7..a8789b3 100644 --- a/lua/src/bin/api.lua +++ b/lua/src/bin/api.lua @@ -132,6 +132,15 @@ do } end }), + ['/backends/:name/:value/score/:score'] = respond_to({ + PUT = function(self) + redis.save_data(self, 'backends', unescape(self.params.name), unescape(self.params.value), unescape(self.params.score), false) + return { + status = self.status, + json = json_response(self) + } + end + }), ['/:type/:name/:value'] = respond_to({ POST = function(self) redis.save_data(self, self.params.type, unescape(self.params.name), unescape(self.params.value), 0, false) @@ -154,15 +163,6 @@ do json = json_response(self) } end - }), - ['/:type/:name/:value/connections/:connections'] = respond_to({ - PUT = function(self) - redis.save_data(self, self.params.type, unescape(self.params.name), unescape(self.params.value), unescape(self.params.connections), true) - return { - status = self.status, - json = json_response(self) - } - end }) } _base_0.__index = _base_0 diff --git a/lua/src/lib/redis.lua b/lua/src/lib/redis.lua index 200a231..284b9bb 100644 --- a/lua/src/lib/redis.lua +++ b/lua/src/lib/redis.lua @@ -203,9 +203,9 @@ M.get_data = function(self, asset_type, asset_name) end return M.finish(red) end -M.save_data = function(self, asset_type, asset_name, asset_value, connections, overwrite) - if connections == nil then - connections = 0 +M.save_data = function(self, asset_type, asset_name, asset_value, score, overwrite) + if score == nil then + score = 0 end if overwrite == nil then overwrite = false @@ -225,7 +225,7 @@ M.save_data = function(self, asset_type, asset_name, asset_value, connections, o if overwrite then red:del('backend:' .. asset_name) end - local ok, err = red:zadd('backend:' .. asset_name, connections, asset_value) + local ok, err = red:zadd('backend:' .. asset_name, score, asset_value) if overwrite then M.commit(self, red, "Failed to save backend: ") end @@ -457,7 +457,7 @@ M.fetch_server = function(self, backend_key) if k:sub(1, 1) ~= "_" then _accum_0[_len_0] = { backend = k, - connections = tonumber(v) + score = tonumber(v) } _len_0 = _len_0 + 1 end @@ -478,62 +478,100 @@ M.fetch_server = function(self, backend_key) library.log_err('Only one backend, choosing it') upstream = upstreams[1]['backend'] else - if config.balance_algorithm == 'least-connections' then + if config.balance_algorithm == 'least-score' or config.balance_algorithm == 'most-score' then if #upstreams == 2 then - local max_connections = tonumber(backend_config['_max_connections']) - if not (max_connections == nil) then - local available_connections = 0 + local max_score = tonumber(backend_config['_max_score']) + if not (max_score == nil) then + local available_score = 0 for _index_0 = 1, #upstreams do local x = upstreams[_index_0] - available_connections = available_connections + (max_connections - x['connections']) + if config.balance_algorithm == 'least-score' then + available_score = available_score + (max_score - x['score']) + else + available_score = available_score + x['score'] + end end - local rand = math.random(1, available_connections) - if rand <= (max_connections - upstreams[1]['connections']) then - upstream = upstreams[1]['backend'] + local rand = math.random(1, available_score) + if config.balance_algorithm == 'least-score' then + if rand <= (max_score - upstreams[1]['score']) then + upstream = upstreams[1]['backend'] + else + upstream = upstreams[2]['backend'] + end else - upstream = upstreams[2]['backend'] + if rand <= (upstreams[1]['score']) then + upstream = upstreams[1]['backend'] + else + upstream = upstreams[2]['backend'] + end end end else - local most_connections = nil - local least_connections = nil + local most_score = nil + local least_score = nil for _index_0 = 1, #upstreams do local up = upstreams[_index_0] - if most_connections == nil or up['connections'] > most_connections then - most_connections = up['connections'] + if most_score == nil or up['score'] > most_score then + most_score = up['score'] end - if least_connections == nil or up['connections'] < least_connections then - least_connections = up['connections'] + if least_score == nil or up['score'] < least_score then + least_score = up['score'] end end - local available_upstreams - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #upstreams do - local up = upstreams[_index_0] - if up['connections'] < most_connections then - _accum_0[_len_0] = up - _len_0 = _len_0 + 1 + if config.balance_algorithm == 'least-score' then + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #upstreams do + local up = upstreams[_index_0] + if up['score'] < most_score then + _accum_0[_len_0] = up + _len_0 = _len_0 + 1 + end end + available_upstreams = _accum_0 + end + else + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #upstreams do + local up = upstreams[_index_0] + if up['score'] > least_score then + _accum_0[_len_0] = up + _len_0 = _len_0 + 1 + end + end + available_upstreams = _accum_0 end - available_upstreams = _accum_0 end if #available_upstreams > 0 then - local available_connections = 0 - for _index_0 = 1, #available_upstreams do - local x = available_upstreams[_index_0] - available_connections = available_connections + (most_connections - x['connections']) + local available_score = 0 + local _list_0 = available_upstreams + for _index_0 = 1, #_list_0 do + local x = _list_0[_index_0] + if config.balance_algorithm == 'least-score' then + available_score = available_score + (most_score - x['score']) + else + available_score = available_score + x['score'] + end end - local rand = math.random(available_connections) + local rand = math.random(available_score) local offset = 0 - for _index_0 = 1, #available_upstreams do - local up = available_upstreams[_index_0] - if rand <= (most_connections - up['connections'] + offset) then + local _list_1 = available_upstreams + for _index_0 = 1, #_list_1 do + local up = _list_1[_index_0] + local value = 0 + if config.balance_algorithm == 'least-score' then + value = (most_score - up['score']) + else + value = up['score'] + end + if rand <= (value + offset) then upstream = up['backend'] break end - offset = offset + (most_connections - up['connections']) + offset = offset + value end end end diff --git a/spec/api_spec.moon b/spec/api_spec.moon index 4338075..77a2da9 100644 --- a/spec/api_spec.moon +++ b/spec/api_spec.moon @@ -270,3 +270,7 @@ describe "redx_api", -> response, code, headers = make_json_request("/backends/5555/config/limit", "GET") assert.same 200, code assert.same response['data'], { limit: '5' } + + it "Create backend and set score #score_api", -> + response, code, headers = make_json_request("/backends/5555/#{escape('rstudio.com:80')}/score/30", "PUT") + assert.same 200, code diff --git a/src/bin/api.moon b/src/bin/api.moon index cd2734a..abd38d2 100644 --- a/src/bin/api.moon +++ b/src/bin/api.moon @@ -71,6 +71,12 @@ webserver = class extends lapis.Application status: @status, json: json_response(@) } + '/backends/:name/:value/score/:score': respond_to { + PUT: => + redis.save_data(@, 'backends', unescape(@params.name), unescape(@params.value), unescape(@params.score), false) + status: @status, json: json_response(@) + } + '/:type/:name/:value': respond_to { POST: => redis.save_data(@, @params.type, unescape(@params.name), unescape(@params.value), 0, false) @@ -83,10 +89,4 @@ webserver = class extends lapis.Application status: @status, json: json_response(@) } - '/:type/:name/:value/connections/:connections': respond_to { - PUT: => - redis.save_data(@, @params.type, unescape(@params.name), unescape(@params.value), unescape(@params.connections), true) - status: @status, json: json_response(@) - } - lapis.serve(webserver) diff --git a/src/lib/redis.moon b/src/lib/redis.moon index 9508286..eaa1c0c 100644 --- a/src/lib/redis.moon +++ b/src/lib/redis.moon @@ -144,7 +144,7 @@ M.get_data = (@, asset_type, asset_name) -> library.log(@msg) M.finish(red) -M.save_data = (@, asset_type, asset_name, asset_value, connections=0, overwrite=false) -> +M.save_data = (@, asset_type, asset_name, asset_value, score=0, overwrite=false) -> red = M.connect(@) return nil if red == nil switch asset_type @@ -154,7 +154,7 @@ M.save_data = (@, asset_type, asset_name, asset_value, connections=0, overwrite= red = M.connect(@) red\init_pipeline() if overwrite red\del('backend:' .. asset_name) if overwrite - ok, err = red\zadd('backend:' .. asset_name, connections, asset_value) + ok, err = red\zadd('backend:' .. asset_name, score, asset_value) M.commit(@, red, "Failed to save backend: ") if overwrite else ok = false @@ -281,7 +281,7 @@ M.fetch_server = (@, backend_key) -> data = {item,rawdata[i+1] for i, item in ipairs rawdata when i % 2 > 0} --split backends from config data upstreams = {} - upstreams = [{ backend:k, connections: tonumber(v)} for k,v in pairs data when k\sub(1,1) != "_"] + upstreams = [{ backend:k, score: tonumber(v)} for k,v in pairs data when k\sub(1,1) != "_"] backend_config = {} backend_config = {k,v for k,v in pairs data when k\sub(1,1) == "_"} if #upstreams == 1 @@ -289,46 +289,67 @@ M.fetch_server = (@, backend_key) -> library.log_err('Only one backend, choosing it') upstream = upstreams[1]['backend'] else - if config.balance_algorithm == 'least-connections' - -- get least connection probability + if config.balance_algorithm == 'least-score' or config.balance_algorithm == 'most-score' + -- get least/most connection probability if #upstreams == 2 - -- get least connection probability relative to max connections - max_connections = tonumber(backend_config['_max_connections']) - unless max_connections == nil - -- get total number of available connections - available_connections = 0 + -- get least/most connection probability relative to max score + max_score = tonumber(backend_config['_max_score']) + unless max_score == nil + -- get total number of available score + available_score = 0 for x in *upstreams - available_connections += (max_connections - x['connections']) - -- pick random number within total available connections - rand = math.random( 1, available_connections ) - if rand <= (max_connections - upstreams[1]['connections']) - upstream = upstreams[1]['backend'] + if config.balance_algorithm == 'least-score' + available_score += (max_score - x['score']) + else + available_score += x['score'] + -- pick random number within total available score + rand = math.random( 1, available_score ) + if config.balance_algorithm == 'least-score' + if rand <= (max_score - upstreams[1]['score']) + upstream = upstreams[1]['backend'] + else + upstream = upstreams[2]['backend'] else - upstream = upstreams[2]['backend'] + if rand <= (upstreams[1]['score']) + upstream = upstreams[1]['backend'] + else + upstream = upstreams[2]['backend'] + else - -- get least connection probability relative to larger connections - -- get largest and least number of connections - most_connections = nil - least_connections = nil + -- get least connection probability relative to larger score + -- get largest and least number of score + most_score = nil + least_score = nil for up in *upstreams - if most_connections == nil or up['connections'] > most_connections - most_connections = up['connections'] - if least_connections == nil or up['connections'] < least_connections - least_connections = up['connections'] - available_upstreams = [ up for up in *upstreams when up['connections'] < most_connections] + if most_score == nil or up['score'] > most_score + most_score = up['score'] + if least_score == nil or up['score'] < least_score + least_score = up['score'] + if config.balance_algorithm == 'least-score' + export available_upstreams = [ up for up in *upstreams when up['score'] < most_score ] + else + export available_upstreams = [ up for up in *upstreams when up['score'] > least_score ] if #available_upstreams > 0 - available_connections = 0 -- available connections to match highest connection count + available_score = 0 -- available score to match highest connection count for x in *available_upstreams - available_connections += (most_connections - x['connections']) - rand = math.random( available_connections ) + if config.balance_algorithm == 'least-score' + available_score += (most_score - x['score']) + else + available_score += x['score'] + rand = math.random( available_score ) offset = 0 for up in *available_upstreams - if rand <= (most_connections - up['connections'] + offset) + value = 0 + if config.balance_algorithm == 'least-score' + value = (most_score - up['score']) + else + value = up['score'] + if rand <= (value + offset) upstream = up['backend'] break - offset += (most_connections - up['connections']) + offset += value if upstream == nil and #upstreams > 0 - -- if least-connections fails to find a backend, fallback to pick one randomly + -- if least-score fails to find a backend, fallback to pick one randomly upstream = upstreams[ math.random( #upstreams ) ]['backend'] else -- choose random upstream From f64114df9545cd760845f74e07ea98cc6f12e336 Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Tue, 14 Oct 2014 11:49:57 -0400 Subject: [PATCH 5/9] use specific version of lapis for travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 24b7555..159ff7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ install: - sudo luarocks install luasec OPENSSL_DIR=/usr OPENSSL_LIBDIR=/usr/lib/x86_64-linux-gnu - sudo luarocks install luafilesystem - sudo luarocks install busted - - sudo luarocks install lapis + - sudo luarocks install lapis 1.0.4-1 - sudo luarocks install moonscript - sudo luarocks install inspect - wget http://openresty.org/download/ngx_openresty-1.7.2.1.tar.gz From 23895d601c0c493a04993403542c2ee6e0572d83 Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Tue, 14 Oct 2014 12:01:18 -0400 Subject: [PATCH 6/9] [travis] only test master branch --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 159ff7a..974766a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,10 @@ env: services: - redis-server +branches: + only: + - master + install: - sudo apt-get update - sudo apt-get install luajit luarocks From 9cb436e490f234e1765e778cc1256f0f7ae48111 Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Tue, 14 Oct 2014 12:04:48 -0400 Subject: [PATCH 7/9] Update readme [skip ci] --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8a08b93..0e2d9c2 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ At [rstudio](http://www.rstudio.com/), we find that redx performs slightly slowe Requirements ============ +[lapis](http://leafo.net/lapis/) version 1.0.4-1 + [openresty](http://openresty.org/) 1.7.2 or greater A [redis](http://redis.io/) server From 0cbe4c9424dde4ebb2c20b1aa12a5078e38f4c7c Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Tue, 14 Oct 2014 12:11:09 -0400 Subject: [PATCH 8/9] misc tweaks --- README.md | 2 +- conf/config.moon | 2 +- lua/conf/config.lua | 2 +- lua/src/lib/library.lua | 12 ------------ src/lib/library.moon | 9 --------- 5 files changed, 3 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 0e2d9c2..15506fd 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ The `backend score` endpoint allows you to update the score a backend has. This ##### `PUT` example ``` -curl -X PUT localhost:8081/backends/mybackend/google.com%3A80/31 +curl -X PUT localhost:8081/backends/mybackend/google.com%3A80/score/31 ``` ### (GET|PUT) /backends/\/\/config/\/\ diff --git a/conf/config.moon b/conf/config.moon index ed26c92..9a0b638 100644 --- a/conf/config.moon +++ b/conf/config.moon @@ -32,5 +32,5 @@ M.stickiness = 0 -- Load Balancing Algorithm -- "random" or "least-score" or "most-score" -M.balance_algorithm = 'least-score' +M.balance_algorithm = 'random' return M diff --git a/lua/conf/config.lua b/lua/conf/config.lua index f1b150e..5c34ad2 100644 --- a/lua/conf/config.lua +++ b/lua/conf/config.lua @@ -7,5 +7,5 @@ M.redis_keepalive_pool_size = 0 M.redis_keepalive_max_idle_timeout = 10000 M.max_path_length = 1 M.stickiness = 0 -M.balance_algorithm = 'least-score' +M.balance_algorithm = 'random' return M diff --git a/lua/src/lib/library.lua b/lua/src/lib/library.lua index 0ec8495..f7329f7 100644 --- a/lua/src/lib/library.lua +++ b/lua/src/lib/library.lua @@ -20,13 +20,6 @@ M.split = function(str, delim) end return _accum_0 end -M.length = function(dict) - local count = 0 - for k, v in pairs(dict) do - count = count + 1 - end - return count -end M.Set = function(list) local set = { } for _, l in ipairs(list) do @@ -34,9 +27,4 @@ M.Set = function(list) end return set end -M.multirequest = function(reqs) - return { - ngx.location.capture_multi(reqs) - } -end return M diff --git a/src/lib/library.moon b/src/lib/library.moon index d3c8f7e..cac7814 100644 --- a/src/lib/library.moon +++ b/src/lib/library.moon @@ -12,19 +12,10 @@ M.split = (str, delim using nil) -> str ..= delim [part for part in str\gmatch "(.-)" .. escape_pattern delim] -M.length = (dict) -> - count = 0 - for k,v in pairs dict - count += 1 - return count - M.Set = (list) -> set = {} for _, l in ipairs(list) do set[l] = true return set -M.multirequest = (reqs) -> - return { ngx.location.capture_multi(reqs) } - return M From 93917c22273bf51c3a81534524528abf48ce4c98 Mon Sep 17 00:00:00 2001 From: Chad Barraford Date: Tue, 14 Oct 2014 12:53:36 -0400 Subject: [PATCH 9/9] update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 638f773..0b28af5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========= +## 1.2.0 (2014-10-14) + +### Feature ++ [PR](https://github.com/rstudio/redx/pulls): Add ability to probabilistically load balance backends based on their score value (most or least) + ## 1.1.0 (2014-07-27) ### Feature