1. 别等线上炸了才懂:高并发治理是微服务绕不过去的坎
我有个习惯,接手一个新项目先不看代码,先看监控面板和告警记录。这两年经手的微服务项目多了,有个很深的感受:大多数系统不是被慢 SQL 拖垮的,也不是被 Bug 搞挂的,而是被“流量”打挂的。尤其是有过一次线上事故之后,你会对“高并发”这三个字有完全不一样的理解。
那次事故发生在一次秒杀活动。系统上线前压测看起来一切正常,QPS 峰值能做到 3000 左右,结果活动一开始,流量瞬间冲到 8000,服务直接卡死,紧接着数据库连接池被打满,Redis 报超时,MQ 堆积开始暴涨,最后整个集群雪崩。事后复盘的时候我们发现,问题并不是出在单点性能上,而是出在整个链路上没有任何一道“守卫”——没有限流,没有熔断,分布式锁用的姿势也不对,消息队列只管发不管消费。一顿操作下来,其实我们连 3000 QPS 的真实水平都没达到,系统就倒了。
这篇文章我想把这几年在高并发和微服务实战里踩过的坑、总结出来的套路,系统性地梳理一遍。包含分布式锁的正确姿势、消息队列削峰填谷的完整链路、限流熔断的落地避坑,还有一套可以直接抄作业的微服务高并发骨架。内容偏实战,不堆概念,适合正在做微服务改造、或者被线上高并发问题折磨过的后端工程师阅读。如果你是刚接触微服务的新人,也可以从头看,我会把关键原理用直白的话讲清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高并发系统首先要回答的三个问题
2.1 流量进来之后,你的系统到底在怕什么
做高并发设计之前,先搞清楚系统面对流量暴增时到底在怕什么。我总结下来就三类:资源不够、数据不对、链路不稳。
资源不够,说的是 CPU、内存、连接池、带宽这些硬指标撑不住了。很多人以为高并发就是加机器,其实不对。加机器只能解决一部分计算压力,数据库连接池、文件存储、第三方接口这些瓶颈不是靠加机器就能绕过去的。数据不对,说的是并发场景下的数据竞争问题——库存超卖、账户扣错、订单状态错乱,这些本质上都是并发写操作没有做好互斥。链路不稳,说的是一个下游服务抖动,导致上游所有服务跟着一起等,等变成超时,超时变成重试,重试变成更大的流量,最后整个系统像多米诺骨牌一样倒掉。
这三个问题对应的解决手段,恰好就是文章标题里的分布式锁、消息队列、限流熔断。我经常跟团队说一句话:高并发治理不是做加法,而是做减法和防守。减法是把不必要的同步调用改成异步,防守是在每个入口、每个依赖边界上把流量管住。
2.2 从用户点击到下单成功,链路里每一环都可能是堵点
举个具体的例子。一个典型的用户下单流程是这样的:用户点击下单,请求先打到 Nginx,再进到网关,网关做路由和限流,然后把请求转发到订单服务,订单服务要校验库存,要扣减库存,要创建订单,要发消息给下游服务。这个流程里,每一个环节都有可能出现堵点。
Nginx 层是第一个堵点,连接数、带宽、worker 进程数都有限制。网关层容易堵在鉴权和转发上,如果过滤器里做了大量的同步 IO 操作,网关会先挂掉。订单服务最容易堵在数据库上,尤其是扣库存这一条 SQL,热点行更新会导致锁等待。消息队列看似是缓冲,但如果消费者消费速度跟不上生产速度,队列堆积越来越多,下游的服务会被延迟消息冲垮。
所以做高并发设计,思维上要先从“单点优化”切换成“链路治理”。你不光要考虑每个服务自己能扛多少 QPS,还要考虑服务和服务之间是怎么协作的,依赖关系是什么,故障是怎么传播的。这也是为什么现在微服务架构里特别强调流量治理这个概念——把限流、熔断、降级、隔离这些能力做进框架里,而不是靠开发人员在业务代码里自己写。
2.3 高并发治理的四板斧:分流、缓冲、互斥、兜底
我把高并发治理的常用手段总结成四板斧:分流、缓冲、互斥、兜底。
分流指的是把流量分散到多个节点、多个链路上去,比如负载均衡、读写分离、缓存挡在数据库前面。缓冲指的是用消息队列把突发的流量存下来,让下游按照自己的节奏去消费,削峰填谷就是这个意思。互斥指的是在分布式环境下保证同一份数据只能被一个线程修改,Redis 分布式锁就是干这个用的。兜底指的是当系统实在扛不住的时候,要有限流熔断降级这样的保护机制,让系统有尊严地失败,而不是整体崩溃。
这四个能力组合在一起,就是一个完整的高并发系统骨架。这篇文章后面每个部分,其实都是在讲这四个能力在实践当中怎么落地。我建议你在做架构设计的时候,也可以拿这四板斧去检查自己的系统,看看每个环节到底缺了哪一个。
3. 分布式锁:为什么说 synchronized 在微服务里不顶用了
3.1 单机锁和分布式锁的本质区别
很多新手会对分布式锁有一个误解,觉得它跟 synchronized 差不多,只是换了个实现方式。这个理解是错的。
synchronized 是 Java 虚拟机层面的锁,它的作用范围是单个 JVM 进程。你在单机部署的时候,多个线程访问同一个资源,synchronized 可以保证互斥。但是微服务部署之后,同一个服务通常会启动多个实例,每个实例是独立的 JVM 进程,线程锁就管不到其他实例上的线程了。
我画过一张图来解释这个事:假设一个库存服务部署了 3 个实例,用户请求被负载均衡分发到不同的实例上。如果每个实例各自用 synchronized 锁住扣减库存的方法,三个请求同时到达三个实例,三个实例可以同时执行扣减逻辑,库存就有可能会扣成负数。分布式锁要解决的是跨进程的互斥问题,它需要一个所有实例都能访问到的公共组件,Redis、ZooKeeper、数据库都可以充当这个角色,但实现难度和可靠性各不相同。
3.2 Redis SETNX 不是不能用,是坑太多
说到分布式锁,很多人第一时间想到 Redis 的 SETNX 命令。这个思路本身没错,但真正落地的时候,坑非常多。
最早期的写法是 SETNX lock_key value,如果返回 1,说明拿到了锁。拿到锁之后执行业务,最后 DEL 删除锁。这个写法有两个致命问题:第一,如果拿到锁的线程在执行业务的过程中挂了,锁永远不会被释放,其他线程永远拿不到锁;第二,如果 A 线程的锁超时过期了,B 线程拿到了新锁,A 线程执行完业务直接 DEL,把 B 的锁给删了,导致多个线程同时进入临界区。
后来官方推荐了原子命令 `SET lock
