「产品矩阵平台」高并发与可用性架构

高并发与可用性架构:负载均衡、限流熔断、服务发现、容灾多活、健康检查、Leader Election 与性能压测体系的完整设计。

第八章 高并发与可用性架构

高并发不是单纯追求 QPS。对产品矩阵平台来说,更重要的是:一个产品的流量高峰不能拖垮其他产品,一个外部依赖故障不能让核心链路不可用,一次发布失败要能快速止损。

可用性设计要围绕四个目标:

  1. 扛得住:容量足够,关键路径低延迟;
  2. 限得住:异常流量不会无限扩散;
  3. 降得住:非核心能力可以降级;
  4. 恢复快:故障定位、切换、回滚都有预案。

8.1 负载均衡与反向代理

入口层通常包含 CDN、WAF、负载均衡和反向代理。

层级职责
CDN静态资源加速、边缘缓存
WAF常见攻击防护
Load Balancer四层或七层流量分发
Nginx / Envoy / Traefik路由、TLS、Header 处理
API Gateway鉴权、限流、审计

对多租户平台,入口层必须保留租户识别信息,不能在代理转发时丢掉 HostX-Forwarded-ForX-Request-ID

8.2 限流与熔断

限流保护系统,熔断保护调用链。

限流维度:

维度示例
IP防恶意请求
User防单用户刷接口
Tenant防某租户影响全局
App防某应用异常流量
API保护昂贵接口

熔断适合外部依赖,例如短信、支付、AI 模型、搜索服务。当错误率或延迟超过阈值,短时间内停止调用,直接返回降级结果。

8.3 服务注册与发现

服务发现适合多实例、多环境部署。常见选择:

工具特点
Kubernetes Service云原生默认选择
Consul服务发现和健康检查成熟
Etcd强一致 KV,适合配置和协调
NacosJava 生态和配置中心友好

如果已经使用 K3s 或 Kubernetes,优先用原生 Service 和 Ingress,避免过早引入第二套服务发现系统。

8.4 异常隔离与降级策略

隔离要按资源、租户和功能分层。

隔离对象做法
线程 / goroutineWorker Pool、并发上限
数据库连接按服务设置连接池
租户租户级限流和配额
功能非核心功能可关闭
第三方依赖超时、熔断、备用渠道

降级例子:

功能降级方案
推荐流返回热门内容
搜索返回最近内容或提示稍后重试
AI 生成转为异步任务
短信切换供应商或进入重试队列
BI 报表返回上一小时快照

8.5 容灾备份与多活架构

可用性指标需要先定义:

指标含义
RTO故障后多久恢复
RPO最多丢失多久数据

不同业务要求不同:

业务RTORPO
登录、支付分钟级秒级或接近 0
内容浏览分钟级分钟级
BI 报表小时级小时级
AI 任务小时级可重放

多活不是早期必需品。很多团队真正需要的是可靠备份、定期恢复演练和跨可用区部署。

8.6 健康检查与自愈

健康检查分三层:

类型检查内容
Liveness进程是否活着
Readiness是否可接收流量
Dependency数据库、Redis、队列是否可用

Readiness 不应只返回 200 OK。如果数据库连接池耗尽,实例就不该继续接流量。

自愈动作包括:

  1. 自动重启异常实例;
  2. 从负载均衡摘除不健康节点;
  3. 自动扩容;
  4. 切换备用依赖;
  5. 触发告警和事件记录。

8.7 Leader Election 与 Failover

分布式系统中,有些任务只能一个实例执行,例如定时账单、全量索引重建、租户归档。可以用 Leader Election 保证单点执行。

常见实现:

实现说明
Redis Lock简单,适合轻量任务
Etcd Lease强一致,适合关键协调
Kubernetes LeaseK8s 环境推荐
数据库锁简单但性能有限

Leader 必须定期续租,失去租约后立即停止任务,避免双主。

8.8 性能优化与基准测试

优化前先测量。建议建立三类测试:

测试目的
Benchmark函数和模块级性能
Load Test接口在预期流量下表现
Stress Test找系统极限和崩溃点

关键指标:

指标说明
P50 / P95 / P99延迟分布
Error Rate错误率
SaturationCPU、内存、连接池、队列积压
Throughput吞吐量

只看平均响应时间会误导判断。用户感受到的是长尾延迟,尤其是 P95 和 P99。

8.9 可用性设计清单

检查项标准
入口有 CDN、WAF、LB、网关分层
限流支持租户、用户、接口维度
熔断外部依赖有超时和降级
隔离核心与非核心资源隔离
备份有恢复演练,不只备份文件
健康检查Readiness 真实反映依赖状态
压测有容量基线和扩容阈值

8.10 代码实践:令牌桶限流、断路器与健康检查

一、Token Bucket 限流器(支持多维度)

