折腾侠
项目实战

从零构建一个高可用 API 网关:架构设计与实战经验

本文分享从零构建高可用 API 网关的完整实战经验,涵盖架构设计、核心功能实现、性能优化和生产部署。通过实际项目案例,深入探讨请求路由、认证鉴权、限流熔断、日志监控等关键模块的设计思路与代码实现,为需要自建网关的团队提供可落地的参考方案。

折腾侠
2026/03/16 发布
15约 9 分钟1317 字 / 1162 词00

从零构建一个高可用 API 网关:架构设计与实战经验

摘要

本文分享从零构建高可用 API 网关的完整实战经验,涵盖架构设计、核心功能实现、性能优化和生产部署。通过实际项目案例,深入探讨请求路由、认证鉴权、限流熔断、日志监控等关键模块的设计思路与代码实现,为需要自建网关的团队提供可落地的参考方案。


一、为什么需要自建 API 网关

1.1 业务背景

在我们开始这个项目时,公司面临着典型的微服务架构痛点:

  • 服务碎片化:15+ 个微服务分散管理,每个服务都要独立处理认证、限流、日志
  • 重复造轮子:每个团队都在实现相似的中间件,代码质量参差不齐
  • 监控盲区:缺乏统一的请求追踪,问题定位困难
  • 安全风险:认证逻辑分散,安全策略难以统一执行

1.2 方案选型

我们评估了几个方向:

方案优点缺点最终决策
Kong功能成熟、插件丰富学习成本高、Lua 扩展受限
APISIX性能优秀、云原生社区相对年轻、定制复杂
Nginx + Lua灵活、性能好开发效率低、调试困难
自研 (Go)完全可控、贴合业务初期投入大

选择 Go 语言自研的核心理由:

  1. 团队技术栈匹配,维护成本低
  2. 高性能,原生支持并发
  3. 编译为单一二进制,部署简单
  4. 完全掌控代码,快速迭代业务需求

二、架构设计

2.1 整体架构

┌─────────────────────────────────────────────────────────┐
│                      Client Layer                        │
│              (Web / Mobile / Third-party)                │
└─────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│                    Load Balancer                         │
│                    (Nginx / LVS)                         │
└─────────────────────────────────────────────────────────┘
                            │
              ┌─────────────┼─────────────┐
              ▼             ▼             ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │  Gateway │ │  Gateway │ │  Gateway │
        │  Node 1  │ │  Node 2  │ │  Node 3  │
        └──────────┘ └──────────┘ └──────────┘
              │             │             │
              └─────────────┼─────────────┘
                            ▼
        ┌───────────────────────────────────┐
        │         Service Discovery         │
        │         (Consul / Etcd)           │
        └───────────────────────────────────┘
                            │
              ┌─────────────┼─────────────┐
              ▼             ▼             ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Service  │ │ Service  │ │ Service  │
        │    A     │ │    B     │ │    C     │
        └──────────┘ └──────────┘ └──────────┘

2.2 核心模块划分

gateway/
├── cmd/                    # 入口程序
├── internal/
│   ├── handler/           # HTTP 处理器
│   ├── middleware/        # 中间件链
│   │   ├── auth/         # 认证鉴权
│   │   ├── ratelimit/    # 限流
│   │   ├── circuit/      # 熔断
│   │   └── logging/      # 日志
│   ├── router/           # 路由匹配
│   ├── proxy/            # 反向代理
│   ├── discovery/        # 服务发现
│   └── config/           # 配置管理
├── pkg/                   # 公共库
└── deploy/                # 部署配置

三、核心功能实现

3.1 动态路由系统

路由是网关的核心,我们设计了基于前缀匹配的动态路由:

Go
type Route struct {
    ID          string            `json:"id"`
    Path        string            `json:"path"`        // /api/v1/users/*
    Methods     []string          `json:"methods"`     // [GET, POST]
    Upstream    string            `json:"upstream"`    // http://user-service:8080
    Timeout     time.Duration     `json:"timeout"`
    Plugins     []PluginConfig    `json:"plugins"`
    CreatedAt   time.Time         `json:"created_at"`
}

type Router struct {
    routes     []*Route
    mu         sync.RWMutex
    trie       *PrefixTrie  // 前缀树加速匹配
}

func (r *Router) Match(path string, method string) (*Route, error) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    
    // 前缀树 O(log N) 匹配
    route := r.trie.Search(path)
    if route == nil {
        return nil, ErrRouteNotFound
    }
    
    // 方法校验
    if !contains(route.Methods, method) {
        return nil, ErrMethodNotAllowed
    }
    
    return route, nil
}

