【架构实战】API网关设计与演进:从Nginx到自研网关

【架构实战】API网关设计与演进:从Nginx到自研网关

一、场景:Nginx 配置到 5000 行后的崩溃

2022 年,我司 API 网关用的是 Nginx + OpenResty + Lua。

刚开始挺爽------十几行 location 就能把请求代理到后端服务。但两年后变成了 5000 行的 nginx.conf

nginx 复制代码
# 想象一下 5000 行这种配置
location /api/order/ {
    set $target "order-service-v2";
    proxy_pass http://$target;
}

location /api/payment/ {
    set $target "payment-service-v1";
    rewrite_by_lua_block {
        -- 100 行自定义鉴权逻辑
    }
    proxy_pass http://$target;
}

问题接踵而来:

  • 配置地狱:修改一个路由规则要在 5000 行里翻半天
  • 灰度不灵活:想按用户 ID 分流,Lua 脚本写了 200 行
  • 动态扩容不友好:后端服务扩缩容需要 reload Nginx
  • 可观测性差 :出问题了只能 tail -f access.log,没有集中式的监控面板
  • 团队维护成本高:只有两个人懂那 5000 行 Nginx 配置

最终决定:从 Nginx+OpenResty 出发,经过 Kong 的短暂过渡,最终走向自研网关。这是一条充满坑的路,也是本文要分享的全过程。


二、网关的核心职责

在做技术选型之前,先梳理清楚 API 网关到底要做什么:

复制代码
                      ┌──────────────┐
                      │   客户端      │
                      └──────┬───────┘
                             │
                      ┌──────▼───────┐
                      │   WAF/CDN    │
                      └──────┬───────┘
                             │
        ┌────────────────────▼────────────────────┐
        │              API 网关                    │
        │                                         │
        │  ┌──────┐ ┌──────┐ ┌──────┐ ┌───────┐ │
        │  │鉴权  │ │限流  │ │路由  │ │协议   │ │
        │  │OAuth │ │令牌桶│ │灰度  │ │转换   │ │
        │  └──────┘ └──────┘ └──────┘ └───────┘ │
        │                                         │
        │  ┌──────┐ ┌──────┐ ┌──────┐ ┌───────┐ │
        │  │熔断  │ │重试  │ │日志  │ │监控   │ │
        │  │降级  │ │策略  │ │审计  │ │告警   │ │
        │  └──────┘ └──────┘ └──────┘ └───────┘ │
        └─────────┬───────────┬───────────────────┘
                  │           │
          ┌───────▼───┐ ┌─────▼──────┐
          │ 订单服务   │ │ 支付服务    │
          └───────────┘ └────────────┘

网关不是在请求链路上多加一跳那么简单------它是流量治理的"咽喉要道"。


三、阶段一:Nginx + OpenResty --- 最朴素的起点

3.1 架构

复制代码
客户端 → Nginx(OpenResty) → Lua 鉴权 → 上游服务
                │
                ▼
           Redis (限流计数器)
           Consul (服务发现)

3.2 典型配置

nginx 复制代码
# 限流配置
lua_shared_dict limit_req_store 100m;

