08. API 网关设计与实践

API 网关核心功能、Kong vs APISIX vs Spring Cloud Gateway 对比、限流/路由/认证/插件化架构

API 网关是微服务架构的"门面",统一管控入口流量并提供协议适配能力。随着服务数量的增多,APISIX、Kong、Envoy 等网关的定位逐渐从"路由转发器"演进为"云原生流量治理中枢"。本文将系统性地拆解网关架构设计模式,通过代码示例演示路由、限流、认证和可观测性能力的落地,并给出几种典型场景下的技术选型建议。

1. 核心功能

功能说明
路由根据 URL、Header 或请求参数将流量转发到不同后端服务
负载均衡支持轮询、权重、一致性哈希等多种调度策略
认证鉴权JWT、OAuth2、API Key 以及 mTLS 终端
限流熔断令牌桶/漏桶/滑动窗口,防止后端被突发流量压垮
协议转换HTTP 转 gRPC、REST 转 GraphQL 、WebSocket 代理
日志监控分布式追踪 ID 传递、结构化的访问日志、Prometheus 指标
灰度发布按权重、用户标签、客户端 IP 进行请求分流

API 网关的设计初衷是解耦外部客户端与内部微服务。没有网关时,客户端需感知每个后端服务的地址与协议,存在强耦合;引入网关后,客户端仅与网关这一统一入口交互,地址聚合、协议转换、安全防护均集中在网关层处理,大幅降低了维护和演进成本。

2. 方案对比

特性KongAPISIXSpring Cloud GatewayNginx
架构OpenResty (Lua)OpenResty (Lua)Java (Reactor)C
性能高极高中极高
插件生态丰富极丰富Spring 生态模块
动态配置支持无需重启需重启需 reload
K8s 集成支持原生 Ingress一般需适配
控制面板Kong ManagerDashboard无原生无
多语言插件Lua/GoLua/Go/Python /WASMJavaC/Lua

从部署形态来看,Kong 和 APISIX 都依托 OpenResty 的高性能事件驱动模型,单节点 QPS 可达数万。APISIX 在 etcd 之上构建了控制面与数据面分离的架构,支持毫秒级配置同步,无需重启即可热加载路由和插件,特别适合高频迭代的云原生场景。Spring Cloud Gateway 深度融入 Java 生态,通过 Reactor 和 Netty 实现非阻塞 I/O,在需要与 Spring Security、Spring Cloud Config 等组件联动的场景下有天然优势。Nginx 作为老牌反向代理,处理静态资源和七层转发的性能无可挑剔,但在动态插件扩展和 API 管理方面不如现代云原生网关灵活。

3. 网关架构模式对比

在引入 API 网关之前,理解不同部署模式的适用场景至关重要。常见的三种模式分别为 Edge Gateway、Backend-for-Frontend(BFF)和 Sidecar Gateway,各自权衡了职责粒度与团队自治空间。

3.1 Edge Gateway(边缘网关)

Edge Gateway 部署在公网入口,对所有传入流量进行第一道过滤。它的职责聚焦于安全、限速和全局路由,不涉及业务逻辑拼接。例如,在标准的三层架构中,Edge Gateway 负责 SSL 终端、WAF、DDoS 防护,之后将合法请求按路径分发给下游微服务集群。这种模式的好处是职责单一、维护成本低,适用于前端统一、后端服务协议相对一致的场景。

3.2 Backend-for-Frontend(BFF)

BFF 模式为每一种客户端(Web、iOS、Android、小程序)独立部署一个网关层。每个 BFF 根据自身客户端的 UI 需求,聚合多个下游微服务的接口,返回定制化的数据结构。例如,移动端首页可能同时需要用户信息、推荐列表和未读消息,BFF 层会并行调用三个下游服务并合并结果,避免客户端发出多次请求。

// Node.js BFF 示例:聚合用户、推荐和消息服务
const express = require('express');
const axios = require('axios');
const app = express();

const SERVICES = {
  user: 'http://user-service:8080',
  rec: 'http://recommend-service:8080',
  msg: 'http://message-service:8080'
};

app.get('/api/mobile/home', async (req, res) => {
  const headers = { Authorization: req.headers.authorization };
  try {
    const [user, rec, msg] = await Promise.all([
      axios.get(`${SERVICES.user}/profile`, { headers, timeout: 500 }),
      axios.get(`${SERVICES.rec}/feed`, { headers, timeout: 800 }),
      axios.get(`${SERVICES.msg}/unread`, { headers, timeout: 500 })
    ]);
    res.json({
      profile: user.data,
      feed: rec.data,
      unreadCount: msg.data.count
    });
  } catch (e) {
    res.status(502).json({ error: '聚合服务超时', detail: e.message });
  }
});

