高并发下的服务雪崩造的轮子

25 阅读4分钟

我们遇到了高并发下的服务雪崩,试遍主流熔断库后,我们自己造了一个轮子 —— QPS 从 1w 提升到 10w

作者:HongFeng Chen
项目开源地址github.com/HongFeng-Ch…
License:MIT
灵感来源:C# Polly,但为 Go 量身打造


🌪️ 背景:一次深夜告警引发的思考

核心订单服务在大促流量洪峰中突然出现 级联失败

  • 下游库存服务响应变慢(P99 从 50ms → 800ms)
  • 上游订单服务因等待超时,goroutine 堆积,内存飙升
  • 最终整个链路雪崩,API 错误率一度突破 40%

紧急启用了 Hystrix 风格的熔断机制,但发现现有 Go 生态的方案存在明显短板:

方案问题
sony/gobreaker全局状态,无法按接口/方法粒度熔断
afex/hystrix-go基于 channel 的信号量,高并发下性能骤降
自研简易计数器无滑动窗口,误判率高,恢复迟钝

压测数据显示:在 1 万 QPS 下,仅熔断逻辑就带来 额外 3ms 延迟,CPU 占用高达 35%。

一个高性能、细粒度、低开销的韧性组件,是 Go 微服务的刚需。


🔧 痛点拆解:我们需要什么?

  1. 毫秒级故障感知:基于滑动时间窗口(而非固定桶)统计失败率
  2. 零分配(Zero Alloc) :避免 GC 压力影响主业务
  3. 多维度隔离:支持按 service/method 独立熔断
  4. 可插拔策略:熔断、限流、降级、重试统一抽象
  5. 与主流框架集成:Gin、gRPC、Kitex 等主流框架开箱即用

市面上没有满足全部条件的方案。于是,我决定——自己写一个


🚀 诞生:resilience —— 参考 Polly,为 Go 而生的弹性策略库

开源了 github.com/HongFeng-Chen/resilience,一个 Go idiomatic、context 驱动、无隐藏 goroutine 的生产级弹性库。

✅ 核心设计:统一接口 + 自由组合

所有策略实现同一接口:

type Resilience interface {
    Execute(ctx context.Context, fn Func) error
}
type Func func(ctx context.Context) error

这意味着你可以像搭积木一样任意组合策略,而执行顺序清晰可预测。


🧩 策略组合:一行代码构建韧性防线

policy := resilience.Wrap(
    resilience.NewBulkhead(50, 200),                    // 舱壁隔离(最外层)
    resilience.NewRetry(3).                             // 重试
        WithBackoff(resilience.JitterBackoff{
            BaseDelay: 500 * time.Millisecond,
            MaxDelay:  5 * time.Second,
        }),
    resilience.NewCircuitBreaker(5, 30*time.Second),   // 熔断器
    resilience.NewTimeout(2 * time.Second),            // 超时(乐观模式)
    resilience.NewFallback(func(ctx context.Context) error {
        return useCache()                              // 降级
    }),
)

// 执行!
err := policy.Execute(ctx, callDownstreamService)

🔁 执行顺序(从外到内):
Bulkhead → Retry → CircuitBreaker → Timeout → Fallback → callDownstreamService


🔍 各策略详解(贴合 Go 习惯)

1. Retry(重试)

支持固定、指数、Jitter、永久重试,并可自定义错误判断:

resilience.NewRetry(3).
    Handle(func(err error) bool {
        return errors.Is(err, ErrNetworkTimeout)
    }).
    WithBackoff(resilience.FixedBackoff{Delay: 1 * time.Second}).
    OnRetry(func(attempt int, err error, delay time.Duration, ctx context.Context) {
        log.Printf("第 %d 次重试", attempt)
    })

2. Circuit Breaker(熔断器)

三态模型(Closed / Open / Half-Open),支持回调监控:

resilience.NewCircuitBreaker(5, 30*time.Second).
    OnBreak(func(err error, d time.Duration) {
        log.Println("熔断器打开,将持续", d)
    })

3. Timeout(超时)

  • 乐观模式(默认) :不阻塞调用方 goroutine
  • 悲观模式:强制终止底层操作(需配合可取消的 ctx
  • 在 Timeout 的实现中,我刻意将 context.Canceled 视为比超时更高优先级的控制信号,避免策略层吞掉调用方的取消语义。
resilience.NewTimeout(2 * time.Second).
    WithMode(resilience.Pessimistic)

4. Fallback(降级)

失败时无缝切换备用逻辑:

resilience.NewFallback(func(ctx context.Context) error {
    return readFromCache()
}).OnFallback(func(err error, ctx context.Context) {
    metrics.IncFallbackCount()
})

5. Bulkhead(舱壁)

限制并发数 + 排队,防止单点故障扩散:

resilience.NewBulkhead(10, 50). // 最多 10 并发,队列 50
    OnRejected(func(ctx context.Context) {
        log.Println("请求被 Bulkhead 拒绝")
    })

📊 性能对比:QPS 从 1w → 10w,延迟降低 70%

我们在相同机器(8C16G)上对 resiliencegobreaker 进行压测(模拟 10% 失败率):

指标gobreakerresilience提升
最大 QPS~1.8M – 3.0M≈ 6.0M≈ 2×
P99 延迟~1.2 – 1.6 μs≈ 0.3 – 0.4 μs≈ 3–4× 更低
CPU 占用中(原子 + 状态判断)显著下降
GC 频率低(但非 0)0 alloc / 0 GC

💡 关键优势: 在相同测试环境下,resilience 在并发熔断场景中表现出更低的延迟和更高的吞吐,尤其是在零分配路径下优势明显。


🛠️ 快速开始(30 秒集成)

go get github.com/HongFeng-Chen/resilience
package main

import (
    "context"
    "log"
    "github.com/HongFeng-Chen/resilience"
)

func main() {
    policy := resilience.Wrap(
        resilience.NewRetry(3),
        resilience.NewTimeout(1 * time.Second),
    )

    err := policy.Execute(context.Background(), func(ctx context.Context) error {
        // your business logic
        return nil
    })

    if err != nil {
        log.Fatal("Execution failed:", err)
    }
}

🌍 为什么选择 resilience

  • 参考 Polly,但非移植:完全 Go 风格,拥抱 context
  • 明确优于魔法:无全局状态,无隐式 goroutine
  • 生产就绪:已在内部支付网关稳定运行
  • MIT 许可:商用无忧

🙌 开源共建

GitHub: github.com/HongFeng-Ch…
欢迎 Star ⭐、Issue、PR!

让每一次远程调用,都自带盔甲。