跳到主要内容

熔断与降级

问题

微服务中如何防止雪崩?Go 有哪些熔断限流方案?

答案

雪崩效应

服务 C 故障 → B 等待超时 → B 的 goroutine 堆积 → B 也不可用 → A 级联失败。

熔断器模式

状态说明
Closed(关闭)正常放行,统计错误率
Open(打开)直接拒绝,快速失败
HalfOpen(半开)放一个请求探测,决定恢复或继续熔断

Go 熔断实现

sony/gobreaker

import "github.com/sony/gobreaker"

cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "user-service",
MaxRequests: 3, // 半开状态最多放 3 个请求
Interval: 10 * time.Second, // 统计窗口
Timeout: 30 * time.Second, // Open → HalfOpen 等待时间
ReadyToTrip: func(counts gobreaker.Counts) bool {
// 错误率 > 60% 触发熔断
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 10 && failureRatio >= 0.6
},
OnStateChange: func(name string, from, to gobreaker.State) {
log.Printf("熔断器 %s: %s → %s", name, from, to)
},
})

// 使用
result, err := cb.Execute(func() (interface{}, error) {
return callUserService()
})

if err == gobreaker.ErrOpenState {
// 熔断中,走降级逻辑
return fallbackResponse()
}

限流

令牌桶(golang.org/x/time/rate)

import "golang.org/x/time/rate"

// 每秒 100 个请求,最多突发 200 个
limiter := rate.NewLimiter(100, 200)

func RateLimitMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if !limiter.Allow() {
c.AbortWithStatusJSON(429, gin.H{"error": "too many requests"})
return
}
c.Next()
}
}

滑动窗口限流

// 基于 Redis 的滑动窗口
func SlidingWindowLimit(ctx context.Context, rdb *redis.Client, key string, limit int64, window time.Duration) bool {
now := time.Now().UnixMilli()
pipe := rdb.Pipeline()

// 移除窗口外的记录
pipe.ZRemRangeByScore(ctx, key, "0", strconv.FormatInt(now-window.Milliseconds(), 10))
// 添加当前请求
pipe.ZAdd(ctx, key, redis.Z{Score: float64(now), Member: now})
// 统计窗口内请求数
countCmd := pipe.ZCard(ctx, key)
pipe.Expire(ctx, key, window)

pipe.Exec(ctx)
return countCmd.Val() <= limit
}

降级策略

func getUserProfile(userID string) (*UserProfile, error) {
// 1. 尝试调用远程服务
profile, err := userService.GetProfile(userID)
if err == nil {
// 存入缓存
cache.Set(userID, profile)
return profile, nil
}

// 2. 降级:从缓存读取
if cached, ok := cache.Get(userID); ok {
log.Warn("user-service 降级,使用缓存")
return cached, nil
}

// 3. 兜底:返回默认值
return &UserProfile{Name: "用户", Avatar: defaultAvatar}, nil
}

常见面试问题

Q1: 熔断和限流的区别?

答案

维度熔断限流
目的保护下游(不调崩别人)保护自身(不被流量打垮)
触发条件下游错误率高请求超过阈值
作用位置调用方被调用方
响应快速失败 + 降级429 Too Many Requests

Q2: sentinel-go vs gobreaker?

答案

  • gobreaker:轻量,只做熔断,API 简单,适合需求简单的场景
  • sentinel-go:阿里开源,功能全面(限流 + 熔断 + 热点 + 系统保护),有 Dashboard

小项目用 gobreaker + x/time/rate,大型微服务用 sentinel-go。

相关链接