app.listen(3000, () => console.log('BFF 服务启动于 3000 端口'));

上述代码使用 Express 和 axios 并行请求三个下游服务,通过 Promise.all 实现并发调用。当任一服务超时或返回异常时,BFF 层捕获错误并返回统一的降级响应,避免客户端直接暴露底层服务故障。

3.3 Sidecar Gateway(边车网关)

Sidecar Gateway 以 Envoy 为代表,与应用容器一同部署在同一个 Pod 或进程空间中,通过数据面代理实现服务间通信治理。这种模式是服务网格(Service Mesh)的核心组成部分,适用于东-西向(服务间)流量的精细化管控。Istio 在此基础上叠加了控制面 Pilot,实现动态流量策略下发。

选择建议:

  • 如果团队规模较小、客户端单一,选用 Edge Gateway 即可满足大部分需求。
  • 如果存在多平台客户端且数据结构差异大,引入 BFF 可显著提升前端开发效率。
  • 如果已采用 Kubernetes 且希望对服务间流量进行细粒度熔断、重试、超时控制,使用 Sidecar Gateway(Service Mesh)。

4. 路由与流量管理

路由是网关最基础也是最重要的能力。现代网关不仅支持静态路径匹配,还需支持基于 Header、查询参数、 Cookie 的动态路由,以及流量比例分割。

4.1 动态路由配置

在 APISIX 中,通过 vars 可以基于请求属性定义复杂的路由匹配规则。以下示例展示了根据 X-Client-Version Header 将不同版本的客户端请求分发到不同上游的完整配置。

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: dynamic-version-route
  namespace: default
