一套能保命的高并发实战指南

92 阅读7分钟

沉默是金,总会发光

大家好,我是沉默

上篇文章我们聊了:“扛住高并发,得有限流、缓存、异步这些武器。”
如果还没看的朋友,可以看看:京东外卖上线就挂了,高并发架构该这么扛!

今天这篇,就来把这些武器细细拆开讲透——

限流算法怎么选?
缓存更新怎么做?
消息队列细节怎么踩稳?

一篇吃透,真正掌握抵御高并发的底气。

image.png

**-**01-

限流

为什么要限流?

想象下:

  • 你开一辆小车,在高速路口突然涌来10万辆车,不管不顾往里冲。

  • 你的发动机再好、刹车再灵,都必然“嗝屁”。

系统也是一样:

负载过大,不崩才怪。

所以限流,就是设置一道闸门:
——来多少车,按节奏、按流速放行,保护后端系统。

常见限流算法

限流,主要靠两大算法流派:
漏桶(Leaky Bucket)令牌桶(Token Bucket)

我们来逐个看看:

1. 漏桶算法(Leaky Bucket)

坚持匀速,不怕狂风暴雨。

原理

  • 系统有一个漏桶,水(请求)不断灌进来。

  • 桶底的小洞以固定速率漏水(处理请求)。

  • 桶满了,新的水就溢出,直接丢弃。

举个栗子🌰:

排队办证大厅,一个号一个号叫,叫得再快也要等叫号,急没用。

适合场景

  • 后台批处理系统

  • 对抖动极为敏感的金融、结算业务

2. 令牌桶算法(Token Bucket)

灵活发牌,允许短时爆发。

原理

  • 桶里定时加入令牌(token)。

  • 请求来时,先拿一个令牌,才能被处理。

  • 没令牌的请求,要么排队,要么丢弃。

举个栗子🌰:

麦当劳点单机,后台根据顾客数发小票号,允许一阵快一阵慢。

适合场景

  • 电商秒杀系统

  • 短时高峰的营销活动

实际工程应用

一般系统组合使用

  • 网关层:漏桶,兜住整体流速

  • 业务接口:令牌桶,允许高峰弹性

再配合限流框架,比如:

  • Sentinel

  • Nginx限速模块

建议:

  • 别一味丢请求,高并发系统要做智能限流+友好降级提示

  • 比如:“系统繁忙,请稍后再试。” 而不是直接500爆掉。

**-**02-

缓存

为什么缓存是高并发第一护盾?

因为内存访问(比如Redis)是纳秒级的,数据库IO是毫秒级的。

直接查DB?卡得飞起。
打Cache?秒回。

所以,缓存搞不好,系统直接废。

缓存和数据库常见问题

缓存用得多了,必然碰到这些:

  • 缓存与数据库数据不一致

  • 缓存击穿、穿透、雪崩

每一个,都是血泪史。

那,怎么做对?

常见的缓存更新策略

1. Cache Aside(旁路缓存)

流程

  • 查缓存 → 缓存没命中 → 查数据库 → 写入缓存

  • 更新时 → 更新数据库 → 删除缓存

举个栗子🌰:

你找车钥匙,先翻抽屉(缓存),找不到再去问爸妈(DB),拿到后用完放回抽屉。

优点

  • 简单直观

  • 读多写少的场景很适合

缺点

  • 写更新期间,可能出现缓存和DB短暂不一致

2. Write Through(写穿缓存)

流程

  • 写的时候直接同步更新缓存和数据库。

举个栗子🌰:

新买了车钥匙,马上放抽屉一份,保险箱也一份。

优点

  • 保证一致性更好

缺点

  • 写入链路变长,性能开销大

3. Refresh Ahead(提前刷新)

流程

  • 缓存快过期时,自动后台刷新。

举个栗子🌰:

知道抽屉钥匙快坏了,提前重新配好一把放进去。

优点

  • 热点数据持续在线

  • 防止击穿

缺点

  • 需要定时任务维护

缓存一致性怎么搞?

要记住一条大原则:

更新数据库 → 删除缓存 → 允许下次查询时再重建缓存

而不是:

更新缓存 → 更新数据库(会乱套)

高并发下加一招:延迟双删

  • 第一次删缓存

  • 延迟几十ms,再次删缓存(兜住并发写冲突)

如何防击穿、穿透、雪崩?

  • 防击穿(单热点爆缓存):缓存空对象+短TTL

  • 防穿透(查不存在的key):布隆过滤器拦截

  • 防雪崩(大批量缓存同时过期):加随机TTL分散过期时间

**-**03-

消息队列

为什么需要消息队列?

因为再快的后端,也扛不住一波洪水直接冲。

MQ就像一个缓冲池,把高并发的请求拦下来,慢慢处理。

消息队列应用场景

  • 秒杀抢购:下单请求排队

  • 大促结算:异步写账单

  • 积分发放:异步通知

MQ用得好,三个关键细节

1. 幂等性(Idempotent)

MQ消息可能重复发送,如果消费端不防,业务就炸了。

解决方案

  • 消费前先查数据库:看操作是否已完成

  • 每条消息带唯一业务ID(如订单ID)

举个栗子🌰:

像快递签收,不能重复签一份。

2. 消费重试(Retry)

网络闪断、临时异常,导致消费失败。

解决方案

  • 设置消费重试机制

  • 达到重试上限,打入死信队列(DLQ)

举个栗子🌰:

