高并发系统的典型瓶颈在哪
大促零点那一刻,平时每秒几百的请求突然冲到几万。系统崩之前,总有一个先顶不住的环节:可能是数据库连接被打满、可能是下游接口超时拖垮线程池、可能是单机网卡跑满。高并发架构的第一原则是先定位瓶颈,再动手扩容——盲目加机器往往只把瓶颈往后推了一层。
常见瓶颈有四类:计算瓶颈(CPU 跑满)、IO 瓶颈(数据库、磁盘)、外部依赖瓶颈(第三方接口慢)、资源池瓶颈(线程池、连接池耗尽)。后两类最隐蔽——机器很闲但服务超时,多半是资源池没配好或下游拖死了线程。
- 异步化把非关键路径从同步调用里拆出去,缩短响应时间。
- 削峰填谷用消息队列把突发流量摊平成平稳流量。
- 熔断降级下游故障时快速失败、返回兜底,防止雪崩扩散。
- 弹性伸缩按流量自动增减实例,但要先算清容量。
异步化:把同步调用拆成事件驱动
一个下单接口如果同步调用了库存、优惠券、积分、消息通知四个下游,响应时间就是四者之和,任一变慢都拖垮整体。异步化的思路是:只把必须同步完成的步骤留在主链路上(扣库存、生成订单),把可以稍后做的(发通知、加积分、写日志)发到消息队列,由消费者慢慢处理。主链路变短,吞吐自然上去。
// 同步主链路:只做必须立刻完成的事
Order order = orderService.create(req); // 本地事务,落库
// 异步:这些发完消息就返回,不阻塞响应
mq.send("order-created", orderEvent); // 下游各自订阅消费
return order; // 用户立刻拿到响应
代价是最终一致性:消息可能延迟、可能重复消费,下游要做幂等。异步化不是免费的午餐,它把"快"换给了主链路,把"对"交给了消息可靠投递与消费幂等。
削峰填谷:流量整形的几种手段
突发流量的可怕之处在于"短时间内冲垮系统,而系统平时有余量"。削峰填谷就是把尖峰摊平:消息队列是最经典的削峰工具——请求进来先入队,后端按自己能处理的速率消费,流量从"脉冲"变成"平稳流"。秒杀场景尤其依赖这一招:10 万人同时点击,请求全部进队列,后端每秒放 1000 个通过,谁先进队列谁先得。
其他整形手段还有:前端按钮置灰防重复提交、网关层排队令牌(先拿令牌再放行)、库存预热提前放到 Redis 让绝大多数请求在缓存层就被挡掉。核心思想都是"不让突发流量同时砸到后端"。
缓存抗读与数据库扛写的分工
高并发系统里,读流量和写流量要分开治理。读流量尽量由缓存扛:详情页、商品信息、排行榜这些热点,99% 的读请求应该命中 Redis 或本地缓存,数据库只在未命中时被回源。写流量才是数据库真正的压力,能异步化的异步化、能合并写的合并写、能批量刷盘的批量刷盘。
一条经验法则:读多写少的系统,缓存设计的收益远大于数据库调优。把读请求挡在缓存层,数据库的连接数和 CPU 立刻宽裕下来,系统能扛的并发成倍提升。
限流、熔断与降级的组合拳
这三件事经常一起出现,但作用点不同。限流是入口挡流量:超过阈值的请求直接拒绝或排队,保护后端不被打垮。熔断是下游出问题时快速失败:某个下游持续超时,熔断器打开,后续请求不再调用它,直接走兜底,避免线程被慢慢拖死。降级是主动牺牲非核心功能:大促时关掉个性化推荐、关掉评论加载,把资源留给下单主链路。
| 手段 | 作用点 | 触发后的行为 |
|---|---|---|
| 限流 | 入口/网关 | 超阈值请求拒绝或排队 |
| 熔断 | 调用下游处 | 下游故障时快速失败走兜底 |
| 降级 | 非核心功能 | 关闭次要功能,保主链路 |
三者配合:入口限流挡住超限流量,熔断防止故障扩散,降级保证核心链路活着。缺少任何一环,一处故障都可能演变成全站雪崩。
连接池、线程池与参数调优
很多"系统很闲却响应慢"的事故,根源是资源池配置。数据库连接池太小,并发一上来请求就在排队等连接;线程池太大,上下文切换吃掉 CPU;超时时间设得太长,下游一抖动,所有线程都被挂住。调优的原则是:连接数按数据库能承受的上限反推、线程池按 CPU 密集还是 IO 密集分档、超时时间宁可短也要快速失败。
# 数据库连接池:不是越大越好
maximumPoolSize=20 # 按数据库 max_connections 反推
connectionTimeout=2000 # 拿不到连接 2 秒就失败,不无限等
# 线程池:IO 密集型可大一些,但要有界
corePoolSize=50
maxPoolSize=200
queueCapacity=1000 # 有界队列,拒绝策略兜底
弹性伸缩与容量评估
云环境下可以按 CPU、QPS 自动增减实例,但弹性伸缩有滞后——流量已经冲进来,新实例还没启动完成。因此大促前要提前扩容到预估峰值的 1.5 倍,而不是依赖自动伸缩。容量评估的基本方法:压测出单机能扛的 QPS,除以目标峰值,再留足冗余,得到需要的实例数。
另一个常被忽视的点是状态:有状态的服务(本地缓存、本地会话)扩容后迁移成本高,高并发架构里尽量把状态外置到 Redis,让实例变成无状态、随时可增减。
实战清单
把高并发落地的顺序记成一张清单:
- 先压测定位瓶颈,再决定扩哪一层。
- 非关键路径全部异步化,主链路越短越好。
- 突发流量用消息队列整形,别让脉冲砸后端。
- 读流量交给缓存,写流量异步合并。
- 限流、熔断、降级三件套配齐,防雪崩扩散。
- 连接池、线程池、超时时间按瓶颈调优。
- 大促提前扩容,实例无状态可随时增减。
高并发没有银弹,它是一系列取舍的组合:用异步换响应时间、用队列换平稳、用降级换主链路存活。把每一环的代价想清楚,系统才能在大流量下既快又稳。