package ratelimiter

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type TokenBucket struct {
    capacity   int
    tokens     int
    refillRate time.Duration // 多长时间补充 1 个 token
    lastRefill time.Time
    mu         sync.Mutex
}

func NewTokenBucket(capacity int, refillRate time.Duration) *TokenBucket {
    return &TokenBucket{
        capacity:   capacity,
        tokens:     capacity,
        refillRate: refillRate,
        lastRefill: time.Now(),
    }
}

func (tb *TokenBucket) Allow(n int) bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    now := time.Now()
    elapsed := now.Sub(tb.lastRefill)
    addTokens := int(elapsed / tb.refillRate)
    if addTokens > 0 {
        tb.tokens = min(tb.capacity, tb.tokens+addTokens)
        tb.lastRefill = now
    }

    if tb.tokens >= n {
        tb.tokens -= n
        return true
    }
    return false
}

// DistributedLimiter 基于 Redis 的分布式限流
func DistributedLimiter(redis RedisClient, key string, capacity int, window time.Duration) bool {
    lua := `
        local key = KEYS[1]
        local capacity = tonumber(ARGV[1])
        local window = tonumber(ARGV[2])
        local current = redis.call('INCR', key)
        if current == 1 then
            redis.call('EXPIRE', key, window)
        end
        return current <= capacity
    `
    ok, err := redis.Eval(context.Background(), lua, []string{key}, capacity, int(window.Seconds())).Result()
    return err == nil && ok.(int64) == 1
}

// 多维度限流中间件
func RateLimitMiddleware(redis RedisClient) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx := r.Context()
            tenant := GetTenant(ctx)
            apiKey := r.URL.Path

            // 接口级限流
            if !DistributedLimiter(redis, "ratelimit:api:"+apiKey, 100, time.Minute) {
                w.WriteHeader(http.StatusTooManyRequests)
                return
            }
            // 租户级限流
            if !DistributedLimiter(redis, "ratelimit:tenant:"+tenant.TenantID, 1000, time.Minute) {
                w.WriteHeader(http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

二、断路器模式(Circuit Breaker)

package circuitbreaker

import (
    "errors"
    "sync"
    "time"
)

type State int

const (
    StateClosed    State = iota // 正常
    StateOpen                   // 熔断开启
    StateHalfOpen              // 半开探测
)

// CircuitBreaker 保护外部依赖调用
type CircuitBreaker struct {
    failureThreshold int
    successThreshold int
    timeout          time.Duration
    state            State
    failures         int
    successes        int
    lastFailureTime  time.Time
    mu               sync.Mutex
}

func New(failureThreshold, successThreshold int, timeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        failureThreshold: failureThreshold,
        successThreshold: successThreshold,
        timeout:          timeout,
        state:            StateClosed,
    }
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    cb.mu.Lock()
    switch cb.state {
    case StateOpen:
        if time.Since(cb.lastFailureTime) > cb.timeout {
            cb.state = StateHalfOpen
            cb.failures = 0
            cb.successes = 0
        } else {
            cb.mu.Unlock()
            return errors.New("circuit breaker is open")
        }
    case StateHalfOpen:
        // 继续执行下面的调用逻辑
    }
    cb.mu.Unlock()

    err := fn()

    cb.mu.Lock()
    defer cb.mu.Unlock()
    if err != nil {
        cb.failures++
        cb.lastFailureTime = time.Now()
        if cb.failures >= cb.failureThreshold {
            cb.state = StateOpen
        }
        return err
    }
    cb.successes++
    if cb.state == StateHalfOpen && cb.successes >= cb.successThreshold {
        cb.state = StateClosed
        cb.failures = 0
        cb.successes = 0
    }
    return nil
}

// 使用示例:保护 SMS 调用
func (svc *NotificationService) SendSMS(ctx context.Context, phone, message string) error {
    cb := cbStore.GetOrCreate("sms:" + svc.provider) // 按供应商共享 CB
    return cb.Call(func() error {
        return svc.smsClient.Send(ctx, phone, message)
    })
}

三、分层健康检查

package health

import (
    "context"
    "database/sql"
    "net/http"
    "time"
)

type Checker struct {
    db      *sql.DB
    redis   RedisPinger
    kafka   KafkaPinger
}

type Status struct {
    Status    string                 `json:"status"`
    Timestamp time.Time              `json:"timestamp"`
    Checks    map[string]CheckResult `json:"checks"`
}

type CheckResult struct {
    Status string `json:"status"`
    Error  string `json:"error,omitempty"`
}

func (c *Checker) Liveness(w http.ResponseWriter, r *http.Request) {
    // 仅判断进程存活
    _ = json.NewEncoder(w).Encode(Status{Status: "ok", Timestamp: time.Now()})
}

func (c *Checker) Readiness(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
    defer cancel()

    checks := make(map[string]CheckResult)
    healthy := true

    // 数据库连通性
    if c.db != nil {
        start := time.Now()
        if err := c.db.PingContext(ctx); err != nil {
            checks["database"] = CheckResult{Status: "down", Error: err.Error()}
            healthy = false
        } else {
            checks["database"] = CheckResult{Status: "ok"}
        }
        checks["database_ping_ms"] = CheckResult{Status: time.Since(start).String()}
    }

    // Redis
    if c.redis != nil {
        if err := c.redis.Ping(ctx).Err(); err != nil {
            checks["redis"] = CheckResult{Status: "down", Error: err.Error()}
            healthy = false
        } else {
            checks["redis"] = CheckResult{Status: "ok"}
        }
    }

    // Kafka
    if c.kafka != nil {
        if err := c.kafka.Ping(); err != nil {
            checks["kafka"] = CheckResult{Status: "down", Error: err.Error()}
            healthy = false
        } else {
            checks["kafka"] = CheckResult{Status: "ok"}
        }
    }

    status := "ready"
    if !healthy {
        status = "not_ready"
        w.WriteHeader(http.StatusServiceUnavailable)
    }

    _ = json.NewEncoder(w).Encode(Status{
        Status:    status,
        Timestamp: time.Now(),
        Checks:    checks,
    })
}

四、Leader Election(Redis 版)

package leader

import (
    "context"
    "fmt"
    "time"
)

type Election struct {
    redis      RedisClient
    key        string
    nodeID     string
    leaseTTL   time.Duration
    renewCh    chan struct{}
    isLeader   bool
}

func (e *Election) Campaign(ctx context.Context) error {
    // 尝试获取锁
    ok, err := e.redis.SetNX(ctx, e.key, e.nodeID, e.leaseTTL).Result()
    if err != nil {
        return err
    }
    if ok {
        e.isLeader = true
        go e.renewLoop(ctx)
        return nil
    }
    // 检查当前持有者是否是自己(防止重启后重入)
    val, _ := e.redis.Get(ctx, e.key).Result()
    if val == e.nodeID {
        e.isLeader = true
        go e.renewLoop(ctx)
        return nil
    }
    e.isLeader = false
    return fmt.Errorf("another node holds leadership")
}

func (e *Election) renewLoop(ctx context.Context) {
    ticker := time.NewTicker(e.leaseTTL / 3)
    defer ticker.Stop()
    for {
        select {
        case <-ctx.Done():
            e.redis.Del(ctx, e.key)
            return
        case <-ticker.C:
            e.redis.Expire(ctx, e.key, e.leaseTTL)
            // 可选:写带 TTL 的续租日志
        }
    }
}

func (e *Election) IsLeader() bool { return e.isLeader }

// 使用示例:定时归档任务仅由 Leader 执行
func (svc *ArchiveService) Schedule(ctx context.Context) {
    ticker := time.NewTicker(1 * time.Hour)
    defer ticker.Stop()
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            if !svc.election.IsLeader() {
                continue
            }
            _ = svc.RunArchive(ctx)
        }
    }
}