快递员三次派件失败,快递放驿站。

3. 顺序性保障(Ordering)

某些场景(比如库存扣减),消息顺序不能乱

解决方案

  • 同一业务key(如订单ID)哈希分到同一个分区

  • 单线程顺序消费

举个栗子🌰:

像考场排队入场,按准考证号顺序一个个进门。

消息堆积怎么处理?

当消费跟不上生产:

  • 加消费并发(多线程、多实例)

  • 限速生产(源头削峰)

  • 按优先级消费(重要业务优先)

要记住一句话:

削峰填谷,才是MQ最大的价值。

**-**04-

实战案例

项目基本搭建

用一个常规 Spring Boot 项目来演示:

1. 创建Spring Boot工程

选依赖时勾选:

  • Spring Web

  • Sentinel

  • Redis

  • RabbitMQ

Maven pom.xml

<dependencies>  <!-- Web服务 -->  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId>  </dependency>  <!-- Sentinel限流 -->  <dependency>    <groupId>com.alibaba.csp</groupId>    <artifactId>sentinel-spring-boot-starter</artifactId>    <version>1.8.6</version>  </dependency>  <!-- Redis缓存 -->  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId>  </dependency>  <!-- RabbitMQ消息队列 -->  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-amqp</artifactId>  </dependency></dependencies>

2. Sentinel 接入限流接口

安装 Sentinel Dashboard(控制台),运行:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -jar sentinel-dashboard-1.8.6.jar

application.yml 配置:

spring:  cloud:    sentinel:      transport:        dashboard:          localhost:8080

定义一个简单接口:

@RestController@RequestMapping("/order")public class OrderController {  @GetMapping("/create")  @SentinelResource(value = "createOrder", blockHandler = "handleBlock")  public String createOrder() {    return "订单创建成功!";      }  public String handleBlock(BlockException ex) {      return "系统繁忙,请稍后再试~";      }}

启动后,去 Sentinel 控制台添加流控规则:

  • 资源名:createOrder

  • 阈值类型:QPS

  • 单机阈值:10(每秒10次)

效果

  • 正常流量返回“订单创建成功”

  • 超过QPS,返回“系统繁忙”

3. Redis 缓存-订单详情

Redis配置(application.yml):

spring:    redis:      host: localhost      port: 6379

服务类:

@Servicepublic class OrderService {  @Autowired  private RedisTemplate<StringString> redisTemplate;  private static final String ORDER_CACHE_PREFIX="order:";  public String getOrderDetail(String orderId) {    String cacheKey= ORDER_CACHE_PREFIX + orderId;    // 先查缓存    Stringdetail= redisTemplate.opsForValue().get(cacheKey);    if (detail != null) {      return "【缓存命中】" + detail;            }    // 模拟数据库查询            detail = "订单详情 - " + orderId;    // 写入缓存            redisTemplate.opsForValue().set(cacheKey, detail, Duration.ofMinutes(10));    return "【数据库查询】" + detail;      }}

控制器调用:

@RestController@RequestMapping("/order")public class OrderController {  @Autowired  private OrderService orderService;  @GetMapping("/detail/{orderId}")  public String getOrderDetail(@PathVariable String orderId) {    return orderService.getOrderDetail(orderId);      } }

测试:

  • 第一次查 /order/detail/123,走数据库

  • 第二次查 /order/detail/123,走缓存

4. RabbitMQ 异步下单

application.yml 配置:

spring:  rabbitmq:    host: localhost    port: 5672    username: guest    password: guest

消息模型:

  • 队列:order.queue

  • 生产者发消息

  • 消费者监听并处理

创建队列配置:

@Configurationpublic class RabbitConfig {  public static final String ORDER_QUEUE="order.queue";  @Beanpublic   Queue orderQueue() {    return newQueue(ORDER_QUEUEtrue);      }}

生产者发送消息:

@Servicepublic class OrderProducer {  @Autowired   private RabbitTemplate rabbitTemplate;  public void sendOrder(String orderId) {    rabbitTemplate.convertAndSend(RabbitConfig.ORDER_QUEUE, orderId);      }}

消费者监听处理:

@Componentpublic class OrderConsumer {  @RabbitListener(queues = RabbitConfig.ORDER_QUEUE)  public void handleOrder(String orderId) {            System.out.println("接收到订单处理请求:" + orderId);    // 异步处理下单逻辑      }}

控制器触发异步下单:

@RestController@RequestMapping("/order")public class OrderController {  @Autowired private OrderProducer orderProducer;  @GetMapping("/asyncCreate/{orderId}")  public String asyncCreate(@PathVariable String orderId) {    orderProducer.sendOrder(orderId);    return"订单异步创建中,请稍后查看结果~";      }}

测试:

  • 调用 /order/asyncCreate/10001

  • 控制台打印:“接收到订单处理请求:10001”

如果你看到这里,恭喜你已经掌握了一套真正能抗住高并发的硬核武器。
后续还会继续带来进阶内容,比如:

  • 限流算法原理:漏桶 vs 令牌桶 深度讲解

  • 缓存更新策略(旁路、双写一致性、主动刷新)

  • 消息队列的消费顺序、幂等性处理秘籍

想看的话,在评论区扣个 1!

**-**05-

粉丝福利

关注:架构师沉默,送你互联网大厂面试题库,如果你正在找工作,又或者刚准备换工作。可以仔细阅读一下,或许对你有所帮助!