Architecture · #api-gateway#kong#apisix

API网关设计与实现:Kong/APISIX对比

2023.12.20 7 min 2.7k
// 目录 · contents

前言

API 网关是微服务架构中的核心基础设施组件,它作为系统的统一入口,承担路由转发、认证授权、限流熔断、协议转换等横切关注点。选择合适的 API 网关对系统的稳定性、性能和可维护性至关重要。本文将深入分析 API 网关的设计原则,并对比两个最流行的开源 API 网关——Kong 和 Apache APISIX。

API 网关的核心职责

graph TB
    Client[客户端] --> Gateway[API Gateway]

    subgraph Gateway功能
        Gateway --> Auth[认证授权]
        Gateway --> Route[路由转发]
        Gateway --> RateLimit[限流]
        Gateway --> CB[熔断]
        Gateway --> Transform[协议转换]
        Gateway --> Log[日志/监控]
        Gateway --> Cache[响应缓存]
        Gateway --> CORS[跨域处理]
    end

    Auth --> Services[后端服务集群]
    Route --> Services
    RateLimit --> Services
    CB --> Services

请求路由

网关根据请求的 URL、Header、Method 等信息将请求路由到对应的后端服务。

sequenceDiagram
    participant C as 客户端
    participant G as API Gateway
    participant US as 用户服务
    participant OS as 订单服务
    participant PS as 商品服务

    C->>G: GET /api/v1/users/123
    G->>G: 路由匹配: /api/v1/users/* → user-service
    G->>US: GET /users/123
    US-->>G: 200 OK
    G-->>C: 200 OK

    C->>G: POST /api/v1/orders
    G->>G: 路由匹配: /api/v1/orders → order-service
    G->>OS: POST /orders
    OS-->>G: 201 Created
    G-->>C: 201 Created

认证与授权

网关集中处理认证逻辑,后端服务无需重复实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-- Kong/APISIX 自定义认证插件示例 (Lua)
local _M = {}

function _M.access(conf)
local token = kong.request.get_header("Authorization")
if not token then
return kong.response.exit(401, { message = "Missing token" })
end

-- 去掉 Bearer 前缀
token = token:gsub("^Bearer%s+", "")

-- 验证 JWT
local jwt_obj = jwt:verify(conf.secret, token)
if not jwt_obj.verified then
return kong.response.exit(401, { message = "Invalid token" })
end

-- 检查权限
local claims = jwt_obj.payload
if not has_permission(claims.role, kong.request.get_path()) then
return kong.response.exit(403, { message = "Forbidden" })
end

-- 将用户信息传递给后端服务
kong.service.request.set_header("X-User-Id", claims.sub)
kong.service.request.set_header("X-User-Role", claims.role)
end

return _M

限流与熔断

保护后端服务免受流量冲击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Kong 限流配置
plugins:
- name: rate-limiting
config:
minute: 100
hour: 10000
policy: redis
redis_host: redis
redis_port: 6379

- name: circuit-breaker
config:
error_threshold: 50 # 错误率阈值 50%
window_size: 10 # 检测窗口 10 秒
half_open_timeout: 60 # 半开状态等待 60 秒

Kong 架构解析

整体架构

Kong 基于 OpenResty(Nginx + Lua)构建,具有高性能和可扩展性。

graph TB
    subgraph Kong Node 1
        Nginx1[Nginx/OpenResty] --> LuaPlugins1[Lua Plugins]
        LuaPlugins1 --> PDK1[Plugin Development Kit]
    end

    subgraph Kong Node 2
        Nginx2[Nginx/OpenResty] --> LuaPlugins2[Lua Plugins]
        LuaPlugins2 --> PDK2[Plugin Development Kit]
    end

    subgraph Control Plane
        AdminAPI[Admin API :8001]
        Manager[Kong Manager UI]
    end

    PostgreSQL[(PostgreSQL)] --> Kong1[Kong Node 1]
    PostgreSQL --> Kong2[Kong Node 2]
    AdminAPI --> PostgreSQL

    Client[客户端] --> LB[Load Balancer]
    LB --> Nginx1
    LB --> Nginx2

Kong 核心概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# Kong 配置示例 - 声明式配置 (kong.yml)
_format_version: "3.0"

services:
- name: user-service
url: http://user-upstream:8080
routes:
- name: user-route
paths:
- /api/v1/users
methods:
- GET
- POST
- PUT
strip_path: true
plugins:
- name: key-auth
config:
key_names:
- apikey
- name: rate-limiting
config:
minute: 60
policy: local
- name: cors
config:
origins:
- "https://example.com"
methods:
- GET
- POST
headers:
- Content-Type
- Authorization
max_age: 3600

- name: order-service
url: http://order-upstream:8080
connect_timeout: 5000
read_timeout: 30000
write_timeout: 30000
routes:
- name: order-route
paths:
- /api/v1/orders
methods:
- GET
- POST

upstreams:
- name: user-upstream
algorithm: round-robin
healthchecks:
active:
healthy:
interval: 5
successes: 3
unhealthy:
interval: 5
http_failures: 3
targets:
- target: user-1:8080
weight: 100
- target: user-2:8080
weight: 100

Kong 插件开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
-- 自定义插件: request-transformer-advanced
local plugin = {
PRIORITY = 1000,
VERSION = "1.0.0",
}

local schema = {
name = "custom-header-injector",
fields = {
{ config = {
type = "record",
fields = {
{ request_id_header = { type = "string", default = "X-Request-ID" } },
{ add_timestamp = { type = "boolean", default = true } },
},
}},
},
}

function plugin:access(conf)
-- 注入请求 ID
local request_id = kong.request.get_header(conf.request_id_header)
if not request_id then
request_id = utils.uuid()
kong.service.request.set_header(conf.request_id_header, request_id)
end

-- 注入时间戳
if conf.add_timestamp then
kong.service.request.set_header("X-Request-Time", ngx.now())
end

-- 记录请求开始时间
kong.ctx.plugin.start_time = ngx.now()
end

function plugin:header_filter(conf)
-- 响应中也加入请求 ID
local request_id = kong.request.get_header(conf.request_id_header)
kong.response.set_header(conf.request_id_header, request_id)
end

function plugin:log(conf)
-- 计算请求耗时
local latency = ngx.now() - kong.ctx.plugin.start_time
kong.log.info("Request latency: ", latency, "s")
end

return plugin

Apache APISIX 架构解析

整体架构

APISIX 同样基于 OpenResty,但使用 etcd 作为配置中心,实现了无单点故障的全动态配置。

graph TB
    subgraph APISIX Cluster
        DP1[Data Plane 1<br/>APISIX Node]
        DP2[Data Plane 2<br/>APISIX Node]
    end

    subgraph Control Plane
        Dashboard[APISIX Dashboard]
        AdminAPI[Admin API]
    end

    etcd[(etcd Cluster)] <-->|Watch配置变更| DP1
    etcd <-->|Watch配置变更| DP2
    AdminAPI -->|写入配置| etcd
    Dashboard --> AdminAPI

    Client[客户端] --> LB[Load Balancer]
    LB --> DP1
    LB --> DP2

    DP1 --> Upstream1[上游服务]
    DP2 --> Upstream1

APISIX 核心配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# APISIX 路由配置
routes:
- id: 1
uri: /api/v1/users/*
name: user-route
methods: ["GET", "POST", "PUT"]
upstream_id: 1
plugins:
jwt-auth: {}
limit-count:
count: 100
time_window: 60
rejected_code: 429
key: remote_addr
proxy-rewrite:
regex_uri: ["^/api/v1/users/(.*)", "/$1"]

- id: 2
uri: /api/v1/orders/*
name: order-route
upstream_id: 2
plugins:
jwt-auth: {}
limit-req:
rate: 50
burst: 100
key: consumer_name

upstreams:
- id: 1
name: user-upstream
type: roundrobin
nodes:
"user-1:8080": 1
"user-2:8080": 1
checks:
active:
type: http
http_path: /health
healthy:
interval: 2
successes: 2
unhealthy:
interval: 1
http_failures: 3
timeout:
connect: 3
send: 5
read: 10
retry_timeout: 5
retries: 3

APISIX 插件开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
-- APISIX 自定义插件示例
local core = require("apisix.core")
local plugin_name = "custom-auth"

local schema = {
type = "object",
properties = {
header_name = { type = "string", default = "X-API-Key" },
auth_service_url = { type = "string" },
cache_ttl = { type = "integer", default = 300 },
},
required = { "auth_service_url" },
}

local _M = {
version = 0.1,
priority = 2600,
name = plugin_name,
schema = schema,
}

function _M.check_schema(conf)
return core.schema.check(schema, conf)
end

function _M.rewrite(conf, ctx)
local api_key = core.request.header(ctx, conf.header_name)
if not api_key then
return 401, { error = "Missing API key" }
end

-- 先检查缓存
local cache_key = "auth:" .. api_key
local cached = core.lrucache.global(cache_key, nil, function()
-- 调用认证服务验证
local httpc = require("resty.http").new()
local res, err = httpc:request_uri(conf.auth_service_url, {
method = "POST",
body = core.json.encode({ api_key = api_key }),
headers = { ["Content-Type"] = "application/json" },
})

if not res or res.status ~= 200 then
return nil
end

return core.json.decode(res.body)
end, conf.cache_ttl)

if not cached then
return 403, { error = "Invalid API key" }
end

-- 将认证信息注入请求头
core.request.set_header(ctx, "X-User-Id", cached.user_id)
core.request.set_header(ctx, "X-User-Role", cached.role)
end

return _M

Kong vs APISIX 深度对比

架构对比

维度 Kong APISIX
底层框架 OpenResty (Nginx + LuaJIT) OpenResty (Nginx + LuaJIT)
配置存储 PostgreSQL / Cassandra etcd
配置生效 式 轮询 DB / DB-less etcd Watch 实时推送
控制平面 Kong Manager / Konga APISIX Dashboard
集群模式 需要数据库 etcd + 无状态节点
插件语言 Lua, Go, Python, JS Lua, Java, Go, Python, Wasm
开源协议 Apache 2.0 (部分 Enterprise) Apache 2.0 (全功能)

性能对比

graph LR
    subgraph 性能基准测试 (wrk, 1000并发)
        direction TB
        A["APISIX<br/>QPS: ~28,000<br/>延迟 P99: 3ms"]
        B["Kong<br/>QPS: ~18,000<br/>延迟 P99: 8ms"]
        C["Spring Cloud Gateway<br/>QPS: ~12,000<br/>延迟 P99: 15ms"]
    end

APISIX 在性能上优于 Kong,主要原因: 1. etcd Watch:配置变更实时推送,无需轮询数据库 2. 全动态路由:路由变更无需 reload Nginx worker 3. 高效缓存:基于 radixtree 的路由匹配

部署对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Kong Docker Compose
version: "3"
services:
kong-database:
image: postgres:15
environment:
POSTGRES_DB: kong
POSTGRES_USER: kong
POSTGRES_PASSWORD: kongpass

kong-migrations:
image: kong:3.5
command: kong migrations bootstrap
depends_on:
- kong-database
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database

kong:
image: kong:3.5
depends_on:
- kong-database
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PROXY_LISTEN: "0.0.0.0:8000"
KONG_ADMIN_LISTEN: "0.0.0.0:8001"
ports:
- "8000:8000"
- "8001:8001"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# APISIX Docker Compose
version: "3"
services:
etcd:
image: bitnami/etcd:3.5
environment:
ALLOW_NONE_AUTHENTICATION: "yes"
ETCD_ADVERTISE_CLIENT_URLS: "http://etcd:2379"

apisix:
image: apache/apisix:3.8.0
depends_on:
- etcd
volumes:
- ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
ports:
- "9080:9080"
- "9443:9443"
- "9180:9180"

apisix-dashboard:
image: apache/apisix-dashboard:3.0
depends_on:
- etcd
ports:
- "9000:9000"

选型建议

graph TD
    Start[选择API网关] --> Q1{需要企业级支持?}
    Q1 -->|是| Q2{预算充足?}
    Q2 -->|是| Kong[Kong Enterprise]
    Q2 -->|否| APISIX[Apache APISIX]

    Q1 -->|否| Q3{性能优先?}
    Q3 -->|是| APISIX
    Q3 -->|否| Q4{生态和社区成熟度?}
    Q4 -->|优先| Kong[Kong OSS]
    Q4 -->|次要| APISIX

选择 Kong 的场景: - 需要成熟的企业级支持 - 团队已有 Kong 使用经验 - 需要丰富的第三方插件生态

选择 APISIX 的场景: - 追求极致性能 - 需要全动态配置能力 - 已有 etcd 基础设施(如 Kubernetes 环境) - 预算有限,需要全功能开源版本

生产环境最佳实践

高可用部署

graph TB
    DNS[DNS] --> CDN[CDN / WAF]
    CDN --> LB1[L4 负载均衡]
    CDN --> LB2[L4 负载均衡]

    LB1 --> GW1[Gateway Node 1]
    LB1 --> GW2[Gateway Node 2]
    LB2 --> GW1
    LB2 --> GW2

    GW1 --> Services[服务集群]
    GW2 --> Services

    subgraph 配置中心
        ETCD1[etcd 1]
        ETCD2[etcd 2]
        ETCD3[etcd 3]
    end

    GW1 <--> ETCD1
    GW2 <--> ETCD2

关键配置建议

  1. 超时设置:根据后端服务的实际响应时间设置合理超时
  2. 健康检查:启用主动健康检查,及时摘除异常节点
  3. 限流策略:按用户/IP/API 维度设置差异化限流
  4. 日志级别:生产环境使用 warn 级别,避免日志打满磁盘
  5. 证书管理:使用自动化证书管理(如 Let’s Encrypt + ACME 插件)

总结

API 网关是微服务架构的流量入口和安全屏障。Kong 和 APISIX 都是优秀的选择,Kong 凭借先发优势拥有更成熟的生态,APISIX 则以更优的性能和全动态配置能力后来居上。在实际选型时,应综合考虑团队能力、基础设施现状、性能需求和预算等因素。无论选择哪个,都要确保高可用部署、合理的限流策略和完善的可观测性配置。

作者 · authorzt
发布 · date2023-12-20
篇幅 · length2.7k 字 · 7 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论