关键设计点:

  • 使用前缀树而非线性搜索,路由匹配复杂度从 O(N) 降到 O(log N)
  • 支持热更新,配置变更无需重启
  • 路由规则存储在 Consul,实现配置中心化管理

3.2 认证鉴权中间件

我们实现了多层次的认证体系:

Go
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 1. 从 Header 提取 Token
        token := extractToken(c.Request)
        if token == "" {
            c.AbortWithStatusJSON(401, gin.H{"error": "missing token"})
            return
        }
        
        // 2. JWT 验证
        claims, err := jwt.Parse(token, secretKey)
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
            return
        }
        
        // 3. 权限校验(RBAC)
        route := c.MustGet("route").(*Route)
        if !hasPermission(claims.Roles, route.RequiredRoles) {
            c.AbortWithStatusJSON(403, gin.H{"error": "forbidden"})
            return
        }
        
        // 4. 将用户信息注入 Context
        c.Set("user_id", claims.UserID)
        c.Set("roles", claims.Roles)
        c.Next()
    }
}

安全增强措施:

  • Token 黑名单(Redis 存储,支持即时吊销)
  • 请求签名(防止重放攻击)
  • IP 白名单(针对敏感接口)
  • 速率限制(防止暴力破解)

3.3 限流与熔断

限流实现(令牌桶算法)

Go
type RateLimiter struct {
    buckets map[string]*Bucket
    mu      sync.Mutex
}

type Bucket struct {
    tokens     float64
    capacity   float64
    refillRate float64  // tokens/second
    lastRefill time.Time
}

func (b *Bucket) Allow() bool {
    now := time.Now()
    elapsed := now.Sub(b.lastRefill).Seconds()
    
    // 补充令牌
    b.tokens = math.Min(b.capacity, b.tokens+elapsed*b.refillRate)
    b.lastRefill = now
    
    // 消耗令牌
    if b.tokens >= 1 {
        b.tokens--
        return true
    }
    return false
}

限流策略:

  • 全局限流:保护网关整体
  • 用户限流:防止单用户滥用
  • 接口限流:针对敏感接口单独配置

熔断器实现

Go
type CircuitBreaker struct {
    state      State  // CLOSED, OPEN, HALF_OPEN
    failures   int
    threshold  int
    timeout    time.Duration
    lastFail   time.Time
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    if cb.state == OPEN {
        if time.Since(cb.lastFail) > cb.timeout {
            cb.state = HALF_OPEN
        } else {
            return ErrCircuitOpen
        }
    }
    
    err := fn()
    if err != nil {
        cb.failures++
        if cb.failures >= cb.threshold {
            cb.state = OPEN
            cb.lastFail = time.Now()
        }
    } else {
        if cb.state == HALF_OPEN {
            cb.state = CLOSED
        }
        cb.failures = 0
    }
    
    return err
}

四、性能优化实战

4.1 连接池优化

Go
// 复用 HTTP 客户端,避免频繁创建连接
var httpClient = &http.Client{
    Transport: &http.Transport{
        MaxIdleConns:        1000,
        MaxIdleConnsPerHost: 100,
        IdleConnTimeout:     90 * time.Second,
        DisableKeepAlives:   false,
        DialContext: (&net.Dialer{
            Timeout:   30 * time.Second,
            KeepAlive: 30 * time.Second,
        }).DialContext,
    },
    Timeout: 60 * time.Second,
}

优化效果:

  • QPS 从 3000 → 8000
  • P99 延迟从 150ms → 45ms

4.2 异步日志

Go
type AsyncLogger struct {
    queue chan LogEntry
    wg    sync.WaitGroup
}

func (l *AsyncLogger) Write(entry LogEntry) {
    select {
    case l.queue <- entry:
        // 入队成功
    default:
        // 队列满,丢弃或降级
        metrics.LogDropped.Inc()
    }
}

func (l *AsyncLogger) Start() {
    go func() {
        batch := make([]LogEntry, 0, 100)
        ticker := time.NewTicker(100 * time.Millisecond)
        
        for {
            select {
            case entry := <-l.queue:
                batch = append(batch, entry)
                if len(batch) >= 100 {
                    l.flush(batch)
                    batch = batch[:0]
                }
            case <-ticker.C:
                if len(batch) > 0 {
                    l.flush(batch)
                    batch = batch[:0]
                }
            }
        }
    }()
}

4.3 内存池复用

Go
var bufferPool = sync.Pool{
    New: func() interface{} {
        return bytes.NewBuffer(make([]byte, 0, 4096))
    },
}

