第四章:API网关设计——微服务的“守门人”

聊到微服务架构,有个角色你绕不开——API网关。说白了,它就是整个系统的“前门”。所有外部请求,都得先经过它,再由它分发到后端的各个服务。

我记得刚接触微服务那会儿,团队直接把服务地址暴露给客户端。结果呢?每次重构接口,客户端就得跟着改。后来加了网关,整个世界清净了。嗯,这就是网关的第一个价值——解耦。

4.1 网关的作用与模式

网关到底干了什么?我总结下来,核心就三件事:

  • 请求路由:把/user/**转到用户服务,把/order/**转到订单服务
  • 横切关注点:认证、限流、日志、监控——这些每个服务都要做的事,统一在网关层搞定
  • 协议转换:外部可能是HTTP,内部可能是gRPC,网关帮你做翻译

你想想看,如果没有网关,每个服务都得自己写认证逻辑。那代码得多冗余?维护起来得多痛苦?

网关的常见模式:

  • 边缘网关:面向客户端,处理外部请求。这是最常见的模式。
  • 内部网关:服务之间的通信也走网关,方便做灰度发布、流量复制。
  • BFF模式:为每种客户端(Web、iOS、Android)各建一个网关。我在项目中用过这个模式,效果不错——每个端可以定制自己的聚合接口。

我的经验:别把网关当成“万能胶”。有些团队喜欢在网关里写业务逻辑,比如数据聚合、字段转换。我个人建议,网关只做路由和横切关注点。业务逻辑还是放到后端服务里,否则网关会变得臃肿不堪。

4.2 基于Kong的网关搭建

Kong是我用得最多的网关之一。它基于OpenResty(Nginx + Lua),性能没得说。而且插件生态丰富,开箱即用。

搭建Kong其实很简单。我习惯用Docker Compose来部署:

# docker-compose.yml
version: '3.8'

services:
  kong-database:
    image: postgres:13
    environment:
      POSTGRES_DB: kong
      POSTGRES_USER: kong
      POSTGRES_PASSWORD: kong_pass
    networks:
      - kong-net

  kong-migrations:
    image: kong:3.5
    command: "kong migrations bootstrap"
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_PASSWORD: kong_pass
    networks:
      - kong-net
    depends_on:
      - kong-database

  kong:
    image: kong:3.5
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_PASSWORD: kong_pass
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
      KONG_ADMIN_LISTEN: 0.0.0.0:8001
    ports:
      - "8000:8000"   # 代理端口
      - "8001:8001"   # 管理端口
    networks:
      - kong-net
    depends_on:
      - kong-migrations

networks:
  kong-net:
    driver: bridge

启动后,通过Admin API配置路由。举个例子,把/user/**路由到用户服务:

# 添加服务
curl -i -X POST http://localhost:8001/services \
  --data "name=user-service" \
  --data "url=http://user-service:8080"

# 添加路由
curl -i -X POST http://localhost:8001/services/user-service/routes \
  --data "paths[]=/user" \
  --data "strip_path=false"

为什么会这样配置?strip_path=false的意思是保留路径前缀。比如请求/user/info,转发到后端时还是/user/info。如果设为true,就会变成/info。这个细节,我在项目里踩过坑——有一次忘了设置,后端接口全404了。

4.3 基于Ocelot的网关搭建

如果你的技术栈是.NET,Ocelot是个不错的选择。它是轻量级的,配置驱动,适合中小型项目。

Ocelot的配置核心是一个JSON文件。我直接贴一个典型配置:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/user/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "user-service",
          "Port": 8080
        }
      ],
      "UpstreamPathTemplate": "/user/{everything}",
      "UpstreamHttpMethod": [ "GET", "POST" ]
    },
    {
      "DownstreamPathTemplate": "/api/order/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "order-service",
          "Port": 8081
        }
      ],
      "UpstreamPathTemplate": "/order/{everything}",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:5000"
  }
}

你看,配置很直观。UpstreamPathTemplate是外部路径,DownstreamPathTemplate是内部路径。Ocelot会自动做路径映射。

注意:Ocelot默认不支持热更新。修改配置后需要重启应用。我曾经在生产环境吃过这个亏——改了路由配置,忘了重启,结果新接口一直404。后来我用了Consul做动态配置,才解决了这个问题。

4.4 请求路由与限流

路由是网关的基本功。但光有路由还不够,你还得考虑限流。为什么?因为微服务架构下,一个服务挂了,可能引发雪崩效应。

限流的常见算法有四种:

算法 原理 优缺点
令牌桶 以固定速率往桶里放令牌,请求消耗令牌 允许突发流量,实现简单
漏桶 请求先进桶,以固定速率流出 流量平滑,但无法应对突发
固定窗口 统计单位时间内的请求数 实现简单,但窗口边界有毛刺
滑动窗口 将时间窗口细分为多个小格 比固定窗口更平滑,但内存占用高

我个人习惯用令牌桶算法。它允许一定的突发流量,又不会让系统过载。在Kong里,开启限流插件很简单:

# 启用限流插件
curl -i -X POST http://localhost:8001/services/user-service/plugins \
  --data "name=rate-limiting" \
  --data "config.minute=100" \
  --data "config.hour=5000" \
  --data "config.policy=local"

这里配置了每分钟最多100次请求,每小时最多5000次。policy=local表示使用本地计数器。如果是集群部署,建议用redis模式,保证数据一致性。

避坑指南:我曾经在限流阈值上吃过亏。一开始设得太宽松,结果某个客户端疯狂调用,把后端服务打垮了。后来我学乖了——先设一个保守的阈值,然后根据监控数据逐步调优。记住,限流不是目的,保护系统才是。

说到路由,还有一个细节:灰度发布。你可以通过网关的权重路由,把一部分流量引到新版本服务上。比如:

# Kong的权重路由(通过upstream配置)
curl -i -X POST http://localhost:8001/upstreams \
  --data "name=user-upstream"

curl -i -X POST http://localhost:8001/upstreams/user-upstream/targets \
  --data "target=user-service-v1:8080" \
  --data "weight=90"

curl -i -X POST http://localhost:8001/upstreams/user-upstream/targets \
  --data "target=user-service-v2:8080" \
  --data "weight=10"

这样,90%的流量走v1,10%的流量走v2。观察一段时间,如果v2没问题,再逐步提高权重。嗯,这个技巧我在生产环境用了很多次,效果很好。

4.5 小结

API网关是微服务架构的“守门人”。它帮你统一处理路由、限流、认证等横切关注点。Kong和Ocelot是两个不错的选择——前者功能强大,后者轻量灵活。

最后提醒一句:网关虽好,但别滥用。不要把业务逻辑塞进网关,保持它的“薄”和“快”。毕竟,网关是流量的入口,它的性能直接影响整个系统的响应速度。