Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add redis and redis-cluster in limit-req #10874

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cfb8739
[2023-12-22][add health-check schema check]
theweakgod Dec 22, 2023
adce66f
[2023-12-22][add health-check schema check]
theweakgod Dec 22, 2023
622e0d3
Merge branch 'apache:master' into master
theweakgod Jan 9, 2024
326d40d
Merge branch 'master' of github.com:theweakgod/apisix
theweakgod Jan 9, 2024
b3391c7
Merge branch 'master' of github.com:theweakgod/apisix
theweakgod Jan 17, 2024
8fee37a
Merge branch 'master' of github.com:theweakgod/apisix
theweakgod Jan 24, 2024
efd7ce2
Merge branch 'master' of github.com:theweakgod/apisix
theweakgod Jan 29, 2024
a56f254
add redis and redis_cluster in limit.req
theweakgod Jan 29, 2024
edbc545
remove redundant code
theweakgod Jan 29, 2024
ef9b3de
remove redundant code
theweakgod Jan 29, 2024
6c5ad3f
add test cases
theweakgod Jan 29, 2024
45277a3
fix ngx_tpl
theweakgod Jan 29, 2024
98f294c
rich doc
theweakgod Jan 29, 2024
9b5aef2
Merge branch 'apache:master' into master
theweakgod Feb 4, 2024
a2309a0
Merge branch 'apache:master' into master
theweakgod Feb 8, 2024
680cacf
Merge branch 'master' into feat/add_redis_in_limit_req
theweakgod Feb 8, 2024
4219d33
add redis-cluster test case
theweakgod Feb 8, 2024
782cf5c
rich doc and linter
theweakgod Feb 8, 2024
7d5c937
linter
theweakgod Feb 8, 2024
bbba591
pretty code
theweakgod Feb 8, 2024
62698ac
fix shared dict
theweakgod Feb 18, 2024
6567375
code style
theweakgod Feb 20, 2024
541540f
fix doc
theweakgod Feb 22, 2024
ec984cd
fix doc
theweakgod Feb 22, 2024
b12b50f
fix doc
theweakgod Feb 22, 2024
8408fd9
fix doc
theweakgod Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apisix/cli/ngx_tpl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ http {
{% end %}

{% if enabled_plugins["limit-req"] then %}
lua_shared_dict plugin-limit-req-redis-cluster-slot-lock {* http.lua_shared_dict["plugin-limit-req-redis-cluster-slot-lock"] *};
lua_shared_dict plugin-limit-req {* http.lua_shared_dict["plugin-limit-req"] *};
{% end %}

Expand Down
61 changes: 55 additions & 6 deletions apisix/plugins/limit-req.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,29 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local limit_req_new = require("resty.limit.req").new
local core = require("apisix.core")
local plugin_name = "limit-req"
local limit_req_new = require("resty.limit.req").new
local core = require("apisix.core")
local redis_schema = require("apisix.utils.redis-schema")
local policy_to_additional_properties = redis_schema.schema
local plugin_name = "limit-req"
local sleep = core.sleep

local redis_single_new
local redis_cluster_new
do
local redis_src = "apisix.plugins.limit-req.limit-req-redis"
redis_single_new = require(redis_src).new

local cluster_src = "apisix.plugins.limit-req.limit-req-redis-cluster"
redis_cluster_new = require(cluster_src).new
end


local lrucache = core.lrucache.new({
type = "plugin",
})


local schema = {
type = "object",
properties = {
Expand All @@ -34,6 +47,11 @@ local schema = {
enum = {"var", "var_combination"},
default = "var",
},
policy = {
type = "string",
enum = {"redis", "redis-cluster", "local"},
default = "local",
},
rejected_code = {
type = "integer", minimum = 200, maximum = 599, default = 503
},
Expand All @@ -45,7 +63,25 @@ local schema = {
},
allow_degradation = {type = "boolean", default = false}
},
required = {"rate", "burst", "key"}
required = {"rate", "burst", "key"},
["if"] = {
properties = {
policy = {
enum = {"redis"},
},
},
},
["then"] = policy_to_additional_properties.redis,
["else"] = {
["if"] = {
properties = {
policy = {
enum = {"redis-cluster"},
},
},
},
["then"] = policy_to_additional_properties["redis-cluster"],
}
}


Expand All @@ -68,8 +104,21 @@ end


local function create_limit_obj(conf)
core.log.info("create new limit-req plugin instance")
return limit_req_new("plugin-limit-req", conf.rate, conf.burst)
if conf.policy == "local" then
core.log.info("create new limit-req plugin instance")
return limit_req_new("plugin-limit-req", conf.rate, conf.burst)

shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
elseif conf.policy == "redis" then
core.log.info("create new limit-req redis plugin instance")
return redis_single_new("plugin-limit-req", conf, conf.rate, conf.burst)

elseif conf.policy == "redis-cluster" then
core.log.info("create new limit-req redis-cluster plugin instance")
return redis_cluster_new("plugin-limit-req", conf, conf.rate, conf.burst)

else
return nil, "policy enum not match"
end
end


Expand Down
50 changes: 50 additions & 0 deletions apisix/plugins/limit-req/limit-req-redis-cluster.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You 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 redis_cluster = require("apisix.utils.rediscluster")
local setmetatable = setmetatable
local util = require("apisix.plugins.limit-req.util")

local _M = {version = 0.1}


local mt = {
__index = _M
}


function _M.new(plugin_name, conf, rate, burst)
local red_cli, err = redis_cluster.new(conf, "plugin-limit-req-redis-cluster-slot-lock")
if not red_cli then
return nil, err
end
local self = {
conf = conf,
plugin_name = plugin_name,
burst = burst * 1000,
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
rate = rate * 1000,
red_cli = red_cli,
}
return setmetatable(self, mt)
end


function _M.incoming(self, key, commit)
return util.incoming(self, self.red_cli, key, commit)
end


return _M
54 changes: 54 additions & 0 deletions apisix/plugins/limit-req/limit-req-redis.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You 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 redis = require("apisix.utils.redis")
local setmetatable = setmetatable
local util = require("apisix.plugins.limit-req.util")

local setmetatable = setmetatable


local _M = {version = 0.1}


local mt = {
__index = _M
}


function _M.new(plugin_name, conf, rate, burst)
local self = {
conf = conf,
plugin_name = plugin_name,
burst = burst * 1000,
shreemaan-abhishek marked this conversation as resolved.
Show resolved Hide resolved
rate = rate * 1000,
}
return setmetatable(self, mt)
end


function _M.incoming(self, key, commit)
local conf = self.conf
local red, err = redis.new(conf)
if not red then
return red, err
end

return util.incoming(self, red, key, commit)
end


return _M
78 changes: 78 additions & 0 deletions apisix/plugins/limit-req/util.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You 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 math = require "math"
local abs = math.abs
local max = math.max
local ngx_now = ngx.now
local ngx_null = ngx.null
local tonumber = tonumber


local _M = {version = 0.1}


-- the "commit" argument controls whether should we record the event in shm.
function _M.incoming(self, red, key, commit)
local rate = self.rate
local now = ngx_now() * 1000

key = "limit_req" .. ":" .. key
local excess_key = key .. "excess"
local last_key = key .. "last"

local excess, err = red:get(excess_key)
if err then
return nil, err
end
local last, err = red:get(last_key)
if err then
return nil, err
end

if excess ~= ngx_null and last ~= ngx_null then
excess = tonumber(excess)
last = tonumber(last)
local elapsed = now - last
excess = max(excess - rate * abs(elapsed) / 1000 + 1000, 0)

if excess > self.burst then
return nil, "rejected"
end
else
excess = 0
end

if commit then
local ok
local err
ok, err = red:set(excess_key, excess)
if not ok then
return nil, err
end

ok, err = red:set(last_key, now)
if not ok then
return nil, err
end
end

-- return the delay in seconds, as well as excess
return excess / rate, excess / 1000
end


return _M
1 change: 1 addition & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ nginx_config: # Config for render the template to generate n
balancer-ewma: 10m
balancer-ewma-locks: 10m
balancer-ewma-last-touched-at: 10m
plugin-limit-req-redis-cluster-slot-lock: 1m
plugin-limit-count-redis-cluster-slot-lock: 1m
plugin-limit-conn-redis-cluster-slot-lock: 1m
tracing_buffer: 10m
Expand Down
13 changes: 13 additions & 0 deletions docs/en/latest/plugins/limit-req.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ The `limit-req` Plugin limits the number of requests to your service using the [
| rejected_msg | string | False | | non-empty | Body of the response returned when the requests exceeding the threshold are rejected. |
| nodelay | boolean | False | false | | If set to `true`, requests within the burst threshold would not be delayed. |
| allow_degradation | boolean | False | false | | When set to `true` enables Plugin degradation when the Plugin is temporarily unavailable and allows requests to continue. |
| policy | string | False | "local" | ["local", "redis", "redis-cluster"] | Rate-limiting policies to use for retrieving and increment the limit count. When set to `local` the counters will be locally stored in memory on the node. When set to `redis` counters are stored on a Redis server and will be shared across the nodes. It is done usually for global speed limiting, and setting to `redis-cluster` uses a Redis cluster instead of a single instance. |
| redis_host | string | required when `policy` is `redis` | | | Address of the Redis server. Used when the `policy` attribute is set to `redis`. |
| redis_port | integer | False | 6379 | [1,...] | Port of the Redis server. Used when the `policy` attribute is set to `redis`. |
| redis_username | string | False | | | Username for Redis authentication if Redis ACL is used (for Redis version >= 6.0). If you use the legacy authentication method `requirepass` to configure Redis password, configure only the `redis_password`. Used when the `policy` is set to `redis`. |
| redis_password | string | False | | | Password for Redis authentication. Used when the `policy` is set to `redis` or `redis-cluster`. |
| redis_ssl | boolean | False | false | | If set to `true`, then uses SSL to connect to redis instance. Used when the `policy` attribute is set to `redis`. |
| redis_ssl_verify | boolean | False | false | | If set to `true`, then verifies the validity of the server SSL certificate. Used when the `policy` attribute is set to `redis`. See [tcpsock:sslhandshake](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake). |
| redis_database | integer | False | 0 | redis_database >= 0 | Selected database of the Redis server (for single instance operation or when using Redis cloud with a single entrypoint). Used when the `policy` attribute is set to `redis`. |
| redis_timeout | integer | False | 1000 | [1,...] | Timeout in milliseconds for any command submitted to the Redis server. Used when the `policy` attribute is set to `redis` or `redis-cluster`. |
| redis_cluster_nodes | array | required when `policy` is `redis-cluster` | | | Addresses of Redis cluster nodes. Used when the `policy` attribute is set to `redis-cluster`. |
| redis_cluster_name | string | required when `policy` is `redis-cluster` | | | Name of the Redis cluster service nodes. Used when the `policy` attribute is set to `redis-cluster`. |
| redis_cluster_ssl | boolean | False | false | | If set to `true`, then uses SSL to connect to redis-cluster. Used when the `policy` attribute is set to `redis-cluster`. |
| redis_cluster_ssl_verify | boolean | False | false | | If set to `true`, then verifies the validity of the server SSL certificate. Used when the `policy` attribute is set to `redis-cluster`. |

## Enable Plugin

Expand Down
Loading
Loading