func getBuffer() *bytes.Buffer {
    return bufferPool.Get().(*bytes.Buffer)
}

func putBuffer(buf *bytes.Buffer) {
    buf.Reset()
    bufferPool.Put(buf)
}

五、监控与可观测性

5.1 指标采集(Prometheus)

Go
// 关键指标
var (
    requestTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{Name: "gateway_requests_total"},
        []string{"method", "route", "status"},
    )
    
    requestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "gateway_request_duration_seconds",
            Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
        },
        []string{"method", "route"},
    )
    
    activeConnections = promauto.NewGauge(
        prometheus.GaugeOpts{Name: "gateway_active_connections"},
    )
)

5.2 分布式追踪(Jaeger)

Go
func tracingMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 从 Header 提取或创建 Trace ID
        span, ctx := tracer.Start(
            c.Request.Context(),
            fmt.Sprintf("HTTP %s %s", c.Request.Method, c.Request.URL.Path),
        )
        defer span.Finish()
        
        // 注入 Context
        c.Request = c.Request.WithContext(ctx)
        c.Set("trace_id", span.Context().(jaeger.SpanContext).TraceID())
        
        c.Next()
        
        // 记录响应状态
        span.SetTag("http.status_code", c.Writer.Status())
    }
}

5.3 告警规则

YAML
groups:
  - name: gateway-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(gateway_requests_total{status=~"5.."}[5m]) > 0.05
        for: 2m
        annotations:
          summary: "网关错误率超过 5%"
          
      - alert: HighLatency
        expr: histogram_quantile(0.99, gateway_request_duration_seconds) > 1
        for: 5m
        annotations:
          summary: "P99 延迟超过 1 秒"
          
      - alert: CircuitBreakerOpen
        expr: gateway_circuit_breaker_state == 1
        for: 1m
        annotations:
          summary: "熔断器开启"

六、生产部署经验

6.1 容器化部署

YAML
# docker-compose.yml
version: '3.8'
services:
  gateway:
    image: mycompany/gateway:latest
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '2'
          memory: 2G
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s
      timeout: 5s
      retries: 3
    environment:
      - CONSUL_ADDR=consul:8500
      - LOG_LEVEL=info

6.2 灰度发布策略

  1. 金丝雀发布:先部署 1 个节点,流量 5%
  2. 监控指标:错误率、延迟、资源使用
  3. 逐步放量:5% → 20% → 50% → 100%
  4. 快速回滚:发现问题 5 分钟内回滚

6.3 配置管理

  • 所有配置存入 Consul
  • 支持配置热更新
  • 配置变更审计日志
  • 敏感配置加密存储

七、踩坑与教训

7.1 坑 1:连接泄漏

问题:生产环境运行一周后,网关连接数持续增长,最终 OOM。

原因:代理响应体未正确关闭,导致连接无法复用。

Go
// ❌ 错误写法
resp, _ := http.Get(url)
body, _ := io.ReadAll(resp.Body)  // Body 未关闭

// ✅ 正确写法
resp, err := http.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()  // 必须关闭
body, err := io.ReadAll(resp.Body)

7.2 坑 2:时间同步问题

问题:JWT 验证偶尔失败,日志显示"token not yet valid"。

原因:网关服务器与认证服务器时间不同步,相差 2 秒。

解决

  • 所有服务器配置 NTP 同步
  • Token 验证增加 30 秒宽容度

7.3 坑 3:限流算法选择

问题:初期使用固定窗口限流,遇到"临界点突发"问题。

解决:改用滑动窗口令牌桶算法。


八、总结与建议

8.1 核心收获

  1. 不要过早优化:先实现核心功能,再根据监控数据优化瓶颈
  2. 可观测性优先:日志、指标、追踪要在设计阶段就考虑
  3. 配置与代码分离:所有可变配置都要支持热更新
  4. 故障演练:定期做混沌工程,验证系统韧性

8.2 是否应该自建?

建议自建的情况:

  • 业务有特殊需求,现有方案无法满足
  • 团队有 Go/Java 开发能力
  • 需要深度定制和快速迭代

建议用现成的情况:

  • 标准需求,Kong/APISIX 能满足
  • 团队规模小,维护成本高
  • 时间紧迫,需要快速上线

关于作者

本文基于真实项目经验整理,该网关目前承载日均 5000 万 + 请求,P99 延迟稳定在 50ms 以内。欢迎交流讨论!

项目地址https://github.com/xxx/api-gateway(开源中)

分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...