五、压测基准测试 (Benchmark)

package benchmark

import (
    "testing"
    "net/http"
    "net/http/httptest"
)

// 模块级基准测试
func BenchmarkOrderCreate(b *testing.B) {
    svc := setupTestOrderService()
    ctx := context.Background()
    cmd := CreateOrderCommand{
        UserID:    "u_001",
        ProductID: "p_001",
        Quantity:  1,
    }
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _, _ = svc.CreateOrder(ctx, cmd)
    }
}

// 接口级负载测试(搭配 go test -bench=. 或 k6)
func BenchmarkOrderHandler(b *testing.B) {
    handler := setupTestHandler()
    body := `{"product_id":"p_001","quantity":1}`
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        req := httptest.NewRequest(http.MethodPost, "/api/v1/orders", strings.NewReader(body))
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("X-App-ID", "app_001")
        w := httptest.NewRecorder()
        handler.ServeHTTP(w, req)
    }
}

本章小结

本章围绕"扛得住、限得住、降得住、恢复快"四个目标,设计了入口层分层(CDN/WAF/LB/Gateway)、租户/用户/API 多维度限流、外部依赖熔断、功能降级与容灾备份方案。关键认知是:多活不是必需品,可靠备份与恢复演练才是多数团队的真需求;P95 和 P99 延迟才是最应关注的指标。


延伸阅读


关联专题

专题关联内容链接
Docker容器化部署与K3s集群/posts/docker/
CloudflareCDN与边缘高可用/posts/cloudflare/
LuaNginx限流与WAF扩展/posts/lua/
GolangGo服务注册与熔断实现/golang/
Node.jsNode.js集群与负载均衡/posts/nodejs/

继续阅读

探索更多技术文章

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

全部文章 返回首页

「SaaS」更多文章

  1. 「产品矩阵平台」未来演进方向
  2. 「产品矩阵平台」运维与成本优化
  3. 「产品矩阵平台」安全与合规体系