server {
    listen 80;

    location /api/ {
        access_by_lua_block {
            -- 限流逻辑
            local limit = require "resty.limit.req"
            local lim, err = limit.new("limit_req_store", 200, 100)
            local delay, err = lim:incoming(ngx.var.binary_remote_addr, true)
            if not delay then
                if err == "rejected" then
                    ngx.exit(503)
                end
            end
        }

        # 动态路由
        set_by_lua_block $target_host {
            local consul = require "resty.consul"
            local instances = consul:get_service("order-service")
            -- 随机选一个实例
            return instances[math.random(#instances)].address
        }

        proxy_pass http://$target_host;
    }
}

3.3 Nginx + OpenResty 的局限

本质上是配置文件驱动,不是真正的 API 管理平台:

功能 Nginx + OpenResty 期望
动态路由 Lua 脚本硬编码 管理后台点选规则
灰度发布 需要改 Lua 按 Request Header/Cookie 动态分流
监控面板 实时 QPS、延迟分布
配置热更新 reload Nginx 实时生效
多租户 多套 Nginx 逻辑隔离

四、阶段二:Kong --- 成熟的过渡方案

4.1 为什么选 Kong

Kong 基于 OpenResty,提供了 Plugin 机制和管理后台。对我们来说迁移成本最低------OpenResty 的技能可以复用。

4.2 Kong 的 Plugin 体系

lua 复制代码
-- 自定义 Kong Plugin:按用户等级分流
local BasePlugin = require "kong.plugins.base_plugin"

local UserLevelRouteHandler = BasePlugin:extend()

function UserLevelRouteHandler:new()
    UserLevelRouteHandler.super.new(self, "user-level-route")
end

function UserLevelRouteHandler:access(conf)
    UserLevelRouteHandler.super.access(self)

    -- 从 Header 或 JWT 中解析用户等级
    local user_level = kong.request.get_header("X-User-Level") or "normal"

    -- VIP 用户路由到高性能集群
    if user_level == "vip" then
        kong.service.set_upstream("order-service-vip")
    end
end

return UserLevelRouteHandler

4.3 Kong 暴露的问题

用了半年 Kong 后,新的痛点浮出水面:

  1. Plugin 开发仍然是 Lua:团队没人愿意写 Lua,招人也困难
  2. 数据库依赖(PostgreSQL):又多了一个需要维护的中间件
  3. 性能瓶颈 :高峰期 Kong 的 workers 进程 CPU 打到 95%,因为 Plugin 里的 Lua 正则匹配太耗时
  4. 复杂路由策略不足:需要按照订单金额区间路由到不同的折扣服务,Kong 的路由表达能力不够

到这一步我们意识到:我们需要的不是"更好的 Nginx",而是一个用 Go/Java 写的、面向业务路由的网关。


五、阶段三:自研网关 --- 面向业务的设计

5.1 设计原则

复制代码
1. 动态配置:配置实时热更新,不重启不中断
2. 高性能:纯异步 IO,单机支撑 10万 QPS
3. 可扩展:Plugin 链式调用,热插拔
4. 可观测:Metrics、Tracing、Logging 三件套
5. 对开发者友好:Java + 注解声明式配置

5.2 整体架构

复制代码
┌─────────────────────────────────────────────────────────┐
│                   自研 API 网关                           │
│                                                          │
│  ┌───────────────────────────────────────────────────┐  │
│  │                 Netty Server (NIO)                 │  │
│  │       Reactor 主从线程模型                          │  │
│  └──────────────────────┬────────────────────────────┘  │
│                         │                                │
│  ┌──────────────────────▼────────────────────────────┐  │
│  │              Plugin Chain(责任链模式)               │  │
│  │                                                    │  │
│  │  ┌─────┐   ┌─────┐   ┌─────┐   ┌─────┐   ┌─────┐ │  │
│  │  │鉴权 │──▶│限流 │──▶│路由 │──▶│灰度 │──▶│负载 │ │  │
│  │  │Plugin│  │Plugin│  │Plugin│  │Plugin│  │均衡 │ │  │
│  │  └─────┘   └─────┘   └─────┘   └─────┘   └─────┘ │  │
│  │                                                    │  │
│  └────────────────────────────────────────────────────┘  │
│                         │                                │
│  ┌──────────────────────▼────────────────────────────┐  │
│  │              Route Matcher                          │  │
│  │   前缀/正则匹配 → 含灰度规则 → 选后端实例             │  │
│  └────────────────────────────────────────────────────┘  │
│                         │                                │
│  ┌──────────────────────▼────────────────────────────┐  │
│  │           Backend Connection Pool                   │  │
│  │     连接复用 / 健康检查 / 主动探活                    │  │
│  └────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

5.3 核心代码:Plugin 链与路由引擎

java 复制代码
/**
 * Plugin 接口------所有功能模块的抽象
 */
public interface GatewayPlugin {
    /** Plugin 名称 */
    String name();

    /** 优先级,数值越小越先执行 */
    int order();

    /** 是否启用 */
    boolean enabled();

    /** 执行 Plugin 逻辑 */
    Mono<GatewayResponse> execute(GatewayRequest request, PluginChain chain);
}

/**
 * 责任链:顺序调用 Plugin
 */
public class PluginChain {
    private final List<GatewayPlugin> plugins;
    private int currentIndex = 0;

    public PluginChain(List<GatewayPlugin> plugins) {
        this.plugins = plugins.stream()
            .filter(GatewayPlugin::enabled)
            .sorted(Comparator.comparingInt(GatewayPlugin::order))
            .collect(Collectors.toList());
    }

    public Mono<GatewayResponse> next(GatewayRequest request) {
        if (currentIndex >= plugins.size()) {
            // 所有 Plugin 执行完成 → 代理到后端
            return proxyToBackend(request);
        }
        GatewayPlugin plugin = plugins.get(currentIndex++);
        return plugin.execute(request, this);
    }
}

/**
 * 限流 Plugin 实现:基于 Redis 滑动窗口 + Lua
 */
@Component
public class RateLimitPlugin implements GatewayPlugin {

    @Override
    public String name() { return "rate-limit"; }

    @Override
    public int order() { return 200; }  // 在鉴权之后

    @Override
    public boolean enabled() { return true; }

    @Override
    public Mono<GatewayResponse> execute(GatewayRequest request, PluginChain chain) {
        RateLimitRule rule = rateLimitConfig.matchRule(request);
        if (rule == null) {
            // 无匹配规则,放行
            return chain.next(request);
        }

        String key = buildLimitKey(request, rule);
        return redisTemplate.execute(luaScript, key, rule.getQps(), rule.getWindowSeconds())
            .flatMap(allowed -> {
                if (allowed) {
                    return chain.next(request);
                }
                return Mono.just(
                    GatewayResponse.tooManyRequests("请求频率超限"));
            });
    }
}

/**
 * 路由 + 灰度 Plugin
 */
@Component
public class RoutePlugin implements GatewayPlugin {

    @Override
    public String name() { return "route"; }

    @Override
    public int order() { return 300; }

    @Override
    public Mono<GatewayResponse> execute(GatewayRequest request, PluginChain chain) {
        // 匹配路由规则
        RouteConfig route = routeMatcher.match(request);

        // 灰度规则匹配
        Backend backend = route.matchCanaryRule(request);
        request.setTargetBackend(backend);

        return chain.next(request);
    }
}

5.4 配置动态化设计

java 复制代码
/**
 * 配置变更监听:基于 Nacos / Apollo
 */
@Component
public class RouteConfigListener {

    @NacosConfigListener(dataId = "gateway-routes", group = "DEFAULT_GROUP")
    public void onRouteConfigChange(String configJson) {
        List<RouteConfig> newRoutes = JsonUtils.fromJson(configJson,
                new TypeReference<List<RouteConfig>>() {});

        // 原子替换路由表
        routeTable.set(newRoutes);

        log.info("路由配置热更新: {} 条路由规则", newRoutes.size());
    }
}

六、网关演进对比

维度 Nginx + OpenResty Kong 自研网关
配置管理 配置文件 管理后台 + DB Nacos 动态配置
热更新 reload (会有短暂中断) 管理员 API 配置变更实时生效
灰度发布 Lua 脚本硬编码 Plugin 实现 声明式路由规则
可观测性 access.log Kong Dashboard Prometheus + Grafana
开发语言 Lua + Nginx C 模块 Lua Java
性能 极高(C + LuaJIT) 高(Netty)
团队维护成本 Lua 能力要求高 Java 生态系统
单机 QPS 50 万+ 10 万+ 15 万+

七、自研网关的性能基准测试

复制代码
测试环境:16 核 32G, Netty (EventLoopGroup=8)
后端:静态返回 200 OK, 1ms 延迟

┌─────────────────────────────────────┐
│  QPS:   152,000 req/s               │
│  P50:   0.8ms                       │
│  P99:   3.2ms                       │
│  P999:  8.5ms                       │
│  CPU:   65%(8 个 worker 线程)       │
│  内存:  2.3GB                       │
│  连接数: 50,000 并发(HttpClient)   │
└─────────────────────────────────────┘

对比 Kong 同环境下 12 万 QPS(P99 6ms),自研网关吞吐提升约 30%,延迟降低约 50%。

实际瓶颈不在网关本身,而在后端的响应速度------网关引入的额外延迟不到 1ms。


八、踩坑实录

坑1:Nginx reload 导致长连接中断

直接 kill -HUP 会导致已有连接全部断开,影响正在进行中的文件上传。改用 nginx -s quit + 新进程平滑升级,但仍有短暂中断。

:自研网关基于 Netty,通过 Signal.handle() 注册优雅关闭,30 秒内排干已有请求。

坑2:Plugin 链顺序写反导致鉴权绕过

限流 Plugin 排在鉴权前面------没登录的用户也能消耗限流额度,恶意利用导致大量 429。

:鉴权 Plugin order=100,限流 Plugin order=200,确保"先鉴权、后限流"。

坑3:Kong 数据库成为单点瓶颈

Kong 的路由查询依赖 PostgreSQL 的连接池,高峰期 DB 连接池耗尽,Kong Admin API 无响应。

:自研网关不依赖外部数据库存储路由,配置全量加载到内存。


九、总结

从 Nginx 到自研网关的演进,核心脉络是:

  1. Nginx + OpenResty 是顶配起点:单机 50 万 QPS,但配置难以管理
  2. Kong 是成熟的过渡方案:Plugin 体系 + 管理后台,适合中等规模场景
  3. 自研网关是终局选择:当路由规则从几十条变成几百条、灰度策略从按 Header 变成按用户画像时,你需要一个"懂业务"的网关

教训

  • 网关的瓶颈永远不在网关本身------在后端服务的响应速度。网关引入的延迟应控制在 1ms 以内
  • 配置管理比性能更重要------10 万 QPS 够了,但 5000 行 Nginx 配置的风险远超性能不足
  • 自研不是目的,场景匹配才是------团队没有 Netty 高手就不要自研,Kong 或者 APISIX 一样能打
  • 可观测性是生命线------网关是流量的"咽喉要道",出了问题必须能在 10 秒内定位到是哪个 Plugin、哪条路由

架构演进没有终点------下一个阶段,我们在考虑把网关接入 Service Mesh,让网关和 Sidecar 协作,走向全链路流量治理。


作者:架构实战团队

日期:2026-07-04

标签:#API网关 #Nginx #Kong #自研网关 #微服务 #架构演进

相关推荐
ai生成式引擎优化技术2 小时前
从参数驱动到认知行为驱动:SAI范式的理论转向与WSaiOS认知内核架构
python·架构·django·virtualenv·pygame
梦帮科技2 小时前
从零到一构建音乐版权公链:RNS Token 区块链基础设施与智能合约架构全解析
架构·区块链·智能合约
大明者省2 小时前
四大模态大模型训练体系全解析(架构+范式+分布式+算力成本·)
笔记·分布式·架构
木木的木云3 小时前
从零构建微前端框架:PavilionMfe 设计揭秘
前端·架构·vite
格子软件3 小时前
2026年分布式GEO代理架构:多租户动态数据源隔离与流控源码解构
java·vue.js·人工智能·分布式·架构·vue·geo
搭贝3 小时前
基于低代码平台的异构系统集成与业财一体化架构实践
架构
nbsaas-boot3 小时前
微服务架构下的分布式事务解决方案深度对比与实战选型
分布式·微服务·架构
AI-好学者4 小时前
MCP企业运用全面知识点-基础篇
服务器·开发语言·网络·人工智能·python·架构
ai生成式引擎优化技术4 小时前
WSaiOS:面向认知资产与工程化认知流程的智能操作系统架构
python·架构·django·virtualenv·pygame