Implementing Dynamic Rate Limiting with the APISIX API Gateway
This tutorial shows how to use the open‑source APISIX API gateway—built on OpenResty and etcd—to create a dynamic rate‑limiting plugin in Lua, configure route matching with a radix tree, and demonstrate the feature with a test endpoint that enforces one request per second and returns HTTP 503 when exceeded.
API gateway refers to a layer that aggregates all API calls, handling both inbound and outbound traffic. By routing all business requests through the gateway, it becomes the main traffic entry point, enabling unified processing, security, speed, and accuracy.
The gateway offers many features—reverse proxy, dynamic load balancing, circuit breaking, health checks, dynamic upstream, rate limiting, dynamic SSL certificates, etc.—allowing services to focus solely on business logic.
Can it replace Nginx? Yes, and it provides additional capabilities that Nginx lacks.
The following tutorial demonstrates how to use the open‑source APISIX framework to implement a dynamic rate‑limiting feature.
1. APISIX Framework
APISIX is an API‑gateway framework built on OpenResty and etcd. Compared with other gateways such as Kong, APISIX shows clear advantages in functionality, performance, and extensibility. Installation instructions and performance comparisons are available on its GitHub repository.
1.1 Starting APISIX
After installation, starting APISIX performs three main tasks:
Initialize the Nginx configuration file and verify the OpenResty version.
Check etcd availability and load the framework’s default configuration.
Launch OpenResty with the generated Nginx configuration.
local openresty_args = [[openresty -p ]] .. apisix_home .. [[ -c ]] .. apisix_home .. [[/conf/nginx.conf]]
function _M.start(...)
init(...)
init_etcd(...)
local cmd = openresty_args
-- print(cmd)
os.execute(cmd)
end1.2 APISIX Plugins
Plugins are the core of APISIX. To develop a rate‑limiting plugin, you only need to implement the plugin logic and bind it to the appropriate route.
The workflow consists of four stages:
Route matching : Match incoming requests against routing rules (method, URI, host, headers, etc.) and retrieve the associated plugin list from etcd.
Plugin matching : Intersect the enabled plugins with the route’s plugin list to obtain the final executable plugins.
Plugin execution : Run plugins in order of priority.
Upstream dispatch : Forward the request to the selected upstream based on health checks and load‑balancing algorithms.
The plugin hot‑reload feature allows Lua code to be updated without restarting OpenResty. The following code shows how APISIX loads a plugin dynamically:
local function load_plugin(name, plugins_list, is_stream_plugin)
local pkg_name = "apisix.plugins." .. name
if is_stream_plugin then
pkg_name = "apisix.stream.plugins." .. name
end
pkg_loaded[pkg_name] = nil
local ok, plugin = pcall(require, pkg_name)
if not ok then
core.log.error("failed to load plugin [", name, "] err: ", plugin)
return
end
if not plugin.priority then
core.log.error("invalid plugin [", name, "], missing field: priority")
return
end
if not plugin.version then
core.log.error("invalid plugin [", name, "] missing field: version")
return
end
plugin.name = name
core.table.insert(plugins_list, plugin)
if plugin.init then
plugin.init()
end
return
end1.3 Plugin Development Rules
The rate‑limiting plugin is implemented as a Lua module with the following structure:
local _M = {
version = 0.1,
priority = 1001,
name = plugin_name,
schema = schema,
}The schema defines configuration validation using JSON‑Schema syntax:
local schema = {
type = "object",
properties = {
rate = {type = "number", minimum = 0},
burst = {type = "number", minimum = 0},
key = {type = "string", enum = {"remote_addr", "server_addr", "http_x_real_ip", "http_x_forwarded_for"}},
rejected_code = {type = "integer", minimum = 200},
},
required = {"rate", "burst", "key", "rejected_code"}
}
function _M.check_schema(conf)
local ok, err = core.schema.check(schema, conf)
if not ok then return false, err end
return true
endThe plugin’s main logic resides in the access phase, where the rate‑limiting library resty.limit.req is instantiated and applied:
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)
end
function _M.access(conf, ctx)
local lim, err = core.lrucache.plugin_ctx(plugin_name, ctx, create_limit_obj, conf)
if not lim then return 500 end
local key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then return conf.rejected_code end
core.log.error("failed to limit req: ", err)
return 500
end
if delay >= 0.001 then sleep(delay) end
endAdditional lifecycle hooks ( init , header_filter , body_filter , log ) are optional and can be implemented as needed.
1.4 Route Matching Rules
APISIX uses a radix tree (implemented via Lua FFI) to perform multi‑dimensional matching (URI, host, method, query parameters, IP, etc.). Example configuration:
local radix = require("resty.radixtree")
local rx = radix.new({
{ paths = "/aa", hosts = "foo.com", method = {"GET", "POST"}, remote_addrs = "127.0.0.1", metadata = "1" },
{ paths = "/bb*", hosts = {"*.bar.com", "gloo.com"}, method = {"GET", "POST", "PUT"}, remote_addrs = "fe80:fe80::/64", vars = {"arg_name", "jack"}, metadata = "2" }
})
ngx.say(rx:match("/aa", { host = "foo.com", method = "GET", remote_addr = "127.0.0.1" }))The radix tree offers O(K) lookup complexity, which is more efficient than traditional hash‑based approaches.
2. Demonstration
The demo configures the front‑end endpoint /test to allow 1 request per second with a burst of 1. When the limit is exceeded, the gateway returns HTTP 503.
Demo URLs:
Dashboard: http://47.106.211.125:9080/apisix/dashboard
Test endpoint: http://47.106.211.125:9080/test
The backend runs three instances on ports 8001, 8002, and 8003, with port 8003 intentionally dropping connections once per minute to illustrate load‑balancing behavior.
37 Interactive Technology Team
37 Interactive Technology Center
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.