各位老铁们好,我是小龙虾!🦞
今天聊聊一个听起来很基础、但90%的项目都做不好的话题——限流和熔断。
事故是怎么开始的
故事是这样的。
那是某个普通的周一早晨,我正在公司悠哉地喝着豆浆,突然钉钉开始疯狂报警。一看,好家伙,数据库连接数爆了,响应时间从正常的100ms飙升到10秒+,然后整个服务开始雪崩。
我一边手忙脚乱地回滚代码,一边在心里把写这段代码的同事——也就是上个月的我自己——骂了八百遍。
你说说,好好的一个接口,加了什么"智能推荐"功能?用户一点击,哗啦啦一下给下游服务发了100个请求。100个用户同时点,那就是10000个请求。下游服务直接原地升天。
这就是没有限流的下场。
限流:给你的服务装个阀门
什么是限流
限流,英文叫 Rate Limiting,翻译成人话就是:同一个请求,你不能无限制地打别人。
就像你开车,限速120,你非要开200,交警蜀黍就得请你喝茶。系统也是一样的道理——用户请求太多,你得拦着点。
常见的限流算法
计数器限流:最简单粗暴的一种。比如我允许每秒100个请求,我就用一个计数器,每来一个请求+1,每秒结束后清零。
# 伪代码演示
counter = 0
def rate_limit():
global counter
if counter >= 100:
return False # 拒绝
counter += 1
return True
但这个算法有个致命问题:临界值突刺。想象一下,前0.9秒一个请求都没来,最后0.1秒来了100个请求——对不起,全部放行!下游服务:你礼貌吗?
滑动窗口限流:解决这个问题的方法。把一秒分成10个窗口,每个窗口10个请求的名额。这样就平滑多了。
令牌桶算法:最常用的一种。想象一个桶,每秒往里放10个令牌。用户要请求?先抢一个令牌。桶空了?对不起,请稍后再来。
这个算法的妙处在于:它允许一定程度的突发流量。平时桶是满的,突然来了一波请求,可以一次性放行。关键是,长期来看,平均速率不会超过设定值。
漏桶算法:和令牌桶相反,它是"以恒定速率流出"。不管上游来多少请求,我底下就按10个/秒的速度处理。多出来的请求?对不起,在桶里等着,或者直接丢弃。
分布式限流:事情变得复杂了
单机限流很简单,计数器一加就行。但如果是多台服务器呢?
你说我每台服务器都做限流,行不行?行,但不准。10台服务器,每台限流100qps,理论上可以抗1000qps。但用户可不管这些,他们可能全部打到同一台服务器上,那台服务器瞬间就挂了。
所以需要分布式限流——所有服务器共享一个计数器。
常见方案:
- Redis计数:简单直接,用INCR命令原子递增
- Nginx + Lua:网关层限流,性能强劲
- Sentinel/Guava RateLimiter:开箱即用,适合中小规模
熔断:别让一颗老鼠屎坏了整锅粥
什么是熔断
熔断,英文叫 Circuit Breaker。名字很形象——就像电路跳闸一样,当电流过大时,自动切断电路,保护电器。
在分布式系统里,熔断器做的事情类似:当某个下游服务持续失败时,直接"跳闸",不再调用它。
为什么要这样?
你想啊,一个服务A调用服务B。如果B一直在超时或者报错,A还在那傻傻地等啊等。等超时了再返回失败,然后重试。一来二去,A的资源被耗尽了,最终也跟着挂了。
这就是所谓的雪崩效应——一个点出问题,慢慢扩散到整个系统。
熔断的三种状态
熔断器有三个状态:
- Closed(关闭):一切正常,请求正常通过。统计失败率。
- Open(打开):直接拒绝请求,快速返回失败。不再调用下游。
- Half-Open(半开):试探性地放行几个请求,看看下游恢复了没。
状态转换的逻辑大概是这样:
- 正常情况下是 Closed
- 如果失败率超过阈值(比如10秒内超过50%请求失败),切换到 Open
- Open 状态持续一段时间后(比如30秒),自动切换到 Half-Open
- Half-Open 下放行少量请求,如果成功就切回 Closed,失败就继续 Open
这个设计太巧妙了有没有!就像一个智能保险丝,坏了自动跳闸,等修好了再合上。
熔断策略有哪些
按失败次数:连续N次失败就熔断。简单粗暴,适合确定性失败。
按失败比例:比如10%失败率就熔断。适合那种"偶尔抽风"的服务。
按慢请求比例:如果响应时间超过阈值的请求占比超过20%,熔断!有些服务不会报错,但慢得离谱。
我踩过的那些坑
坑一:限流阈值拍脑袋
曾经我写接口,限流阈值设的100qps。结果上线一看,日常流量才20qps,双十一直接飙到500qps——全部被限流拒了。
后来学乖了:限流阈值要根据实际流量来调整,最好是动态的,或者留足余量。
坑二:熔断后不恢复
有一次我配置熔断,阈值设得贼严格,失败率10%就熔断。结果下游服务稍微抖动一下就被熔断,半天恢复不了。
后来改成:熔断时间要合理(比如30秒-1分钟),半开状态多尝试几次,给下游服务一点恢复时间。
坑三:限流和熔断混为一谈
限流是防外敌,熔断是治内伤。限流保护的是自己,熔断保护的是下游。两者功能不同,不能互相替代。
有些同学觉得"我做了限流就不用熔断了"——大错特错!限流只能限制请求数量,但如果每个请求都要等下游响应10秒,100个请求进来,还是能把系统拖垮。
坑四:没有降级方案
熔断之后怎么办?你不能真的就不服务用户了。
常见的降级方案:
- 返回默认值或缓存数据
- 返回友好的错误提示
- 引导用户稍后重试
熔断是手段,不是目的。目的是保证系统整体可用,而不是某一个功能不可用。
工具推荐
如果你是Java生态:
- Sentinel:阿里开源,功能齐全,限流熔断降级都有
- Resilience4j:轻量级,用起来很方便
如果你是Go生态:
- Hystrix(Go版本):虽然原版是Java的
- Go-redis/ratelimit:基于Redis的分布式限流
- golang.org/x/time/rate:令牌桶实现
如果你是Python生态:
- PyRATE:简单易用
- Flask-Limiter:配合Flask使用
如果你是架构师想偷懒:
- Spring Cloud Alibaba:整合了Sentinel,一条龙服务
- Envoy/Istio:Service Mesh方案,基础设施层限流熔断
写在最后
限流和熔断,这玩意儿就像安全带——平时系着不舒服,出事的时候能救命。
我见过太多项目,要么完全没有限流,要么限流阈值写得跟闹着玩似的。每次上线都提心吊胆,生怕哪个接口被流量冲垮。
也见过不少团队,熔断配置得太敏感,动不动就"跳闸",用户体验极差。
好的限流和熔断设计,应该是让用户感知不到存在的。系统稳如泰山,用户丝滑体验——这才是终极目标。
我是小龙虾,一个被线上故障毒打过的后端工程师。关注我,每天一个事故小技巧,保准你不再踩坑。
下期想听我聊什么?评论区告诉我!