spec:
  http:
    - name: route-v2-clients
      match:
        hosts:
          - api.example.com
        paths:
          - /api/orders/*
        exprs:
          - subject:
              scope: Header
              name: X-Client-Version
            op: GE
            value: "2.0"
      backends:
        - serviceName: order-service-v2
          servicePort: 8080
    - name: route-v1-clients
      match:
        hosts:
          - api.example.com
        paths:
          - /api/orders/*
      backends:
        - serviceName: order-service-v1
          servicePort: 8080

上述配置中,route-v2-clients 使用 exprs 表达式匹配 X-Client-Version >= 2.0 的请求,将其转发到 order-service-v2;其余不符合条件的请求由 route-v1-clients 捕获并回落到旧版本服务。

4.2 金丝雀发布与 A/B 测试

金丝雀发布(Canary Release)的核心思想是将新版本服务以小比例流量先行暴露,观察稳定性后再逐步扩大流量占比。APISIX 的 traffic-split 插件可以精确控制比例。

#!/bin/bash
# 金丝雀发布:90% 流量走 v1,10% 走 v2
APISIX_ADMIN="http://apisix-admin:9180"
API_KEY="edd1c9f034335f136f87ad84b625c8f1"

curl -s "${APISIX_ADMIN}/apisix/admin/routes/canary-order" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/orders/*",
  "plugins": {
    "traffic-split": {
      "rules": [
        {
          "weighted_upstreams": [
            {
              "upstream": {
                "name": "order_v1",
                "type": "roundrobin",
                "nodes": {
                  "10.0.0.10:8080": 1,
                  "10.0.0.11:8080": 1
                }
              },
              "weight": 90
            },
            {
              "upstream": {
                "name": "order_v2",
                "type": "roundrobin",
                "nodes": {
                  "10.0.0.20:8080": 1
                }
              },
              "weight": 10
            }
          ]
        }
      ]
    }
  }
}'

该脚本通过 APISIX Admin API 创建一条路由,将 /api/orders/* 的流量按 9:1 切分到两个不同版本的上游。当 v2 版本通过监控指标确认无异常后,可以逐步上调 weight 至 50、80、100,完成平滑过渡。A/B 测试的实现思路类似,区别在于分流维度通常是用户特征(如用户 ID 哈希、地域)而非纯随机比例,网关可根据 X-User-Id 或 X-Region 等 Header 进行定向路由。

4.3 蓝绿部署在网关层的实现

蓝绿部署的关键是要求网关能够在两个完全独立的上游集群之间瞬间切换全部流量。APISIX 通过修改上游定义(Upstream)的节点列表即可实现零停机切换,因为路由规则本身引用的是上游的 ID,而非直接写死节点地址。

# 将 order_upstream 的节点从 blue 环境切换到 green 环境
curl -s "${APISIX_ADMIN}/apisix/admin/upstreams/order_upstream" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "type": "roundrobin",
  "nodes": {
    "10.0.0.30:8080": 1,
    "10.0.0.31:8080": 1
  }
}'

在上述命令中,只需将 nodes 字段替换为绿色环境的实例地址,所有引用 order_upstream 的路由会立即生效,无需逐条修改路由配置。

5. 认证与授权集成

在微服务体系中,每个服务都自行实现认证逻辑会导致代码冗余、密钥分散管理困难。将认证收敛到网关层是最佳实践,网关负责校验令牌合法性,后端服务专注于业务授权即可。

5.1 OAuth2 / OIDC 统一登录流程

APISIX 通过 openid-connect 插件可对接任意标准 OIDC 提供商(如 Keycloak、Okta、Auth0)。以下配置演示了完整的集成方式。

curl -s "${APISIX_ADMIN}/apisix/admin/routes/oidc-protected" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/protected/*",
  "plugins": {
    "openid-connect": {
      "client_id": "my-app",
      "client_secret": "super-secret",
      "discovery": "https://auth.example.com/.well-known/openid-configuration",
      "redirect_uri": "https://api.example.com/oidc/callback",
      "scope": "openid profile email",
      "bearer_only": true,
      "realm": "master"
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:8080": 1
    }
  }
}'

配置中 bearer_only: true 表示网关仅接受 Bearer Token 访问,不会主动重定向到登录页,适合前后端分离的 SPA 场景。验证通过后,网关会将解析出的用户声明(claims)通过 Header 透传给后端,例如 X-User-Id、X-User-Email,后端无需再次解析 JWT。

5.2 JWT 在网关层的校验与透传

APISIX 的 jwt-auth 插件支持 HS256、RS256 等多种算法。以下示例展示了在消费者(Consumer)级别的 JWT 配置。

# 1. 创建一个消费者并绑定 JWT 凭证
curl -s "${APISIX_ADMIN}/apisix/admin/consumers/leo" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "username": "leo",
  "plugins": {
    "jwt-auth": {
      "key": "leo-key",
      "secret": "my-256-bit-secret-string-here",
      "algorithm": "HS256"
    }
  }
}'

# 2. 创建需要 JWT 校验的路由
curl -s "${APISIX_ADMIN}/apisix/admin/routes/jwt-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/secure/*",
  "plugins": {
    "jwt-auth": {}
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:8080": 1
    }
  }
}'

# 3. 生成 JWT Token(使用 apisix 的 sign 接口或本地生成)
# 本地生成示例(Python)
python3 -c "
import jwt, time
payload = {
    'sub': 'leo',
    'iss': 'leo-key',
    'exp': int(time.time()) + 3600
}
token = jwt.encode(payload, 'my-256-bit-secret-string-here', algorithm='HS256')
print(token)
"

# 4. 携带 Token 发起请求
curl -H "Authorization: Bearer <token>" https://api.example.com/api/secure/data

在这个流程中,网关承担了解密和验证的职责,只有在签名有效且在有效期内时,请求才会被转发给后端。后端可以从 Authorization Header 中直接复用该 Token,也可以依赖网关注入的 X-Consumer-Username 进行权限判定。

5.3 外部授权:OPA 集成

对于复杂的授权策略(如 RBAC、ABAC),可以在网关层集成 Open Policy Agent(OPA),实现策略即代码(Policy as Code)。APISIX 的 opa 插件将请求上下文发送到 OPA 服务,由 OPA 执行 Rego 策略并返回允许或拒绝的决定。

# APISIX 路由配置,启用 OPA 插件
curl -s "${APISIX_ADMIN}/apisix/admin/routes/opa-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/admin/*",
  "plugins": {
    "opa": {
      "host": "opa-service:8181",
      "policy": "api_authz",
      "with_route": true,
      "with_consumer": true
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:8080": 1
    }
  }
}'

对应的 OPA Rego 策略文件 api_authz.rego 如下:

package api_authz

import future.keywords.if
import future.keywords.in

# 定义管理员角色可以访问的路径
allow if {
    input.consumer.username == "admin"
    input.request.headers["x-action"] == "manage"
}

# 普通用户只允许 GET 请求
allow if {
    input.request.method == "GET"
    input.consumer.username != "admin"
}

OPA 独立运行,策略更新无需重启网关或后端服务,实现了授权逻辑与业务代码的完全解耦。

6. 性能优化

网关作为流量入口,其性能瓶颈会直接影响整个系统的吞吐量和延迟。以下是几项关键的优化手段。

6.1 连接池复用与 HTTP/2

APISIX 基于 Nginx/OpenResty 的事件驱动模型,天然支持连接池复用。在反向代理场景中,与后端服务保持长连接(keepalive)可以避免频繁的 TCP 握手开销。APISIX 的上游配置中,可以通过 keepalive_pool 参数调优连接池大小。

curl -s "${APISIX_ADMIN}/apisix/admin/upstreams/http2-upstream" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "type": "roundrobin",
  "nodes": {
    "10.0.0.5:8080": 1,
    "10.0.0.6:8080": 1
  },
  "scheme": "https",
  "keepalive_pool": {
    "size": 320,
    "idle_timeout": 60,
    "requests": 1000
  }
}'

若后端服务支持 HTTP/2,网关可以在同一 TCP 连接上多路复用多个请求,显著降低连接数和头部压缩开销。对于 gRPC 服务,APISIX 提供 grpc-transcode 插件,可将 HTTP/JSON 请求转换为 gRPC 调用,桥接 REST 客户端与 gRPC 后端。

# 启用 gRPC 转码:HTTP JSON -> gRPC
curl -s "${APISIX_ADMIN}/apisix/admin/routes/grpc-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/grpc/hello",
  "plugins": {
    "grpc-transcode": {
      "proto_id": "1",
      "service": "helloworld.Greeter",
      "method": "SayHello"
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:50051": 1
    },
    "scheme": "grpc"
  }
}'

6.2 响应压缩与缓存策略

网关层启用 gzip/br 压缩可以减少传输体积,APISIX 通过 gzip 插件实现。

curl -s "${APISIX_ADMIN}/apisix/admin/routes/compress-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/static/*",
  "plugins": {
    "gzip": {
      "min_length": 20,
      "types": ["text/html", "application/json"]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:8080": 1
    }
  }
}'

对于读多写少的接口,可以在网关层配置响应缓存,APISIX 的 proxy-cache 插件支持内存和磁盘两级缓存。

curl -s "${APISIX_ADMIN}/apisix/admin/routes/cache-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/products/list",
  "plugins": {
    "proxy-cache": {
      "cache_zone": "disk_cache_one",
      "cache_key": ["$uri", "-", "$args"],
      "cache_bypass": ["$http_x_refresh"],
      "cache_method": ["GET"],
      "cache_http_status": [200],
      "hide_cache_headers": false,
      "no_cache": ["$http_pragma"]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:8080": 1
    }
  }
}'

上述配置对 GET /api/products/list 的响应进行缓存,缓存 Key 由 URI 和查询参数组成。客户端可通过发送 X-Refresh: true Header 绕过缓存强制回源。

7. 可观测性

网关的可观测性覆盖日志、指标和追踪三个维度。缺乏可观测能力的网关如同黑盒,故障排查将极其困难。

7.1 分布式追踪上下文传递

APISIX 原生支持 Zipkin、SkyWalking、OpenTelemetry 等追踪后端。以下配置启用 OpenTelemetry 插件,将网关作为追踪的第一个 Span 注入到请求中,并把 traceparent Header 透传给下游服务。

curl -s "${APISIX_ADMIN}/apisix/admin/routes/trace-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/orders/*",
  "plugins": {
    "opentelemetry": {
      "sampler": {
        "name": "always_on"
      },
      "additional_attributes": [
        "http.scheme",
        "http.host"
      ]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:8080": 1
    }
  }
}'

当请求进入网关时,OpenTelemetry 插件会自动生成或延续 trace-id 和 span-id,通过 W3C 标准的 traceparent Header 向下游传播。后端服务只需配置对应的 OpenTelemetry SDK,即可将自身的 Span 挂载到同一 Trace 树中,实现全链路追踪。

7.2 结构化访问日志与指标暴露

APISIX 的 http-logger 插件可以将访问日志以 JSON 格式批量发送到外部分析系统(如 Elasticsearch、Kafka、ClickHouse)。

curl -s "${APISIX_ADMIN}/apisix/admin/routes/log-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/*",
  "plugins": {
    "http-logger": {
      "uri": "http://log-collector:8080/ingest",
      "batch_max_size": 100,
      "inactive_timeout": 5,
      "concat_method": "json"
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.5:8080": 1
    }
  }
}'

在指标层面,APISIX 内置 Prometheus 端点,默认暴露于 localhost:9091/apisix/prometheus/metrics,包含 apisix_http_status、apisix_http_latency_bucket、apisix_nginx_http_current_connections 等核心指标。使用 Grafana 导入官方 Dashboard 即可实现可视化监控。

# Prometheus scrape 配置示例
scrape_configs:
  - job_name: 'apisix'
    static_configs:
      - targets: ['apisix-gateway:9091']
    metrics_path: '/apisix/prometheus/metrics'

8. APISIX 实践

8.1 路由配置

# 路由:匹配 /api/users/* 转发到 user-service
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: user-route
spec:
  http:
    - name: user-api
      match:
        hosts:
          - api.example.com
        paths:
          - /api/users/*
      backends:
        - serviceName: user-service
          servicePort: 8080
      plugins:
        - name: limit-count
          enable: true
          config:
            count: 1000
            time_window: 60
            rejected_code: 429
        - name: jwt-auth
          enable: true

上述 Kubernetes CRD 配置将符合 api.example.com/api/users/* 的请求路由到 user-service:8080,同时叠加了每分钟 1000 次的计数限流和 JWT 认证插件。

8.2 限流插件

# 令牌桶限流
APISIX_ADMIN="http://apisix-admin:9180"
API_KEY="edd1c9f034335f136f87ad84b625c8f1"

curl -s "${APISIX_ADMIN}/apisix/admin/routes/rate-limit-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/api/*",
  "plugins": {
    "limit-req": {
      "rate": 100,
      "burst": 50,
      "rejected_code": 429,
      "key": "consumer_name"
    },
    "limit-count": {
      "count": 1000,
      "time_window": 60,
      "key": "consumer_name",
      "rejected_code": 429
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "10.0.0.1:8080": 1,
      "10.0.0.2:8080": 1
    }
  }
}'

这里同时启用了 limit-req(漏桶/令牌桶)和 limit-count(固定窗口计数)两层限流,前者控制突发流量,后者控制总请求量,形成互补防护。

8.3 灰度发布

# 按流量比例灰度
curl -s "${APISIX_ADMIN}/apisix/admin/routes/canary-route" \
  -H "X-API-KEY: ${API_KEY}" -X PUT -d '
{
  "uri": "/app/*",
  "plugins": {
    "traffic-split": {
      "rules": [
        {
          "weighted_upstreams": [
            {
              "upstream": {
                "name": "upstream_v1",
                "type": "roundrobin",
                "nodes": {
                  "10.0.0.1:8080": 1
                }
              },
              "weight": 90
            },
            {
              "upstream": {
                "name": "upstream_v2",
                "type": "roundrobin",
                "nodes": {
                  "10.0.0.2:8080": 1
                }
              },
              "weight": 10
            }
          ]
        }
      ]
    }
  }
}'

9. Spring Cloud Gateway 实践

以下 Java 配置展示了 Spring Cloud Gateway 中路由定义、熔断降级和请求限流的完整实现。

package com.example.gateway;

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-service", r -> r
                .path("/api/users/**")
                .filters(f -> f
                    .stripPrefix(2)
                    .addRequestHeader("X-Gateway-Source", "spring-cloud-gateway")
                    .circuitBreaker(config -> config
                        .setName("userCircuitBreaker")
                        .setFallbackUri("forward:/fallback/user")
                        .setStatusCodes("500", "502", "503"))
                    .requestRateLimiter(rate -> rate
                        .setRateLimiter(redisRateLimiter())
                        .setKeyResolver(userKeyResolver())
                        .setStatusCode(HttpStatus.TOO_MANY_REQUESTS)))
                .uri("lb://user-service"))
            .route("order-service", r -> r
                .path("/api/orders/**")
                .filters(f -> f
                    .stripPrefix(2)
                    .retry(retryConfig -> retryConfig
                        .setRetries(3)
                        .setStatuses(HttpStatus.BAD_GATEWAY, HttpStatus.SERVICE_UNAVAILABLE)))
                .uri("lb://order-service"))
            .build();
    }

    @Bean
    public RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(100, 200);
    }

    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getHeaders().getFirst("X-User-Id")
        );
    }
}

这段代码定义了两条路由:user-service 路由配置了熔断、限流和自定义请求头,order-service 路由配置了重试策略。限流基于 Redis 实现令牌桶算法,Key 解析器使用 X-User-Id Header 作为限流维度,确保每个用户有独立的配额。

package com.example.gateway;

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtUtil jwtUtil;

    public AuthGlobalFilter(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        String jwt = token.substring(7);
        if (!jwtUtil.validate(jwt)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        // 将解析后的用户 ID 追加到请求头,供下游服务使用
        String userId = jwtUtil.extractUserId(jwt);
        ServerWebExchange mutated = exchange.mutate()
            .request(r -> r.header("X-User-Id", userId))
            .build();
        return chain.filter(mutated);
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

上述全局过滤器在每个请求进入网关时优先执行(getOrder() = -100),负责校验 JWT 的合法性和时效性。校验通过后将用户 ID 注入到请求头中,避免后端服务重复解析 Token,实现了认证的统一收口。

10. 网关选型对比

维度KongAPISIXTraefikZuulEnvoy Gateway
架构OpenResty/LuaOpenResty/LuaGoJava (阻塞)C++
性能高极高中高中极高
K8s Ingress需适配原生 CRD原生不适用原生 Gateway API
动态配置Admin API无需重启自动发现 (Provider)需重启xDS 协议
插件语言Lua/GoLua/Go/Python/WASMGo (中间件)Java/GroovyWASM/C++
服务网格不支持不支持不支持不支持Istio 数据面
开源协议Apache 2.0Apache 2.0MITApache 2.0Apache 2.0

选型建议:

  • APISIX:如果团队使用 Kubernetes 并希望获得极致性能、丰富插件和动态配置能力,APISIX 是当前开源网关中最均衡的选择。
  • Kong:适合已经有 Kong Enterprise 采购计划或需要内置开发者门户(Dev Portal)的企业。
  • Traefik:适合以 Docker Swarm 或轻量 K8s 为主的中小企业,配置简单、自动服务发现体验优秀。
  • Envoy Gateway:如果已采用 Istio 服务网格,使用 Envoy Gateway 作为南北向入口可以保持技术栈统一。
  • Spring Cloud Gateway:仅在已深度投入 Spring Cloud 生态、且对性能要求不极端的场景下选用。
  • Zuul 1.x:由于基于阻塞 I/O,性能已难以满足现代需求,建议迁移到 Zuul 2 或 Spring Cloud Gateway。

11. 网关设计要点

  1. 无状态化:网关本身必须无状态,会话、缓存等不应存储在本地磁盘或内存中,保证水平扩展时行为一致。
  2. 异步非阻塞:对于高并发场景,优先选择基于事件驱动的实现(如 Netty、OpenResty),避免线程池耗尽。
  3. 核心精简与插件化:网关内核应只包含路由和负载均衡,认证、限流、日志等功能通过插件扩展,降低维护复杂度。
  4. 配置热更新:生产环境不允许因路由变更而重启网关,必须支持毫秒级的配置推送或事件通知机制。
  5. 熔断降级:网关应具备对后端服务的健康感知能力,在异常时自动熔断并返回预定义的降级数据,防止故障扩散。
  6. 日志脱敏与合规:访问日志中禁止记录密码、Token、身份证号等敏感内容,必要时对日志进行分级存储和加密。

总结

场景推荐方案
K8s 原生高可用网关APISIX
企业级 API 管理(含计费、开发者门户)Kong Enterprise
Spring Cloud 生态深度集成Spring Cloud Gateway
超高性能静态/反向代理Nginx / OpenResty
服务网格统一入口(南北向 + 东西向)Envoy Gateway + Istio
Serverless / 无服务器架构AWS API Gateway / 阿里云 APIG

API 网关的设计没有银弹,关键在于根据团队规模、技术栈现状和演进路线选择最合适的方案。对于处于快速迭代期的云原生团队,APISIX 凭借其在动态配置、插件扩展和性能方面的综合优势,通常是最佳起点。而对于已经落地 Service Mesh 的组织,将 Envoy Gateway 作为入口网关可以实现南北向与东西向流量的统一治理,进一步降低运维复杂度。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「distributed-systems」更多文章

  1. 边缘计算架构与就近接入
  2. 分布式系统成本优化与容量治理
  3. 单体到分布式:遗留系统迁移与绞杀者模式