从限流算法、缓存策略到消息队列,一套能保命的高并发实战指南
限流算法怎么选?
缓存更新怎么做?
消息队列细节怎么踩稳?
一篇吃透,真正掌握抵御高并发的底气。
一、限流
想象下:
你开一辆小车,在高速路口突然涌来10万辆车,不管不顾往里冲。
你的发动机再好、刹车再灵,都必然“嗝屁”。
系统也是一样:负载过大,不崩才怪。
所以限流,就是设置一道闸门——
来多少车,按节奏、按流速放行,保护后端系统。
限流,主要靠两大算法流派:
漏桶(Leaky Bucket) 和 令牌桶(Token Bucket)。
我们来逐个看看:
1)漏桶算法(Leaky Bucket)
坚持匀速,不怕狂风暴雨。
原理:
系统有一个漏桶,水(请求)不断灌进来。
桶底的小洞以固定速率漏水(处理请求)。
桶满了,新的水就溢出,直接丢弃。
举个例子:
排队办证大厅,一个号一个号叫,叫得再快也要等叫号,急没用。
特点总结:
适合场景:
后台批处理系统
对抖动极为敏感的金融、结算业务
2)令牌桶算法(Token Bucket)
灵活发牌,允许短时爆发。
原理:
桶里定时加入令牌(token)。
请求来时,先拿一个令牌,才能被处理。
没令牌的请求,要么排队,要么丢弃。
举个例子:
麦当劳点单机,后台根据顾客数发小票号,允许一阵快一阵慢。
特点总结:
适合场景:
电商秒杀系统
短时高峰的营销活动
3)漏桶 VS 令牌桶,对比总结
实际工程应用
一般系统组合使用:
网关层:漏桶,兜住整体流速
业务接口:令牌桶,允许高峰弹性
再配合限流框架,比如:
Sentinel
Nginx限速模块
建议:
别一味丢请求,高并发系统要做智能限流+友好降级提示。
比如:“系统繁忙,请稍后再试。” 而不是直接500爆掉。
二、缓存
为什么缓存是高并发第一护盾?
因为内存访问(比如Redis)是纳秒级的,数据库IO是毫秒级的。
直接查DB?卡得飞起。
打Cache?秒回。
所以,缓存搞不好,系统直接废。
缓存用得多了,必然碰到这些:
缓存与数据库数据不一致
缓存击穿、穿透、雪崩
每一个,都是血泪史。
那,怎么做对?
1)Cache Aside(旁路缓存)
流程:
查缓存 → 缓存没命中 → 查数据库 → 写入缓存
更新时 → 更新数据库 → 删除缓存
举个例子:
你找车钥匙,先翻抽屉(缓存),找不到再去问爸妈(DB),拿到后用完放回抽屉。
优点:
简单直观
读多写少的场景很适合
缺点:
写更新期间,可能出现缓存和DB短暂不一致
2)Write Through(写穿缓存)
流程:
写的时候直接同步更新缓存和数据库。
举个例子:
新买了车钥匙,马上放抽屉一份,保险箱也一份。
优点:
保证一致性更好
缺点:
写入链路变长,性能开销大
3)Refresh Ahead(提前刷新)
流程:
缓存快过期时,自动后台刷新。
举个例子:
知道抽屉钥匙快坏了,提前重新配好一把放进去。
优点:
热点数据持续在线防止击穿
缺点:
需要定时任务维护
要记住一条大原则:
更新数据库 → 删除缓存 → 允许下次查询时再重建缓存
而不是:更新缓存 → 更新数据库(会乱套)
高并发下加一招:延迟双删
第一次删缓存
延迟几十ms,再次删缓存(兜住并发写冲突)
防击穿(单热点爆缓存):缓存空对象+短TTL
防穿透(查不存在的key):布隆过滤器拦截
防雪崩(大批量缓存同时过期):加随机TTL分散过期时间
三、消息队列
为什么需要消息队列?
因为再快的后端,也扛不住一波洪水直接冲。
MQ就像一个缓冲池,把高并发的请求拦下来,慢慢处理。
秒杀抢购:下单请求排队
大促结算:异步写账单
积分发放:异步通知
1)幂等性(Idempotent)
MQ消息可能重复发送,如果消费端不防,业务就炸了。
解决方案:
消费前先查数据库:看操作是否已完成
每条消息带唯一业务ID(如订单ID)
举个例子:
像快递签收,不能重复签一份。
2)消费重试(Retry)
网络闪断、临时异常,导致消费失败。
解决方案:
设置消费重试机制
达到重试上限,打入死信队列(DLQ)
举个例子:
快递员三次派件失败,快递放驿站。
3)顺序性保障(Ordering)
某些场景(比如库存扣减),消息顺序不能乱。
解决方案:
同一业务key(如订单ID)哈希分到同一个分区
单线程顺序消费
举个例子:
像考场排队入场,按准考证号顺序一个个进门。
当消费跟不上生产:
加消费并发(多线程、多实例)
限速生产(源头削峰)
按优先级消费(重要业务优先)
要记住一句话:削峰填谷,才是MQ最大的价值。
四、实战案例
用一个常规 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>
1)安装 Sentinel Dashboard(控制台),运行:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -jar sentinel-dashboard-1.8.6.jar2)application.yml 配置:
spring:cloud:sentinel:transport:dashboard:localhost:8080
3)定义一个简单接口:
@RestController@RequestMapping("/order")public class OrderController {@GetMapping("/create")@SentinelResource(value = "createOrder", blockHandler = "handleBlock")public String createOrder() {return "订单创建成功!";}public String handleBlock(BlockException ex) {return "系统繁忙,请稍后再试~";}}
4)启动后,去 Sentinel 控制台添加流控规则:
资源名:createOrder
阈值类型:QPS
单机阈值:10(每秒10次)
5)效果:
正常流量返回“订单创建成功”
超过QPS,返回“系统繁忙”
1)Redis配置(application.yml):
spring:redis:host: localhostport: 6379
2)服务类:
@Servicepublic class OrderService {@Autowiredprivate RedisTemplate<String, String> 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;}}
3)控制器调用:
@RestController@RequestMapping("/order")public class OrderController {@Autowiredprivate OrderService orderService;@GetMapping("/detail/{orderId}")public String getOrderDetail(@PathVariableString orderId) {return orderService.getOrderDetail(orderId);}}
4)测试:
第一次查 /order/detail/123,走数据库
第二次查 /order/detail/123,走缓存
1)application.yml 配置:
spring:rabbitmq:host: localhostport: 5672username: guestpassword: guest
2)消息模型:
队列:order.queue
生产者发消息
消费者监听并处理
3)创建队列配置:
@Configurationpublic class RabbitConfig {public static final String ORDER_QUEUE="order.queue";@BeanpublicQueue orderQueue() {return newQueue(ORDER_QUEUE, true);}}
4)生产者发送消息:
@Servicepublic class OrderProducer {@Autowiredprivate RabbitTemplate rabbitTemplate;public void sendOrder(String orderId) {rabbitTemplate.convertAndSend(RabbitConfig.ORDER_QUEUE, orderId);}}
5)消费者监听处理:
@Componentpublic class OrderConsumer {@RabbitListener(queues = RabbitConfig.ORDER_QUEUE)public void handleOrder(String orderId) {System.out.println("接收到订单处理请求:" + orderId);// 异步处理下单逻辑}}
6)控制器触发异步下单:
@RestController@RequestMapping("/order")public class OrderController {@Autowired private OrderProducer orderProducer;@GetMapping("/asyncCreate/{orderId}")public String asyncCreate(@PathVariableString orderId) {orderProducer.sendOrder(orderId);return"订单异步创建中,请稍后查看结果~";}}
7)测试:
调用 /order/asyncCreate/10001
控制台打印:“接收到订单处理请求:10001”
如果你看到这里,恭喜你已经掌握了一套真正能抗住高并发的硬核武器。