7.9.5 动态路由与API网关实践

动态路由与API网关实践重点在于以OpenResty作为统一流量入口,通过Lua实现路由、鉴权、灰度、限流与观测。典型架构如下:

文章图片

环境准备与安装示例#

以下以OpenResty社区版为例,适合在测试环境快速验证动态路由与网关能力。

# 1) 安装依赖(以CentOS 7为例)
yum -y install gcc pcre-devel openssl-devel zlib-devel git

# 2) 下载并安装OpenResty
cd /usr/local/src
wget https://openresty.org/download/openresty-1.21.4.2.tar.gz
tar -zxvf openresty-1.21.4.2.tar.gz
cd openresty-1.21.4.2
./configure --prefix=/usr/local/openresty
make -j4 && make install

# 3) 验证
/usr/local/openresty/nginx/sbin/nginx -v

关键命令解释
- ./configure --prefix:指定安装目录,便于统一管理。
- make -j4:并行编译,提升速度。
- nginx -v:验证版本与编译成功。

目录与配置文件建议#

# 推荐目录结构
/usr/local/openresty/nginx/
├── conf/
│   ├── nginx.conf
│   ├── conf.d/
│   │   └── gateway.conf
├── lua/
│   ├── router.lua
│   ├── auth.lua
│   └── limiter.lua
└── logs/

动态路由核心配置示例#

nginx.conf(关键片段)

worker_processes auto;
events { worker_connections 10240; }

http {
    lua_shared_dict routes 10m;
    lua_shared_dict limits 10m;
    lua_shared_dict tokens 10m;

    init_worker_by_lua_file /usr/local/openresty/nginx/lua/router.lua;

    server {
        listen 80;
        server_name localhost;

        location /api/ {
            access_by_lua_file /usr/local/openresty/nginx/lua/auth.lua;
            access_by_lua_file /usr/local/openresty/nginx/lua/limiter.lua;

            # 动态选择后端
            set $upstream_host '';
            access_by_lua_block {
                local route = ngx.shared.routes:get(ngx.var.request_uri) 
                if not route then
                    return ngx.exit(404)
                end
                ngx.var.upstream_host = route
            }

            proxy_pass http://$upstream_host;
        }
    }
}

router.lua(服务发现与路由加载)

-- /usr/local/openresty/nginx/lua/router.lua
local cjson = require "cjson.safe"
local http = require "resty.http"
local routes = ngx.shared.routes

local function refresh_routes()
    local httpc = http.new()
    local res, err = httpc:request_uri("http://127.0.0.1:8848/nacos/v1/ns/instances?serviceName=svcA", {
        method = "GET",
        timeout = 2000
    })
    if not res then
        ngx.log(ngx.ERR, "nacos fetch failed: ", err)
        return
    end

    -- 示例:将 /api/v1/* 路由到 svcA 实例
    local data = cjson.decode(res.body) or {}
    local instance = data.hosts and data.hosts[1]
    if instance then
        local host = instance.ip .. ":" .. instance.port
        routes:set("/api/v1/", host, 10) -- 10秒过期
    end
end

local ok, err = ngx.timer.every(5, refresh_routes)
if not ok then
    ngx.log(ngx.ERR, "timer failed: ", err)
end

auth.lua(鉴权示例:API Key)

-- /usr/local/openresty/nginx/lua/auth.lua
local token = ngx.req.get_headers()["X-API-Key"]
if not token then
    ngx.status = 401
    ngx.say('{"code":401,"msg":"missing api key"}')
    return ngx.exit(401)
end

-- 简化:演示用静态校验
if token ~= "demo-key" then
    ngx.status = 403
    ngx.say('{"code":403,"msg":"invalid api key"}')
    return ngx.exit(403)
end

limiter.lua(令牌桶限流示例)

-- /usr/local/openresty/nginx/lua/limiter.lua
local limit = ngx.shared.limits
local key = ngx.var.remote_addr
local current = limit:get(key) or 0

if current >= 10 then
    ngx.status = 429
    ngx.say('{"code":429,"msg":"rate limit"}')
    return ngx.exit(429)
end

limit:incr(key, 1, 0, 1)  -- 初始值1,过期1

预期效果
- /api/v1/ 自动路由到 Nacos 中的 svcA 实例。
- X-API-Key 不是 demo-key 直接拒绝。
- 单IP每秒超过10次请求返回 429。

运行与验证#

# 启动/重载
/usr/local/openresty/nginx/sbin/nginx
/usr/local/openresty/nginx/sbin/nginx -s reload

# 验证路由
curl -H "X-API-Key: demo-key" http://127.0.0.1/api/v1/health

# 触发限流
for i in {1..20}; do curl -s -o /dev/null -w "%{http_code}\n" \
  -H "X-API-Key: demo-key" http://127.0.0.1/api/v1/health; done

故障排查清单(含命令)#

  1. 路由不生效
# 检查共享字典是否写入
/usr/local/openresty/nginx/sbin/nginx -T | grep lua_shared_dict
# 查看错误日志
tail -f /usr/local/openresty/nginx/logs/error.log
  1. 鉴权失败
# 检查请求头
curl -I -H "X-API-Key: demo-key" http://127.0.0.1/api/v1/health
  1. Nacos不可达
# 测试注册中心接口
curl "http://127.0.0.1:8848/nacos/v1/ns/instances?serviceName=svcA"
  1. 限流过严
# 临时扩大限流阈值
# 修改 limiter.lua 中阈值,reload 生效

路由策略与实践建议(含示例)#

  • 路径版本路由
# /api/v1 -> svcA, /api/v2 -> svcB
  • Header 灰度
local env = ngx.req.get_headers()["X-Env"]
if env == "canary" then ngx.var.upstream_host = "10.0.0.10:8080" end
  • 用户哈希分流
local uid = ngx.req.get_uri_args()["uid"] or "0"
local slot = (ngx.crc32_short(uid) % 100)
if slot < 10 then ngx.var.upstream_host = "10.0.0.10:8080" end

练习题#

  1. auth.lua 改为支持 JWT(仅校验签名与过期时间)。
  2. 将路由表由“单实例”扩展为“多实例加权轮询”。
  3. 增加 X-Request-ID 并在日志中输出,便于链路追踪。
  4. 将限流由“IP级”改为“用户级+接口级”。

以上实践可构建具备动态路由、鉴权、限流、灰度与可观测能力的轻量级API网关,满足生产环境的稳定性与扩展性需求。