第八章 高并发与可用性架构
高并发不是单纯追求 QPS。对产品矩阵平台来说,更重要的是:一个产品的流量高峰不能拖垮其他产品,一个外部依赖故障不能让核心链路不可用,一次发布失败要能快速止损。
可用性设计要围绕四个目标:
- 扛得住:容量足够,关键路径低延迟;
- 限得住:异常流量不会无限扩散;
- 降得住:非核心能力可以降级;
- 恢复快:故障定位、切换、回滚都有预案。
8.1 负载均衡与反向代理
入口层通常包含 CDN、WAF、负载均衡和反向代理。
| 层级 | 职责 |
|---|---|
| CDN | 静态资源加速、边缘缓存 |
| WAF | 常见攻击防护 |
| Load Balancer | 四层或七层流量分发 |
| Nginx / Envoy / Traefik | 路由、TLS、Header 处理 |
| API Gateway | 鉴权、限流、审计 |
对多租户平台,入口层必须保留租户识别信息,不能在代理转发时丢掉 Host、X-Forwarded-For、X-Request-ID。
8.2 限流与熔断
限流保护系统,熔断保护调用链。
限流维度:
| 维度 | 示例 |
|---|---|
| IP | 防恶意请求 |
| User | 防单用户刷接口 |
| Tenant | 防某租户影响全局 |
| App | 防某应用异常流量 |
| API | 保护昂贵接口 |
熔断适合外部依赖,例如短信、支付、AI 模型、搜索服务。当错误率或延迟超过阈值,短时间内停止调用,直接返回降级结果。
8.3 服务注册与发现
服务发现适合多实例、多环境部署。常见选择:
| 工具 | 特点 |
|---|---|
| Kubernetes Service | 云原生默认选择 |
| Consul | 服务发现和健康检查成熟 |
| Etcd | 强一致 KV,适合配置和协调 |
| Nacos | Java 生态和配置中心友好 |
如果已经使用 K3s 或 Kubernetes,优先用原生 Service 和 Ingress,避免过早引入第二套服务发现系统。
8.4 异常隔离与降级策略
隔离要按资源、租户和功能分层。
| 隔离对象 | 做法 |
|---|---|
| 线程 / goroutine | Worker Pool、并发上限 |
| 数据库连接 | 按服务设置连接池 |
| 租户 | 租户级限流和配额 |
| 功能 | 非核心功能可关闭 |
| 第三方依赖 | 超时、熔断、备用渠道 |
降级例子:
| 功能 | 降级方案 |
|---|---|
| 推荐流 | 返回热门内容 |
| 搜索 | 返回最近内容或提示稍后重试 |
| AI 生成 | 转为异步任务 |
| 短信 | 切换供应商或进入重试队列 |
| BI 报表 | 返回上一小时快照 |
8.5 容灾备份与多活架构
可用性指标需要先定义:
| 指标 | 含义 |
|---|---|
| RTO | 故障后多久恢复 |
| RPO | 最多丢失多久数据 |
不同业务要求不同:
| 业务 | RTO | RPO |
|---|---|---|
| 登录、支付 | 分钟级 | 秒级或接近 0 |
| 内容浏览 | 分钟级 | 分钟级 |
| BI 报表 | 小时级 | 小时级 |
| AI 任务 | 小时级 | 可重放 |
多活不是早期必需品。很多团队真正需要的是可靠备份、定期恢复演练和跨可用区部署。
8.6 健康检查与自愈
健康检查分三层:
| 类型 | 检查内容 |
|---|---|
| Liveness | 进程是否活着 |
| Readiness | 是否可接收流量 |
| Dependency | 数据库、Redis、队列是否可用 |
Readiness 不应只返回 200 OK。如果数据库连接池耗尽,实例就不该继续接流量。
自愈动作包括:
- 自动重启异常实例;
- 从负载均衡摘除不健康节点;
- 自动扩容;
- 切换备用依赖;
- 触发告警和事件记录。
8.7 Leader Election 与 Failover
分布式系统中,有些任务只能一个实例执行,例如定时账单、全量索引重建、租户归档。可以用 Leader Election 保证单点执行。
常见实现:
| 实现 | 说明 |
|---|---|
| Redis Lock | 简单,适合轻量任务 |
| Etcd Lease | 强一致,适合关键协调 |
| Kubernetes Lease | K8s 环境推荐 |
| 数据库锁 | 简单但性能有限 |
Leader 必须定期续租,失去租约后立即停止任务,避免双主。
8.8 性能优化与基准测试
优化前先测量。建议建立三类测试:
| 测试 | 目的 |
|---|---|
| Benchmark | 函数和模块级性能 |
| Load Test | 接口在预期流量下表现 |
| Stress Test | 找系统极限和崩溃点 |
关键指标:
| 指标 | 说明 |
|---|---|
| P50 / P95 / P99 | 延迟分布 |
| Error Rate | 错误率 |
| Saturation | CPU、内存、连接池、队列积压 |
| 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 延迟才是最应关注的指标。
延伸阅读
- 上一章:数据、缓存与存储架构 — 数据库分层、缓存策略、ES/ClickHouse 与数据一致性。
- 下一章:AI 大模型系统架构 — Prompt 模板、RAG 检索、模型适配与多租户 AI 配额治理。
关联专题
| 专题 | 关联内容 | 链接 |
|---|---|---|
| Docker | 容器化部署与K3s集群 | /posts/docker/ |
| Cloudflare | CDN与边缘高可用 | /posts/cloudflare/ |
| Lua | Nginx限流与WAF扩展 | /posts/lua/ |
| Golang | Go服务注册与熔断实现 | /golang/ |
| Node.js | Node.js集群与负载均衡 | /posts/nodejs/ |
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。