ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

写给后端工程师:如何优雅地处理高并发请求

写给后端工程师:如何优雅地处理高并发请求 你负责的接口每秒只能扛住500个请求但老板要求它扛住50万。这不是虚构的桥段而是每个后端工程师都会撞上的那堵墙。高并发请求像一场突袭的暴风雨你手里的微服务框架、数据库、缓存集群瞬间从从容的协奏曲变成撕心裂肺的尖叫。你开始疯狂调参、加机器、改代码却发现越努力越焦虑。高并发请求的本质是排队问题而排队问题的核心是时间——不是服务器的CPU快慢而是每个请求愿意等待多久、系统又能容忍多少个请求同时等待。优雅地处理高并发不是把机器堆到无限而是学会在有限资源下做有策略的放弃。先把你心中的“高并发”翻译成数字很多工程师开口就说“我们要应对高并发”但当被问起“目标QPS、P99延迟、可用性指标”时眼神就开始飘忽。没有量化目标的高并发处理都是自我感动。优雅的第一条原则是不处理并发只处理请求计数。你要先回答三个问题每秒最多进来多少个请求单个请求最晚多久返回算合格系统在多大比例的失败之内还能被接受这三组数字一摆出来你就知道该用缓存扛还是用限流挡。如果你连这些数字都没跟产品对齐那么任何技术方案都是赌博。高并发系统的每一项设计本质上是对时间预算的分配。请求进来后它在队列里待多久在CPU上跑多久在数据库连接池里等多久在网络上耗多久——这些时间的总和决定了用户体验。后端工程师的优雅就是让每个请求的时间预算透明化、可量化、可博弈。有了目标数字你才能判断那50万QPS里有多少是读请求、多少是写请求读多写少是绝大部分业务的常态这意味着你的缓存层至少可以拦截掉80%的流量。写请求能不能合并、批量、异步化比如点赞、日志、埋点根本不需要实时落库。真正的优雅是在入口处就把请求分类成“必须即时处理”和“可以稍后再办”。分类做得好你的系统压力直接下降一个数量级。限流说“不”是一门艺术很多人害怕限流觉得丢弃请求是天大的罪过。但面对超出承载能力的洪峰拒绝一部分请求不是认输而是保护大多数请求的生存权。服务器就像一家餐厅厨房最多同时做10桌菜来了100桌客人如果每桌都上菜最后所有人都饿死。限流就是领班堵在门口告诉后面的客人“暂时没位”要么排队等号要么改天再来。限流算法里令牌桶、漏桶、滑动窗口各有优劣但比算法更重要的是你要在哪个层面限流。网关层做全局限流应用层做接口级限流数据库层做连接池限制每个层面都有自己的节奏。优雅的限流不是把流量粗暴地挡死而是给流量一个“预期”——返回429比让请求超时更善良。客户端收到429可以重试、降级、提醒用户而超时只会让用户反复点击产生更多流量形成雪崩。限流另一个常被忽略的维度是“公平性”。有些用户是VIP有些是普通用户有些是内部监控。你可以在令牌桶里加权重也可以给关键路径单独开一条泳道。没有权重的限流是懒政有权重的限流才是艺术。当你的限流策略能精准识别“哪些请求该丢、哪些请求必须保”你就已经超越了90%的后端工程师。缓存让请求死在最靠近用户的地方高并发读请求的终极解法就是让请求根本不落到业务逻辑上。缓存不是加速器而是请求的“安乐死”——在数据还没触及数据库之前它就带着结果满意地离开了。这就是优雅的最高境界没有请求处理只有结果命中。缓存的层次要像护城河一样层层递进客户端缓存、CDN、网关缓存、应用本地缓存、分布式缓存。每一层都能拦截一部分流量但每一层都有数据一致性的代价。别再纠结“缓存和数据库一致性”的老话题了真正的业务场景里能接受秒级不一致的缓存远比追求强一致但频繁打穿数据库的缓存更优雅。你需要做的是为不同数据设定不同的缓存策略读多写少的用TTL热点数据用内存级缓存关键交易数据根本不该被缓存。缓存最怕的是什么是缓存击穿、缓存穿透、缓存雪崩。这三个敌人有一个共同的根源你把缓存当成了万能的盾却没有给盾牌配一个“如果不存在怎么办”的预案。穿透要用布隆过滤器拦住不存在的key击穿要用互斥锁或逻辑过期保护热点key重建雪崩要给过期时间加随机值还要准备永不过期后台异步更新的后台线程。只有把这些细节都打磨好缓存才能真正成为压垮并发的那根稻草——压垮的是流量不是你的数据库。异步让系统学会呼吸同步处理高并发的悲哀在于每个请求都死死攥着一个线程线程们排队等待IO时CPU却在旁边嗑瓜子。线程是资源而等待是最昂贵的资源浪费。优雅的系统应该学会异步请求进来后立刻返回一个“受理凭证”后台线程池悄悄把活干完然后再通知你结果。这样你的线程不再陪着外部服务一起磨叽而是把宝贵的CPU时间用来处理更多新请求。异步的实现方式从老派的线程池、消息队列到现代的协程、响应式编程本质都是把“请求-响应”从同步阻塞变成事件驱动。你不需要立刻给用户结果只需要告诉用户“我收到了稍后告诉你”。这在削峰填谷的典型场景里无比美妙比如秒杀系统所有用户点击瞬间到达你只要把请求接入消息队列让后端系统按自己的消费速率慢慢处理剩下的几个小时后告诉他们“抢购成功”即可。异步真正解决的不是请求变少而是请求与处理之间的时间差被拉长了——系统有了喘息的空间。别把异步局限于业务代码数据库写入也可以异步化。比如订单表、日志表、统计表完全可以先打进本地队列或消息中间件由消费者批量落库。批量是异步的孪生兄弟一次批量写100条和100次单条写前者的时间开销小到令人感动。但别忘了给异步系统设计好消息确认、失败重试和幂等处理。否则消息丢了、重复了、乱序了你会发现异步带来的优雅变成了更高级的灾难。熔断与隔离活下来的系统才有资格优雅当依赖的下游服务开始变慢你的系统不能跟着它一起崩溃。高并发下最愚蠢的行为就是让所有服务共享同一个命运的绳索——下游一抖全链路瘫痪。熔断器的原理很简单当错误率超过阈值直接打开开关快速失败不再发起请求过一段冷却时间之后放一小撮流量去试探如果恢复就慢慢关闭。这就是“电网跳闸”思想在后端世界的应用。隔离则是把服务划分成一个个独立的舱室就像轮船的隔水舱一个舱进水只淹一个舱船不会沉。你可以在线程池层面隔离给不同的调用方分配不同的线程池谁也不许抢谁也可以在服务模块层面隔离把核心交易和边缘服务彻底拆开。还记得那个著名的“酒店预订”事故吗一个低优先级的报告服务把数据库连接池耗尽导致所有预订接口全部超时。如果用线程池隔离加熔断那个报告服务就能优雅地失败而核心预订服务依然稳定。熔断和隔离看起来让架构更复杂了但它们换来的是“局部失败”的勇气。在一个高并发的世界里允许部分请求失败不是失败允许故障蔓延才是真正的失败。你的系统应该有一个“坏死检查清单”哪些故障自动容忍哪些故障快速失败哪些故障靠降级方案撑着。把这些写清楚比写一百行“try-catch”有用得多。扩容与压测优雅需要用证据说话很多人以为设计好缓存、限流、异步、熔断系统就稳了。可真正上线时你会发现瓶颈永远在你没有预料到的地方可能是DNS解析太慢可能是日志写入卡了IO可能是线程上下文切换开销巨大。没有经过压测的优雅只是纸面优雅。你要用压测工具把系统打到它自己喘不过气为止观察它的吞吐量曲线、延迟柱状图、错误率变化找到那个“膝盖点”——超过这个点性能急剧恶化。然后你在膝盖点附近设置合理的水位线让限流器刚好在膝盖点之前介入。扩容也不是无限加机器。水平扩容有一个残酷的预设你的系统得是“无状态”的。如果你的会话、本地缓存、数据库连接都绑死在单机上再加多少机器都没用。所以在设计之初就让自己变成“可以被随便杀掉或复活”的无状态进程。每个实例都相当于一个透明的水滴你随时可以往池子里加水滴也可以移除水滴。只有这样的系统扩容才是一句命令的事。压测之后你还要做混沌演练手动杀掉一个Redis节点停掉一个消费者把数据库磁盘占满看看系统会不会自动降级。优雅的高并发系统应该像一位老练的船长——他不需要保证风浪不来他只要保证风浪来了船还能按计划航行。每一次演练都是在给你的系统积累“事故底气”让你在凌晨两点被报警吵醒时不至于手抖。优雅是一种有策略的放弃后端工程师处理高并发常常陷入了“什么都要完美”的执念请求不能丢延迟不能高数据不能错。但现实物理定律告诉我们任何系统都有承载上限优雅的本质是承认这个上限然后在边界内做最聪明的取舍。你可以拒绝一部分请求换得更多请求的响应你可以接受偶尔的重复消息换得极致的吞吐你可以容忍缓存的脏读换得数据库的安宁。这些取舍不是妥协而是设计。当你把限流、缓存、异步、熔断、隔离、扩容、压测这七板斧都用熟之后你会发现高并发不再是一个敌人而是一个可以被估算、被测量、被管理的服务对象。你不追求“处理所有请求”你只追求“稳定地处理你应该处理的请求”。那个曾经让你整夜失眠的50万QPS被你解剖成一个个可控制的子问题。系统依然会有故障但你不再狼狈。真正的优雅不是永远不倒下而是倒下时还有一套Predefined的姿势。这姿势里包含着一行你自己写的限流日志一个从容返回的429一条在队列里等待的消息和一个始终坚挺的数据库连接池。这远比“看似成功了所有请求实际上把系统拖入了深渊”要高级得多。后端工程师们请记住我们不是超人我们是懂得如何让系统在绝境中体面生存的设计师。
返回列表