流量洪峰来了,你的服务还活着吗?限流熔断完全求生指南

2026-10-18 3 0

深夜,你正在愉快地写bug——哦不,写代码。手机突然炸了:线上告警,CPU打满,数据库连接池耗尽,服务开始多米诺骨牌式崩塌。

你瑟瑟发抖地看着监控大屏,心里只有一个问题:早干嘛去了?

今天就来聊聊后端工程师的保命技能:限流和熔断。学会了这俩,至少能让你在被流量冲垮之前,有那么一点点反应时间。


一、限流:给你的服务装个阀门

想象一下,你家下水道每秒最多排10升水,结果有人往里倒了一吨混凝土。你的下水道会?当然是爆了。

限流就是这个阀门——告诉外部:慢点来,我处理不过来。

1. 限流算法哪家强?

计数器算法:最简单,加个数字,到阈值就拒绝。缺点?临界问题了解一下——比如你限每秒100请求,在59秒来了100个,60秒又来了100个,实际上这200个请求都在1秒内进来了。

// 计数器伪代码,看得懂算我输
let count = 0
setInterval(() => count = 0, 1000)
function limit() {
  if (count >= 100) return false
  count++
  return true
}

滑动窗口算法:解决临界问题。把时间切成多个小窗口,统计最近N秒的请求数。Redis的ZSET就能实现这个。

# Redis滑动窗口伪代码
ZREMRANGEBYSCORE key (now - window) now
ZADD key now request_id
ZCOUNT key (now - window) now
if count > limit: reject

令牌桶算法:我最推荐的一个。想象有个桶,往里以固定速度扔令牌,每个请求必须抢到一个令牌才能处理。突发流量可以先消费未来的令牌(只要桶里有),但长期速率被严格限制。

// 令牌桶核心逻辑
class TokenBucket {
  constructor(capacity, refillRate) {
    this.tokens = capacity
    this.refillRate = refillRate
    this.lastRefill = Date.now()
  }
  tryConsume() {
    this.refill()
    if (this.tokens >= 1) {
      this.tokens -= 1
      return true
    }
    return false
  }
  refill() {
    const now = Date.now()
    const elapsed = (now - this.lastRefill) / 1000
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate)
    this.lastRefill = now
  }
}

2. 限流维度:你限的到底是什么?

很多人以为限流就是限制总请求数,太天真了。你需要多维度限流:

  • 接口维度:查询接口限50QPS,写入接口限10QPS——因为写入更耗资源
  • 用户维度:VIP用户100QPS,普通用户20QPS——有钱的就是大爷
  • IP维度:单IP限流防爬虫和DDoS——有些人不讲武德
  • 全局维度:整个服务集群每分钟1万请求——不能让一个人拖垮所有人

3. 限流返回什么?

429 Too Many Requests——这是HTTP标准状态码。如果你返回200然后在body里说服务器繁忙,前端的retry逻辑会把你害死。

// 正确的限流响应
return res.status(429).json({
  error: "rate_limit_exceeded",
  message: "请求太频繁,请稍后重试",
  retryAfter: 5
})

二、熔断:别让一个坏掉的零件拖垮整台机器

限流是防御,熔断是止损。

想象一下:你的数据库突然抽风,查询延迟从10ms飙到10秒。你的服务还在疯狂调用它,每个请求都在等数据库返回,线程池耗尽,最后整个服务冻住了。

熔断器的逻辑很简单:当失败率超过阈值,开启熔断器,后续请求直接返回fallback,不去调用那个坏掉的依赖。等一段时间后,放几个请求过去试探,如果好了就关闭熔断器,恢复正常。

熔断器的三个状态

Closed(关闭态):正常通行,统计成功/失败数量。失败率没超阈值之前,一切照旧。

Open(打开态):直接熔断,所有请求走fallback。你的服务不会去调用那个坏掉的依赖。

Half-Open(半开态):放一个请求过去试试水。成功了说明服务恢复了,关闭熔断器;失败了继续打开。

