You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/01/05 01:05:27 UTC

[apisix] branch master updated: feat(limit-count): add constant key type (#5984)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new c6f1a83  feat(limit-count): add constant key type (#5984)
c6f1a83 is described below

commit c6f1a83c6ff17fdbadd6c7b95f9d1169ce06676e
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Wed Jan 5 09:05:19 2022 +0800

    feat(limit-count): add constant key type (#5984)
    
    Co-authored-by: Yu.Bozhong <y....@foxmail.com>
    Co-authored-by: Bisakh <bi...@gmail.com>
---
 apisix/plugins/limit-count.lua        |  9 ++--
 docs/en/latest/plugins/limit-count.md | 38 +++++++++++++++--
 docs/zh/latest/plugins/limit-count.md | 37 ++++++++++++++--
 t/plugin/limit-count2.t               | 79 +++++++++++++++++++++++++++++++++++
 4 files changed, 152 insertions(+), 11 deletions(-)

diff --git a/apisix/plugins/limit-count.lua b/apisix/plugins/limit-count.lua
index 5b0e8c3..2a57792 100644
--- a/apisix/plugins/limit-count.lua
+++ b/apisix/plugins/limit-count.lua
@@ -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 = {
@@ -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
@@ -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
     end
 
     core.log.info("limit key: ", key)
diff --git a/docs/en/latest/plugins/limit-count.md b/docs/en/latest/plugins/limit-count.md
index 0062596..e4aafce 100644
--- a/docs/en/latest/plugins/limit-count.md
+++ b/docs/en/latest/plugins/limit-count.md
@@ -39,8 +39,8 @@ 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           | 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.|
+| 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 "constant", the key will be treated as a constant. 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 key, plugin will be restricted by two variables which are "remote_addr" and "consumer_name". If [...]
 | 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.                                                                                                                                                                                                             |
 | policy              | string  | optional                                | "local"       | ["local", "redis", "redis-cluster"]                                                                     | The rate-limiting policies to use for retrieving and incrementing the limits. Available values are `local`(the counters will be stored locally in-memory on the node), `redis`(counters are stored on a Redis server and will be shared across the nodes, usually use it to do the global speed limit) [...]
@@ -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": {
@@ -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",
@@ -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:
diff --git a/docs/zh/latest/plugins/limit-count.md b/docs/zh/latest/plugins/limit-count.md
index 4d7273d..124b959 100644
--- a/docs/zh/latest/plugins/limit-count.md
+++ b/docs/zh/latest/plugins/limit-count.md
@@ -42,8 +42,8 @@ title: limit-count
 | ------------------- | ------- | --------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [...]
 | count               | integer | 必须                               |               | count > 0                                                                                               | 指定时间窗口内的请求数量阈值                                                                                                                                                                                                                                                                                                [...]
 | time_window         | integer | 必须                               |               | time_window > 0                                                                                         | 时间窗口的大小(以秒为单位),超过这个时间就会重置                                                                                                                                                                                                                                                                                     [...]
-| key_type      | string | 可选   |  "var"      | ["var", "var_combination"]                                          | 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。 |
+| key_type      | string | 可选   |  "var"      | ["var", "var_combination", "constant"]                                          | key 的类型 |
+| key           | string  | 可选   |    "remote_addr"    |  | 用来做请求计数的依据。如果 `key_type` 为 "constant",那么 key 会被当作常量。如果 `key_type` 为 "var",那么 key 会被当作变量名称。如果 `key_type` 为 "var_combination",那么 key 会当作变量组。比如如果设置 "$remote_addr $consumer_name" 作为 key,那么插件会同时受 remote_addr 和 consumer_name 两个变量的约束。如果 key 的值为空,$remote_addr 会被作为默认 key。 |
 | rejected_code       | integer | 可选                               | 503           | [200,...,599]                                                                                           | 当请求超过阈值被拒绝时,返回的 HTTP 状态码                                                                                                                                                                                                                                                                                      [...]
 | rejected_msg       | string | 可选                                |            | 非空                                                                                           | 当请求超过阈值被拒绝时,返回的响应体。                                                                                                                                                                                                             |
 | policy              | string  | 可选                               | "local"       | ["local", "redis", "redis-cluster"]                                                                     | 用于检索和增加限制的速率限制策略。可选的值有:`local`(计数器被以内存方式保存在节点本地,默认选项) 和 `redis`(计数器保存在 Redis 服务节点上,从而可以跨节点共享结果,通常用它来完成全局限速);以及`redis-cluster`,跟 redis 功能一样,只是使用 redis 集群方式。                                                                                                                                                        |
@@ -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": {
@@ -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",
@@ -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`。
+通过设置 `key_type` 为 `constant`,`key` 的值将会直接作为常量来处理。
+
+现在每个配置了 `group` 为 `services_1#1640140620` 的 Route 上的所有请求,都将共享同一个每分钟只能访问一次的计数器,即使它们来自不同的 IP 地址。
+
 如果你需要一个集群级别的流量控制,我们可以借助 redis server 来完成。不同的 APISIX 节点之间将共享流量限速结果,实现集群流量限速。
 
 如果启用单 redis 策略,请看下面例子:
diff --git a/t/plugin/limit-count2.t b/t/plugin/limit-count2.t
index 3f6bdf3..621edad 100644
--- a/t/plugin/limit-count2.t
+++ b/t/plugin/limit-count2.t
@@ -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]