OpenResty入门:OpenResty限流与熔断

2026-03-31 25 阅读 中间件
所属系列:OpenResty入门系列教程 查看完整系列

一、前言

在高并发场景下,保护后端服务免受流量冲击是至关重要的。OpenResty 提供了强大的限流和熔断能力,可以在网关层面实现对后端服务的保护。本文将介绍如何使用 OpenResty 实现请求限流、连接数限制以及服务熔断。

1、本文主要内容

  • 基于 limit_req 模块的请求速率限流
  • 基于 limit_conn 模块的并发连接数限制
  • 使用 Lua 实现服务熔断
  • 限流与熔断组合配置
  • 分布式限流+降级+熔断(基于 Redis + lua-resty-http)

2、本文环境信息

工具/环境 版本说明
OpenResty 1.27.1.2
Redis 7.0+(Docker)
操作系统 Windows 10/11 或 Linux/macOS

3、准备工作

1、安装部署 OpenResty
Linux 部署 OpenrestymacOS 部署 OpenrestyWindows 部署 Openresty使用 Docker 部署 Openresty

安装后配置把 conf/myconf/*.conf 加入 nginx.conf 中,后续示例中的配置默认都在 conf/myconf 目录下

2、引入 Lua 依赖库

为保证版本一致性,从 GitHub 克隆指定版本:

# 创建目录(Windows 使用 Git Bash)
mkdir -p ~/openresty/lib
cd ~/openresty/lib

# 安装 lua-resty-http(HTTP 客户端)
git clone -b release/0.17.2 https://github.com/ledgetech/lua-resty-http.git
# 安装 lua-resty-redis(Redis 客户端)
git clone -b v0.33 https://github.com/openresty/lua-resty-redis.git
# 复制库文件到统一目录
cp -r ~/openresty/lib/lua-resty-http/lib/resty ~/openresty/lib/
cp -r ~/openresty/lib/lua-resty-redis/lib/resty ~/openresty/lib/

nginx.conf 配置

在 nginx.conf 中的 http 配置块添加 Lua 包路径:

# Windows
lua_package_path "C:/Users/ken/openresty/lib/?.lua;;";

# macOS
lua_package_path "/Users/ken/openresty/lib/?.lua;;";

# Linux
lua_package_path "/home/ken/openresty/lib/?.lua;;";

📌 cjson 使用 OpenResty 内置库,无需额外安装

3、使用 Docker 启动后端服务和 Redis

# 启动后端服务
docker run -d --name backend -p 8000:8080 kentalk/httpserver:latest

# 启动 Redis
docker run -d --name redis -p 6379:6379 redis:latest

测试后端服务:

curl localhost:8000

4、安装 wrk 并测试

参考:HTTP 压测工具 wrk 安装与使用

# 4个线程、100个并发连接、10秒测试时间
wrk -t4 -c100 -d10s http://localhost:8000

# 测试结果
Running 10s test @ http://localhost:8000
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.19ms    2.23ms  48.85ms   87.22%
    Req/Sec    14.14k     1.46k   35.75k    88.78%
  564357 requests in 10.10s, 163.62MB read
Requests/sec:  55881.22
Transfer/sec:     16.20MB

📌 测试结果:每秒处理 8552.38 个请求,传输 1.65MB 数据。没有限流熔断机制,所以每个请求都正常返回

5、重要提示

Windows 环境下,由于WSL跟本机的网络隔离特性,默认不能在WSL中访问本机的非80端口,可以在 server_name 中增加本机IP地址,或者也可以更换其他压测工具

  • server_name 中增加本机IP地址
listen 8021;
server_name localhost 192.168.1.100;
wrk -t4 -c100 -d10s http://192.168.1.100:8021/

二、请求速率限流(limit_req)

OpenResty 内置的 ngx_http_limit_req_module 模块可以实现基于漏桶算法的请求限流。

1、配置实现

创建 conf/myconf/limit_req_8021.conf

limit_req_zone $binary_remote_addr zone=req_limit_8021:10m rate=10r/s;

upstream backend_8021 {
    server 127.0.0.1:8000;
}

server {
    listen 8021;
    server_name localhost;
    
    location / {
        default_type application/json;
        
        limit_req zone=req_limit_8021 burst=20 nodelay;
        limit_req_status 429;
        
        proxy_pass http://backend_8021;
        proxy_set_header Host $host;
    }
    
    error_page 429 = /rate_limit;
    location = /rate_limit {
        default_type application/json;
        return 429 '{"code": 429, "message": "请求过于频繁,请稍后重试"}';
    }
}

配置说明

参数 说明
limit_req_zone 定义限流区域,使用客户端IP作为key
zone=req_limit_8021:10m 区域名称为 req_limit_8021,分配 10MB 内存
rate=10r/s 限制每秒最多 10 个请求
burst=20 允许突发 20 个请求排队
nodelay 超过限制时立即返回 503,不延迟处理
listen 8021 监听端口 8021

2、测试

重载配置后,使用 wrk 进行压力测试:

nginx -s reload
wrk -t4 -c100 -d10s http://localhost:8021/

预期结果统计:

Running 10s test @ http://localhost:8021/
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.21ms    5.58ms 190.80ms   98.57%
    Req/Sec     5.20k   798.85    19.38k    97.26%
  207484 requests in 10.10s, 47.30MB read
  Non-2xx or 3xx responses: 207361
Requests/sec:  20544.32
Transfer/sec:      4.68MB

⚠️ Non-2xx or 3xx responses 显示为限流返回的数量(约 207361 个 429/503 响应)

三、并发连接数限制(limit_conn)

ngx_http_limit_conn_module 模块可以限制单个 IP 的并发连接数。

1、配置实现

创建 conf/myconf/limit_conn_8022.conf

limit_conn_zone $binary_remote_addr zone=conn_limit_8022:10m;

upstream backend_8022 {
    server 127.0.0.1:8000;
}

server {
    listen 8022;
    server_name localhost;
    
    location / {
        default_type application/json;
        
        limit_conn conn_limit_8022 10;
        limit_conn_status 429;
        
        proxy_pass http://backend_8022;
        proxy_set_header Host $host;
    }
    
    error_page 429 = /conn_limit;
    location = /conn_limit {
        default_type application/json;
        return 429 '{"code": 429, "message": "并发连接数超限"}';
    }
}

配置说明

参数 说明
limit_conn_zone 定义连接限制区域
zone=conn_limit_8022 区域名称
limit_conn conn_limit_8022 10 每个 IP 最多 10 个并发连接
listen 8022 监听端口 8022

2、测试

重载配置后,使用 wrk 进行并发连接测试:

nginx -s reload
wrk -t4 -c50 -d10s http://localhost:8022/

📌 -c50 表示 50 个并发连接,正好可以测试限制 10 个连接的效果

预期结果统计:

Running 10s test @ http://localhost:8022/
  4 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.40ms   10.98ms 172.29ms   98.34%
    Req/Sec     3.02k   467.55     4.12k    72.28%
  121764 requests in 10.22s, 27.27MB read
  Non-2xx or 3xx responses: 111761
Requests/sec:  11917.53
Transfer/sec:      2.67MB

⚠️ limit_conn 统计的是请求数而非真正的 TCP 连接数,只有当请求被处理时才会计数

四、服务熔断(Lua 实现)

OpenResty 可以通过 Lua 脚本实现更灵活的熔断策略,当后端服务异常时自动熔断。

1、配置实现

创建 conf/myconf/circuit_breaker_8023.conf

lua_shared_dict circuit_breaker_8023 1m;

upstream backend_8023 {
    server 127.0.0.1:8000;
}

server {
    listen 8023;
    server_name localhost;
    
    location / {
        default_type application/json;
        
        proxy_pass http://backend_8023;
        proxy_set_header Host $host;
    }
    
    location = /breaker {
        default_type application/json;
        
        access_by_lua_block {
            local shared_dict = ngx.shared.circuit_breaker_8023
            local circuit_open = shared_dict:get("circuit_open") or false
            
            if circuit_open then
                local open_time = shared_dict:get("open_time") or 0
                if ngx.now() - open_time < 30 then
                    ngx.status = 503
                    ngx.say('{"code": 503, "message": "服务暂时不可用,请稍后重试"}')
                    ngx.exit(503)
                else
                    shared_dict:set("circuit_open", false)
                    shared_dict:set("fail_count", 0)
                    shared_dict:set("total_count", 0)
                end
            end
        }
        
        proxy_pass http://127.0.0.1:8000/error?status=500;
        proxy_set_header Host $host;
        
        header_filter_by_lua_block {
            local shared_dict = ngx.shared.circuit_breaker_8023
            local status = ngx.status
            
            if status >= 500 then
                shared_dict:incr("fail_count", 1, 0)
                shared_dict:incr("total_count", 1, 0)
                
                local fail_count = shared_dict:get("fail_count") or 0
                local total_count = shared_dict:get("total_count") or 0
                
                if total_count > 10 and (fail_count / total_count) > 0.5 then
                    shared_dict:set("circuit_open", true)
                    shared_dict:set("open_time", ngx.now())
                end
            else
                shared_dict:incr("total_count", 1, 0)
            end
        }
    }
    
    location = /status {
        default_type application/json;
        content_by_lua_block {
            local circuit_breaker = ngx.shared.circuit_breaker_8023
            local keys = circuit_breaker:get_keys()
            local result = {}
            for _, key in ipairs(keys) do
                result[key] = circuit_breaker:get(key)
            end
            ngx.say(require("cjson").encode(result))
        }
    }
}

📌 配置说明:

  • / 简单转发到后端服务(不受熔断保护)
  • /breaker 触发熔断的测试端点,代理到后端 /error?status=500
  • /status 查看当前熔断状态

2、测试

重载配置后,使用 wrk 向 /breaker 端点发送请求触发熔断:

nginx -s reload
wrk -t4 -c20 -d180s http://localhost:8023/breaker

检查熔断状态:

# 查看当前熔断状态,可以看压测过程中的数据变化
curl http://localhost:8023/status

预期结果:

{
    "total_count": 3651116,
    "open_time": 1773773705.707,
    "circuit_open": true,
    "fail_count": 3651116
}

验证熔断生效:

# 测试 `/breaker` 熔断是否生效,在压测过程中执行
curl -i http://localhost:8023/breaker

预期响应:

HTTP/1.1 503 Service Temporarily Unavailable
Server: openresty/1.27.1.2
Date: Sat, 14 Mar 2026 11:51:01 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

{"code": 503, "message": "服务暂时不可用,请稍后重试"}

💡 30 秒后熔断会自动恢复(半开状态),可以再次测试

3、熔断状态说明

状态 说明
关闭 正常状态,请求正常转发
打开 熔断状态,直接返回 503
半开 熔断恢复期,允许部分请求通过测试

📌 本示例中,当错误率超过 50% 且总请求数超过 10 时触发熔断,熔断持续 30 秒

五、限流与熔断组合配置

将限流和熔断结合使用,实现多层防护。

1、配置实现

创建 conf/myconf/combined_8024.conf

limit_req_zone $binary_remote_addr zone=req_limit_8024:10m rate=100r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit_8024:10m;
lua_shared_dict circuit_breaker_8024 1m;

upstream backend_8024 {
    server 127.0.0.1:8000;
}

server {
    listen 8024;
    server_name localhost;
    
    location / {
        default_type application/json;
        
        limit_req zone=req_limit_8024 burst=50 nodelay;
        limit_req_status 429;
        
        limit_conn conn_limit_8024 20;
        limit_conn_status 429;
        
        proxy_pass http://backend_8024;
        proxy_set_header Host $host;
        proxy_connect_timeout 3s;
        proxy_read_timeout 5s;
        proxy_send_timeout 3s;
        
        error_page 429 = @too_many_requests;
    }
    
    location @too_many_requests {
        default_type application/json;
        return 429 '{"code": 429, "message": "请求过于频繁"}';
    }
    
    location = /breaker {
        default_type application/json;
        
        access_by_lua_block {
            local shared_dict = ngx.shared.circuit_breaker_8024
            local circuit_open = shared_dict:get("circuit_open") or false
            
            if circuit_open then
                local open_time = shared_dict:get("open_time") or 0
                if ngx.now() - open_time < 30 then
                    ngx.status = 503
                    ngx.say('{"code": 503, "message": "服务暂时不可用"}')
                    ngx.exit(503)
                else
                    shared_dict:set("circuit_open", false)
                    shared_dict:set("fail_count", 0)
                    shared_dict:set("total_count", 0)
                end
            end
        }
        
        proxy_pass http://127.0.0.1:8000/error?status=500;
        proxy_set_header Host $host;
        
        header_filter_by_lua_block {
            local shared_dict = ngx.shared.circuit_breaker_8024
            local status = ngx.status
            
            if status >= 500 then
                shared_dict:incr("fail_count", 1, 0)
                shared_dict:incr("total_count", 1, 0)
                
                local fail_count = shared_dict:get("fail_count") or 0
                local total_count = shared_dict:get("total_count") or 0
                
                if total_count > 10 and (fail_count / total_count) > 0.5 then
                    shared_dict:set("circuit_open", true)
                    shared_dict:set("open_time", ngx.now())
                end
            else
                shared_dict:incr("total_count", 1, 0)
            end
        }
    }
    
    location = /status {
        default_type application/json;
        content_by_lua_block {
            local circuit_breaker = ngx.shared.circuit_breaker_8024
            local keys = circuit_breaker:get_keys()
            local result = {}
            for _, key in ipairs(keys) do
                result[key] = circuit_breaker:get(key)
            end
            ngx.say(require("cjson").encode(result))
        }
    }
}

📌 配置说明:

  • / 限流转发(rate=100r/s, burst=50, 连接数限制20)
  • /breaker 触发熔断的测试端点
  • /status 查看熔断状态

2、测试

限流测试:

nginx -s reload
wrk -t4 -c100 -d10s http://localhost:8024/

预期结果统计:

Running 10s test @ http://localhost:8024/
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    30.76ms  112.51ms 802.54ms   94.50%
    Req/Sec     5.15k     2.09k   19.26k    95.56%
  198318 requests in 10.87s, 41.94MB read
  Non-2xx or 3xx responses: 197239
Requests/sec:  18237.95
Transfer/sec:      3.86MB

熔断测试:

wrk -t4 -c20 -d30s http://localhost:8024/breaker
curl http://localhost:8024/status
curl -i http://localhost:8024/breaker

六、分布式限流+降级+熔断(Lua + Redis)

在多实例或需要跨 worker 共享状态的场景下,可用 Redis 统一保存限流计数熔断状态;业务请求经 lua-resty-http 转发到后端,在 Lua 中根据响应码累计成功与失败,用于触发熔断与半开恢复(属于请求路径上的被动统计,与 Nginx upstream 主动健康探测不是同一类机制)。

依赖说明lua-resty-redislua-resty-http 的安装与 lua_package_path 已在 第一节「准备工作」 中配置,本节示例的 *.conf 中不再重复写出。

1、请求处理流程

  1. 连接 Redis;失败则返回 503(Redis 不可用)。
  2. 读取 cb:statuscb:open_ttl:若熔断为 opencb:open_ttl 的值可用(示例中为正数),直接返回 503;若该键已过期或不存在,则把状态置为 half-open 并继续后续步骤。
  3. 对当前客户端 IP 的限流键执行 INCR,首包设置窗口过期时间;超过「每窗口最大次数」则返回 429。
  4. 使用 lua-resty-http 请求后端(可用 ?error=500 等参数模拟失败)。
  5. 若响应缺失或 status >= 500:累加失败计数,满足条件则置熔断为 open 并给 cb:open_ttl 赋值且 EXPIRE;向客户端返回 502 及降级 JSON。
  6. 若成功:累加成功相关计数;half-open 下探测成功则清零并恢复 closed;否则在成功率满足条件时也可恢复关闭状态。
  7. 将 Redis 连接 set_keepalive 归还连接池。

2、响应说明(HTTP 状态与业务含义)

HTTP 场景 说明
200 等 后端正常 透传后端状态码与 body
429 分布式限流 当前时间窗口内该 IP 请求次数超过阈值
502 后端异常或不可达 返回 JSON,含 data.fallback 表示降级提示(未熔断但保护性响应)
503 Redis 不可用 / 熔断打开 Redis 故障或熔断 open 且仍在 cb:open_ttl 有效期内

3、配置实现

创建 conf/myconf/distributed_8025.conf

server {
    listen 8025;
    server_name localhost;
    
    default_type application/json;
    
    location / {
        content_by_lua_block {
            local redis = require("resty.redis")
            local http = require("resty.http")
            local cjson = require("cjson")
            
            local red = redis:new()
            red:set_timeout(2000)
            
            local ok, err = red:connect("127.0.0.1", 6379)
            if not ok then
                ngx.status = 503
                ngx.say(cjson.encode({code = 503, message = "Redis服务不可用"}))
                return
            end
            
            local cb_status_key = "cb:status"
            local cb_ttl_key = "cb:open_ttl"
            local status = red:get(cb_status_key)
            local open_ttl = red:get(cb_ttl_key)
            
            if status == "open" then
                if open_ttl and tonumber(open_ttl) > 0 then
                    ngx.status = 503
                    ngx.say(cjson.encode({
                        code = 503, 
                        message = "服务已熔断", 
                        data = {fallback = "系统繁忙,请稍后重试", retry_after = tonumber(open_ttl)}
                    }))
                    red:set_keepalive(10000, 100)
                    return
                else
                    red:set(cb_status_key, "half-open")
                    status = "half-open"
                end
            end
            
            local client_ip = ngx.var.remote_addr
            local rate_key = "rate_limit:" .. client_ip
            local rate_limit = 100
            local window = 10
            
            local current, err = red:incr(rate_key)
            if not current then
                ngx.status = 503
                ngx.say(cjson.encode({code = 503, message = "限流服务异常"}))
                red:set_keepalive(10000, 100)
                return
            end
            
            if current == 1 then
                red:expire(rate_key, window)
            end
            
            if current > rate_limit then
                ngx.status = 429
                ngx.say(cjson.encode({code = 429, message = "请求过于频繁,请稍后再试"}))
                red:set_keepalive(10000, 100)
                return
            end
            
            local httpc = http.new()
            httpc:set_timeout(3000)
            
            local error_status = ngx.var.arg_error
            local backend_path = "/"
            if error_status then
                backend_path = "/error?status=" .. error_status
            end
            
            local res, err = httpc:request_uri("http://127.0.0.1:8000", {
                method = "GET",
                path = backend_path
            })
            
            if not res or res.status >= 500 then
                local fail_count, err = red:incr("cb:fail_count")
                if not fail_count then fail_count = 1 end
                local total_count, err = red:incr("cb:total_count")
                if not total_count then total_count = 1 end
                
                if fail_count >= 10 or (total_count > 10 and (fail_count / total_count) > 0.5) then
                    red:set(cb_status_key, "open")
                    red:set(cb_ttl_key, 30)
                    red:expire(cb_ttl_key, 30)
                end
                
                ngx.status = 502
                ngx.say(cjson.encode({
                    code = 502, 
                    message = "后端服务异常", 
                    data = {fallback = "已触发降级保护"}
                }))
            else
                local health_count, err = red:incr("cb:health_count")
                local total_count, err = red:incr("cb:total_count")
                
                if status == "half-open" then
                    red:set(cb_status_key, "closed")
                    red:set("cb:fail_count", 0)
                    red:set("cb:health_count", 0)
                    red:set("cb:total_count", 0)
                    red:del(cb_ttl_key)
                elseif health_count and total_count and total_count > 10 and (health_count / total_count) > 0.8 then
                    red:set(cb_status_key, "closed")
                    red:set("cb:fail_count", 0)
                    red:set("cb:health_count", 0)
                    red:set("cb:total_count", 0)
                end
                
                ngx.status = res.status
                ngx.say(res.body)
            end
            
            red:set_keepalive(10000, 100)
        }
    }
    
    location = /status {
        default_type application/json;
        content_by_lua_block {
            local redis = require("resty.redis")
            local cjson = require("cjson")
            
            local red = redis:new()
            red:set_timeout(1000)
            local ok, err = red:connect("127.0.0.1", 6379)
            if not ok then
                ngx.say(cjson.encode({status = "redis_error"}))
                return
            end
            
            -- 与主逻辑保持一致(避免 /status 展示与实际限流参数不一致)
            local rate_limit = 100
            local window = 10

            local client_ip = ngx.var.remote_addr
            local rate_key = "rate_limit:" .. client_ip
            local rate_count = red:get(rate_key)
            local rate_ttl = red:ttl(rate_key)
            local current_count = tonumber(rate_count) or 0
            local ttl = tonumber(rate_ttl) or -1
            local limited = current_count > rate_limit
            local remaining = rate_limit - current_count
            if remaining < 0 then
                remaining = 0
            end
            
            local cb_status = red:get("cb:status")
            local cb_ttl = red:get("cb:open_ttl")
            local fails = red:get("cb:fail_count")
            local health = red:get("cb:health_count")
            local total = red:get("cb:total_count")
            
            ngx.say(cjson.encode({
                rate_limit = {
                    client_ip = client_ip,
                    current_count = current_count,
                    limit = 100,
                    window = 10,
                    ttl = ttl,
                    limited = limited,
                    remaining = remaining
                },
                circuit_breaker = {
                    status = cb_status or "closed",
                    open_ttl = tonumber(cb_ttl) or 0,
                    fail_count = tonumber(fails) or 0,
                    health_count = tonumber(health) or 0,
                    total_count = tonumber(total) or 0
                }
            }))
            red:set_keepalive(10000, 100)
        }
    }
    
    location = /reset {
        default_type application/json;
        content_by_lua_block {
            local redis = require("resty.redis")
            local cjson = require("cjson")
            
            local red = redis:new()
            red:set_timeout(1000)
            local ok, err = red:connect("127.0.0.1", 6379)
            if not ok then
                ngx.say(cjson.encode({code = 503, message = "Redis连接失败"}))
                return
            end
            
            local client_ip = ngx.var.remote_addr
            local rate_key = "rate_limit:" .. client_ip
            
            red:set("cb:status", "closed")
            red:set("cb:fail_count", 0)
            red:set("cb:health_count", 0)
            red:set("cb:total_count", 0)
            red:del("cb:open_ttl")
            red:del(rate_key)
            
            ngx.say(cjson.encode({code = 200, message = "限流与熔断状态已重置"}))
            red:set_keepalive(10000, 100)
        }
    }
}

📌 配置说明摘要

  • /resty.redis 做分布式限流,resty.http 转发后端并做成功/失败统计
  • /status:查看当前限流计数、限流状态(limitedremaining)与熔断相关字段
  • /reset:清空本示例中的熔断与当前 IP 限流键(仅供本地与演示;生产必须鉴权、限源或移除)
  • 限流参数:每个客户端 IP 在 10 秒 时间窗口内最多 100 次请求(固定窗口,窗口长度由首次命中时的 EXPIRE 决定)
  • 熔断触发cb:fail_count >= 10,或 cb:total_count > 10 且失败率 > 50%
  • 熔断恢复cb:open_ttl 对应键 30 秒 TTL 过期 后进入 half-open;探测成功则恢复 closed(注:本示例返回的 open_ttl 为配置值,不是实时剩余秒数)

4、限流、降级、熔断测试

因为限流根据IP进行,所以为了避免不必要的麻烦,本章节所有访问都使用实际IP替代localhost

重载配置:

nginx -s reload

限流测试:

# 重置状态
curl http://127.0.0.1:8025/reset

# 终端1:4个线程、10个并发连接,通常会快速超过「10秒100次」阈值
wrk -t4 -c10 -d120s http://127.0.0.1:8025/

# 终端2:查看状态
curl -s http://127.0.0.1:8025/status | jq .

熔断测试:

# 重置状态
curl http://127.0.0.1:8025/reset

# 终端1:错误请求(触发熔断)
wrk -t4 -c20 -d120s http://127.0.0.1:8025/?error=500

# 终端2:查看状态
curl -s http://127.0.0.1:8025/status | jq .

降级验证:

以下 429 / 502 / 503 为不同阶段可能出现的示例响应,不会在同一时刻同时出现:

  • 429:触发限流(同一 IP 在 10 秒窗口内超过 100 次)
  • 502:后端异常但尚未进入熔断打开阶段
  • 503:熔断已打开
# 在进行限流、熔断、降级测试时,新开终端进行验证
curl http://127.0.0.1:8025/
{"code": 429, "message": "请求过于频繁,请稍后再试"}
{"code": 502, "message": "后端服务异常", "data": {"fallback": "已触发降级保护"}}
{"code": 503, "message": "服务已熔断", "data": {"fallback": "系统繁忙,请稍后重试", "retry_after": 25}}

5、生产环境提示

  • lua-resty-http:高并发下建议为客户端配置连接池或复用连接(如按官方文档使用 set_keepalive),避免每次请求新建 TCP 带来的延迟与 ephemeral 端口压力。
  • lua-resty-redis:对 resty.redis 使用 set_keepalive(max_idle_timeout, pool_size) 并按并发量调大 pool_size;连接池不足会导致频繁建连、排队等待与延迟抖动。必要时结合 Redis 端 maxclients、网络与超时参数一起压测评估。
  • /reset:勿对公网开放;可以配合鉴权或由运维限制仅内网管理端访问

七、备忘

1、查看共享内存使用情况

在 Lua 中添加调试接口:

location /status/shared_dict {
    default_type application/json;
    content_by_lua_block {
        local circuit_breaker = ngx.shared.circuit_breaker
        local keys = circuit_breaker:get_keys()
        local result = {}
        for _, key in ipairs(keys) do
            result[key] = circuit_breaker:get(key)
        end
        ngx.say(require("cjson").encode(result))
    }
}

⚠️ 生产环境中请移除或保护调试接口,避免信息泄露

2、限流熔断策略总结

方案 适用场景 特点
limit_req 限制请求速率 基于漏桶算法,平滑流量
limit_conn 限制并发连接 防止单个客户端占用过多资源
Lua 熔断 服务异常保护 自动熔断与恢复,保护后端服务
分布式限流+熔断 多节点共享限流计数与熔断状态 Redis 固定窗口 INCR + 请求路径统计;依赖第一节 Lua 库;降级 JSON;/reset 须在生产保护

💡 合理组合使用这些策略,可以有效保护后端服务的稳定性