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(limit-count): add constant key type #5984

Merged
merged 5 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
9 changes: 6 additions & 3 deletions apisix/plugins/limit-count.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ local schema = {
group = {type = "string"},
key = {type = "string", default = "remote_addr"},
key_type = {type = "string",
enum = {"var", "var_combination"},
enum = {"var", "var_combination", "constant"},
default = "var",
},
rejected_code = {
Expand Down Expand Up @@ -238,6 +238,8 @@ function _M.access(conf, ctx)
if n_resolved == 0 then
key = nil
end
elseif conf.key_type == "constant" then
key = conf_key
else
key = ctx.var[conf_key]
end
Expand All @@ -248,10 +250,11 @@ function _M.access(conf, ctx)
key = ctx.var["remote_addr"]
end

-- here we add a separator ':' to mark the boundary of the prefix and the key itself
if not conf.group then
key = key .. ctx.conf_type .. ctx.conf_version
key = ctx.conf_type .. ctx.conf_version .. ':' .. key
else
key = key .. conf.group
key = conf.group .. ':' .. key
Comment on lines +253 to +257
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add a note here to alert those who use the key to monitoring or something else.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you give an example for the note?

Copy link
Member

@leslie-tsang leslie-tsang Jan 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

-- key format changed from `key*` to `*:key` for constant key type support

Copy link
Member Author

@spacewander spacewander Jan 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err...
I don't want to comment on all the changes in the code, unless it is something that affects public API. AFAIK, we don't describe the key format in the doc. So I think such change is just implementation details.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make Sense, leave the comment here should do the trick as well.

end

core.log.info("limit key: ", key)
Expand Down
36 changes: 33 additions & 3 deletions docs/en/latest/plugins/limit-count.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Limit request rate by a fixed number of requests in a given time window.
| ------------------- | ------- | --------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| count | integer | required | | count > 0 | the specified number of requests threshold. |
| time_window | integer | required | | time_window > 0 | the time window in seconds before the request count is reset. |
| key_type | string | optional | "var" | ["var", "var_combination"] | the type of key. |
| key_type | string | optional | "var" | ["var", "var_combination", "constant"] | the type of key. |
| key | string | optional | "remote_addr" | | the user specified key to limit the rate. If the `key_type` is "var", the key will be treated as a name of variable. If the `key_type` is "var_combination", the key will be a combination of variables. For example, if we use "$remote_addr $consumer_name" as keys, plugin will be restricted by two keys which are "remote_addr" and "consumer_name". If the value of the key is empty, `remote_addr` will be set as the default key.|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we add a line here[L43] to document how the key will be interpreted when the key_type is constant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

| rejected_code | integer | optional | 503 | [200,...,599] | The HTTP status code returned when the request exceeds the threshold is rejected, default 503. |
| rejected_msg | string | optional | | non-empty | The response body returned when the request exceeds the threshold is rejected. |
Expand Down Expand Up @@ -110,7 +110,7 @@ You also can complete the above operation through the web interface, first add a

It is possible to share the same limit counter across different routes. For example,

```
```shell
curl -i http://127.0.0.1:9080/apisix/admin/services/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"plugins": {
Expand All @@ -133,7 +133,7 @@ curl -i http://127.0.0.1:9080/apisix/admin/services/1 -H 'X-API-KEY: edd1c9f0343

Every route which group name is "services_1#1640140620" will share the same count limitation `1` in one minute per remote_addr.

```
```shell
$ curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"service_id": "1",
Expand All @@ -156,6 +156,36 @@ HTTP/1.1 503 ...
Note that every limit-count configuration of the same group must be the same.
Therefore, once update the configuration, we also need to update the group name.

It is also possible to share the same limit counter in all requests. For example,

```shell
curl -i http://127.0.0.1:9080/apisix/admin/services/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"plugins": {
"limit-count": {
"count": 1,
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr",
"key_type": "constant",
"group": "services_1#1640140621"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

Compared with the previous configuration, we set the `key_type` to `constant`.
By setting `key_type` to `constant`, we don't evaluate the value of `key` but treat it as a constant.

Now every route which group name is "services_1#1640140621" will share the same count limitation `1` in one minute among all the requests,
even these requests are from different remote_addr.

If you need a cluster-level precision traffic limit, then we can do it with the redis server. The rate limit of the traffic will be shared between different APISIX nodes to limit the rate of cluster traffic.

Here is the example if we use single `redis` policy:
Expand Down
35 changes: 32 additions & 3 deletions docs/zh/latest/plugins/limit-count.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ title: limit-count
| ------------------- | ------- | --------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| count | integer | 必须 | | count > 0 | 指定时间窗口内的请求数量阈值 |
| time_window | integer | 必须 | | time_window > 0 | 时间窗口的大小(以秒为单位),超过这个时间就会重置 |
| key_type | string | 可选 | "var" | ["var", "var_combination"] | key 的类型 |
| key_type | string | 可选 | "var" | ["var", "var_combination", "constant"] | key 的类型 |
| key | string | 可选 | "remote_addr" | | 用来做请求计数的依据。如果 `key_type` 为 "var",那么 key 会被当作变量名称。如果 `key_type` 为 "var_combination",那么 key 会当作变量组。比如如果设置 "$remote_addr $consumer_name" 作为 keys,那么插件会同时受 remote_addr 和 consumer_name 两个 key 的约束。如果 key 的值为空,$remote_addr 会被作为默认 key。 |
| rejected_code | integer | 可选 | 503 | [200,...,599] | 当请求超过阈值被拒绝时,返回的 HTTP 状态码 |
| rejected_msg | string | 可选 | | 非空 | 当请求超过阈值被拒绝时,返回的响应体。 |
Expand Down Expand Up @@ -115,7 +115,7 @@ curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335

我们也支持在多个 Route 间共享同一个限流计数器。举个例子,

```
```shell
curl -i http://127.0.0.1:9080/apisix/admin/services/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"plugins": {
Expand All @@ -138,7 +138,7 @@ curl -i http://127.0.0.1:9080/apisix/admin/services/1 -H 'X-API-KEY: edd1c9f0343

每个配置了 `group` 为 `services_1#1640140620` 的 Route 都将共享同一个每个 IP 地址每分钟只能访问一次的计数器。

```
```shell
$ curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"service_id": "1",
Expand All @@ -161,6 +161,35 @@ HTTP/1.1 503 ...
注意同一个 group 里面的 limit-count 配置必须一样。
所以,一旦修改了配置,我们需要更新对应的 group 的值。

我们也支持在所有请求间共享同一个限流计数器。举个例子,

```shell
curl -i http://127.0.0.1:9080/apisix/admin/services/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"plugins": {
"limit-count": {
"count": 1,
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr",
"key_type": "constant",
"group": "services_1#1640140621"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

比起之前的配置,我们把 `key_type` 改成 `constant`。
spacewander marked this conversation as resolved.
Show resolved Hide resolved
通过设置 `key_type` 为 `constant`,`key` 的值将会直接作为常量来处理。

现在每个配置了 `group` 为 `services_1#1640140620` 的 Route 上的所有请求,都将共享同一个每分钟只能访问一次的计数器,即使它们来自不同的 IP 地址。

如果你需要一个集群级别的流量控制,我们可以借助 redis server 来完成。不同的 APISIX 节点之间将共享流量限速结果,实现集群流量限速。

如果启用单 redis 策略,请看下面例子:
Expand Down
79 changes: 79 additions & 0 deletions t/plugin/limit-count2.t
Original file line number Diff line number Diff line change
Expand Up @@ -685,3 +685,82 @@ passed
[error]
--- response_body
{"error_msg":"failed to check the configuration of plugin limit-count err: group conf mismatched"}



=== TEST 20: group with constant key
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/services/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"limit-count": {
"count": 2,
"time_window": 60,
"rejected_code": 503,
"key_type": "constant",
"group": "afafafhao2"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)

if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed



=== TEST 21: hit multiple paths
--- config
location /t {
content_by_lua_block {
local json = require "t.toolkit.json"
local http = require "resty.http"
local uri1 = "http://127.0.0.1:" .. ngx.var.server_port
.. "/hello"
local uri2 = "http://127.0.0.1:" .. ngx.var.server_port
.. "/hello_chunked"
local ress = {}
for i = 1, 4 do
local httpc = http.new()
local uri
if i % 2 == 1 then
uri = uri1
else
uri = uri2
end

local res, err = httpc:request_uri(uri)
if not res then
ngx.say(err)
return
end
table.insert(ress, res.status)
end
ngx.say(json.encode(ress))
}
}
--- grep_error_log eval
qr/limit key: afafafhao2:remote_addr/
--- grep_error_log_out
limit key: afafafhao2:remote_addr
limit key: afafafhao2:remote_addr
limit key: afafafhao2:remote_addr
limit key: afafafhao2:remote_addr
--- response_body
[200,200,503,503]