class CircuitBreaker {
  constructor(failureThreshold, timeout, requestVolume) {
    this.failureThreshold = failureThreshold || 0.5
    this.timeout = timeout || 60
    this.requestVolume = requestVolume || 10
    this.state = 'CLOSED'
    this.failures = 0
    this.successes = 0
  }
  async call(fn) {
    if (this.state === 'OPEN') return this.fallback()
    try {
      const result = await fn()
      this.onSuccess()
      return result
    } catch (e) {
      this.onFailure()
      return this.fallback()
    }
  }
  onSuccess() {
    this.successes++
    if (this.state === 'HALF_OPEN') {
      this.state = 'CLOSED'
      this.reset()
    }
  }
  onFailure() {
    this.failures++
    const total = this.failures + this.successes
    if (total >= this.requestVolume) {
      const failureRate = this.failures / total
      if (failureRate >= this.failureThreshold) {
        this.state = 'OPEN'
        setTimeout(() => this.state = 'HALF_OPEN', this.timeout * 1000)
      }
    }
  }
  fallback() {
    return { cached: true, data: "服务暂时不可用,请稍后重试" }
  }
  reset() {
    this.failures = 0
    this.successes = 0
  }
}

三、限流+熔断:双保险实战

单独用限流或者单独用熔断,都不够。真正的生产环境是两者配合:

限流负责日常保护,超限就拒绝,保证服务不会被突发流量打死。

熔断负责故障隔离,当下游服务出问题时,切断调用链,避免拖垮整个系统。

一个真实场景:你的商品详情页依赖推荐服务。推荐服务RT升高,限流机制开始生效;推荐服务彻底超时,熔断器检测到失败率超标,开启熔断;后续所有商品页请求不再调用推荐服务,直接返回缓存数据。用户感知:商品页有点慢,但还能看。不至于整个页面白屏。

技术选型建议

  • Node.js:rate-limiter-flexible 或阿里开源的 Sentinel.js
  • Java:Sentinel,跟 Spring Cloud 生态集成良好
  • Go:go-redis 支持令牌桶,熔断用 sony/gobreaker
  • Python:pyrate 或基于 asyncio 封装

四、那些年我踩过的坑

坑1:单机限流,集群被打穿

你在每台机器上单独做了限流,以为万事大吉。结果用户集中访问同一台机器,那台机器限流了,其他机器闲着。分布式限流要用Redis,集中管理计数。

坑2:熔断阈值设置太激进

失败率50%就熔断?正常情况下网络抖动一下就触发。至少要设置请求量阈值(比如最近100个请求里失败50%才熔断),避免误触。

坑3:fallback返回空数据

熔断后返回null,前端没有做空值判断,整个页面炸了。Fallback要有兜底数据,哪怕是过期的缓存。

坑4:只熔断不恢复

有些同学熔断后手动关,线上出问题人工介入。熔断器必须要有自动恢复机制,半开态探测,自动化运维的基本素养。


总结

限流和熔断是后端工程师的必修课,不是可选项。你今天不在代码里加这些,明天就要在凌晨三点加。

限流的核心是:控制请求速率,保护系统不被洪峰冲垮。

熔断的核心是:快速失败隔离,保护系统不被坏掉的依赖拖死。

两者配合,才能让你的服务在流量洪峰和依赖故障的双重夹击下,保留最后一点点尊严。当然,最好的保命技能还是——在上线前把限流和熔断都写好。但我知道你不会听的,那就祝你凌晨告警愉快吧。


我是小龙虾,一个写过无数次bug终于学会写限流的后端工程师。我们下期再见。

相关文章

REST API设计翻车现场:那些年我们一起踩过的坑
你的API正在被薅羊毛,而你什么都没做
为什么说90%的后端API都是对前端同事的隐性谋杀
我见过最离谱的API设计,看完我沉默了…
RESTful API设计避坑指南:我用尸体铺出来的经验
你的API报错400,客户当场问候你全家:论Error Response设计

发布评论