高并发系统三板斧-缓存降级限流
高并发治理的第一步,不是急着提升处理速度,而是减少系统真正需要处理的请求。缓存、限流和降级虽然解决的问题不同,但都在帮助系统减少不必要的消耗。
高并发系统设计(一):减少请求,不只是加缓存
前言
系统出现性能问题时,我们很容易把注意力放在“如何处理得更快”上。
接口慢了,就优化 SQL;线程不够了,就调整线程池;机器扛不住了,就继续扩容。这些方式当然有用,但它们都默认了一个前提:进入系统的每一个请求,都应该被完整处理。
实际上,这个前提并不成立。
同一份数据可能正在被反复查询,超过系统容量的流量可能已经没有能力处理,一个请求内部也可能包含大量非核心逻辑。如果这些请求全部进入核心链路,再强的系统也有被拖垮的一天。
所以高并发治理的第一步,往往不是提升系统处理请求的速度,而是先减少系统真正需要处理的请求。
这里所说的“减少请求”,并不只是把请求拒之门外,而是包含三个层面:
- 能够复用结果的请求,不再重复执行,这就是缓存。
- 超过系统容量的请求,不再继续接收,这就是限流。
- 已经接收的请求,不再执行全部逻辑,这就是降级。
缓存、限流和降级看起来是三个独立的技术名词,背后却是同一个思路:
不要让每一个请求,都以最高成本穿透整个系统。
这篇文章先不讲代码,也不讨论具体应该如何设计。我们先把缓存、限流和降级这三个概念讲清楚,理解它们分别减少了什么,以及为什么它们会成为高并发系统最基础的三种手段。
一、缓存:减少重复处理的请求
先说缓存。
缓存是开发中最常见的性能优化手段。很多人对缓存的理解是:Redis 比数据库快,所以把数据放进 Redis,接口速度就能提升。
这个理解没有错,但只看到了缓存的表面。
缓存真正重要的价值,不是换了一个更快的存储介质,而是让相同的请求不必反复进入后面的处理链路。
比如一个商品详情在短时间内被访问了十万次。商品名称、图片和介绍并没有发生变化,但如果每次请求都查询数据库,那么数据库就要重复回答十万次相同的问题。
有了缓存以后,第一次请求负责生成结果,后续请求直接复用这份结果。数据库需要处理的,不再是十万次查询,而可能只有少数几次。
你会发现,数据库本身并没有变快,SQL 也没有发生变化,只是大量重复请求在到达数据库之前就已经结束了。
这才是缓存和“减少请求”之间真正的关系。
不过,并不是所有数据都适合缓存。
缓存更适合读取频率高、变化频率低,并且允许短时间复用结果的数据。例如商品介绍、配置信息、字典数据、榜单结果等。它们的共同特点是:计算一次以后,可以服务后面的很多次请求。
相反,如果数据变化非常频繁,或者每次读取都必须拿到绝对准确的结果,那么缓存带来的就不只有性能收益,还会增加数据一致性的理解成本。
所以缓存并不是“查询接口的标准配置”。在考虑缓存以前,应该先问三个问题:
- 这些请求得到的结果是不是相同的?
- 这个结果可以被复用多长时间?
- 返回短时间内的旧结果,业务能不能接受?
这三个问题没有答案,缓存就很容易从性能优化手段变成新的故障来源。
缓存解决的是这样一类问题:
系统有能力处理这些请求,但是没有必要重复处理。
它减少的是重复查询、重复计算,以及相同结果对核心资源的反复消耗。
但是缓存不能解决所有问题。
当请求本身无法复用,或者流量已经远远超过系统容量时,即使缓存命中率很高,系统仍然可能被压垮。这个时候,就需要在入口处控制请求数量。
二、限流:减少超过系统容量的请求
任何系统都有容量上限。
一套系统可能每秒稳定处理一千个请求,也可能处理一万个请求,但不可能无限处理。线程、连接、CPU、内存和下游服务,都会形成边界。
平时流量没有触碰这个边界,问题不明显。一旦遇到活动、热点事件或者异常重试,请求量突然超过系统容量,系统就会开始排队。
很多人会觉得,多进来一些请求无非是慢一点,只要让它们继续等待,系统总能处理完。
真实情况往往相反。
请求进入系统以后,会占用线程、连接和内存。等待的请求越多,能够真正完成工作的资源反而越少。响应时间开始升高,调用方因为超时继续重试,又产生更多请求,最终形成一个恶性循环。
原本只是部分请求处理不了,最后可能变成所有请求都处理不了。
限流的作用,就是在系统容量和外部流量之间画出一条边界。系统能够处理多少,就接收多少;超过容量的部分,不再继续进入核心链路。
所以,限流不是单纯地拒绝用户,也不是系统偷懒。它是在资源有限的情况下,主动放弃无法完成的请求,避免这些请求占用资源以后拖垮整个系统。
举个简单的例子。
一部电梯最多承载十个人。门口来了三十个人,正确的做法不是让三十个人全部挤进去,然后期待电梯慢一点也能运行,而是先让十个人进入,剩下的人等待下一次。
系统限流也是一样。
如果系统稳定容量是一千个请求,那么第一千零一个请求不是“再努努力就能处理”,而可能是让前面一千个请求一起变慢的开始。
这里还有一个常见误区:限流不是只看系统总请求量。
同样是一千个请求,可能来自不同的用户、接口、业务和资源。某个热点商品、某个异常客户端或者某个大客户,都可能独占系统资源。因此,限流真正保护的不是一个抽象的 QPS 数字,而是系统背后的稀缺资源。
这一篇我们先不展开限流算法、阈值计算和规则放在哪里。大家先记住限流要解决的问题:
系统已经没有能力处理更多请求,就不要再让新的请求继续消耗核心资源。
缓存面对的是“可以处理,但没有必要重复处理”;限流面对的是“想处理,但已经处理不过来”。
但还有一种情况:请求量并没有超过入口限制,请求也已经进入系统,可其中某些非核心服务变慢了。如果仍然要求每个请求完整执行,核心业务一样会被非核心能力拖住。
这时候就需要降级。
三、降级:减少单次请求中的非核心工作
降级和限流经常被放在一起讨论,也很容易被混淆。
限流是在请求进入系统以前做取舍,决定哪些请求可以进来;降级是在请求已经进来以后做取舍,决定这个请求还需要执行多少内容。
比如一个商品详情接口,除了查询商品、价格和库存,还可能查询推荐商品、用户画像、优惠活动和浏览统计。
在正常情况下,这些内容都可以执行,用户得到的是一份完整的商品详情。
但是当推荐服务开始超时,或者系统资源已经比较紧张时,如果商品详情仍然等待推荐结果,那么一个非核心功能就会拖慢整个核心链路。
降级的做法,是暂时放弃这部分非核心能力。
推荐商品可以不展示,浏览统计可以暂停,个性化标签可以换成通用内容。用户看到的功能没有平时完整,但最核心的商品查询和购买能力仍然可用。
这就是降级。
它不是让请求消失,也不是直接告诉用户系统不可用,而是让一次原本需要执行十件事的请求,在特殊情况下只执行最重要的三件事。
从“减少请求”的角度看,降级减少的不是入口请求数量,而是请求在系统内部继续发起的调用、计算和资源消耗。
所以,降级首先是一个业务问题,然后才是技术问题。
哪些功能必须保留,哪些功能可以暂时关闭;哪些数据必须实时准确,哪些数据可以使用旧值;哪些失败需要直接阻断流程,哪些失败可以被忽略。这些边界不应该等到系统出故障时,再由开发人员临时决定。
如果核心和非核心没有提前分清,那么所谓降级,很容易变成随手写一个异常捕获。表面上接口没有报错,实际上返回的数据已经无法支撑业务。
真正的降级,是在服务能力下降时,有意识地缩小系统承诺的范围。
它解决的是这样一类问题:
请求已经进入系统,但没有必要在任何情况下都完整执行。
限流是控制系统接收多少工作,降级是控制系统为每个请求完成多少工作。两者作用的位置不同,但目的都是避免有限资源被快速耗尽。
四、总结
回到文章最开始的问题:为什么缓存、限流和降级,都可以被看作减少请求的方式?
因为高并发系统真正关心的,不只是入口有多少次 HTTP 调用,而是这些调用最终让核心系统承担了多少工作。
- 缓存减少的是重复工作,让相同结果不必反复生成。
- 限流减少的是入口流量,让超过容量的请求不再继续消耗资源。
- 降级减少的是执行内容,让请求在特殊情况下只保留核心逻辑。
三者解决的问题不同,也不能互相替代。
只有缓存,没有限流,突发流量仍然可能冲垮系统;只有限流,没有缓存,大量可以复用的结果仍然在浪费处理能力;只有降级,没有入口控制,请求无限增长以后,缩短过的链路最终也会过载。
所以它们不是三种互相竞争的技术方案,而是从不同位置减少系统负担的三道防线。
这一篇只需要先建立一个基本认知:
高并发治理,不只是想办法把更多请求处理掉,更重要的是判断哪些请求不必重复处理、哪些请求不能继续接收、哪些请求不必完整执行。
先把请求和工作量减下来,再讨论并行、异步、线程池、数据库优化以及扩容,后面的优化才有意义。