缓存更新怎么做?
消息队列细节怎么踩稳?
一篇吃透,真正掌握抵御高并发的底气。
一、限流
1、为什么要限流?
想象下:
你开一辆小车,在高速路口突然涌来10万辆车,不管不顾往里冲。
你的发动机再好、刹车再灵,都必然“嗝屁”。
系统也是一样:负载过大,不崩才怪。
所以限流,就是设置一道闸门——
来多少车,按节奏、按流速放行,保护后端系统。
2、常见限流算法
限流,主要靠两大算法流派:
漏桶(Leaky Bucket) 和 令牌桶(Token Bucket)。
我们来逐个看看:
1)漏桶算法(Leaky Bucket)
坚持匀速,不怕狂风暴雨。
原理:
举个例子:
排队办证大厅,一个号一个号叫,叫得再快也要等叫号,急没用。
特点总结:
适合场景:
2)令牌桶算法(Token Bucket)
灵活发牌,允许短时爆发。
原理:
举个例子:
麦当劳点单机,后台根据顾客数发小票号,允许一阵快一阵慢。
特点总结:
适合场景:
3)漏桶 VS 令牌桶,对比总结
实际工程应用
一般系统组合使用:
再配合限流框架,比如:
建议:
二、缓存
为什么缓存是高并发第一护盾?
因为内存访问(比如Redis)是纳秒级的,数据库IO是毫秒级的。
直接查DB?卡得飞起。
打Cache?秒回。
所以,缓存搞不好,系统直接废。
1、缓存和数据库常见问题
缓存用得多了,必然碰到这些:
每一个,都是血泪史。
那,怎么做对?
2、常见的缓存更新策略
1)Cache Aside(旁路缓存)
流程:
举个例子:
优点:
缺点:
2)Write Through(写穿缓存)
流程:
写的时候直接同步更新缓存和数据库。
举个例子:
优点:
缺点:
3)Refresh Ahead(提前刷新)
流程:
举个例子:
优点:
缺点:
3、缓存一致性怎么搞?
要记住一条大原则:
更新数据库 → 删除缓存 → 允许下次查询时再重建缓存
而不是:更新缓存 → 更新数据库(会乱套)
高并发下加一招:延迟双删
4、如何防击穿、穿透、雪崩?
三、消息队列
为什么需要消息队列?
因为再快的后端,也扛不住一波洪水直接冲。
MQ就像一个缓冲池,把高并发的请求拦下来,慢慢处理。
1、消息队列应用场景
2、MQ用得好,三个关键细节
1)幂等性(Idempotent)
MQ消息可能重复发送,如果消费端不防,业务就炸了。
解决方案:
举个例子:
2)消费重试(Retry)
网络闪断、临时异常,导致消费失败。
解决方案:
举个例子:
3)顺序性保障(Ordering)
某些场景(比如库存扣减),消息顺序不能乱。
解决方案:
举个例子:
3、消息堆积怎么处理?
当消费跟不上生产:
要记住一句话:削峰填谷,才是MQ最大的价值。
四、实战案例
用一个常规 Spring Boot 项目来演示:
环境配置:
1、创建Spring Boot工程
选依赖时勾选:
Maven pom.xml :
org.springframework.boot
spring-boot-starter-web
com.alibaba.csp
sentinel-spring-boot-starter
1.8.6
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-amqp
2、Sentinel 接入限流接口
1)安装 Sentinel Dashboard(控制台),运行:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -jar sentinel-dashboard-1.8.6.jar
2)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 控制台添加流控规则:
5)效果:
3、Redis 缓存-订单详情
1)Redis配置(application.yml):
spring:
redis:
host: localhost
port: 6379
2)服务类:
@Service
public class OrderService {
@Autowired
private RedisTemplate 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 {
@Autowired
private OrderService orderService;
@GetMapping("/detail/{orderId}")
public String getOrderDetail(@PathVariable String orderId) {
return orderService.getOrderDetail(orderId);
}
}
4)测试:
4、RabbitMQ 异步下单
1)application.yml 配置:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
2)消息模型:
3)创建队列配置:
@Configuration
public class RabbitConfig {
public static final String ORDER_QUEUE="order.queue";
@Beanpublic
Queue orderQueue() {
return newQueue(ORDER_QUEUE, true);
}
}
4)生产者发送消息:
@Service
public class OrderProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrder(String orderId) {
rabbitTemplate.convertAndSend(RabbitConfig.ORDER_QUEUE, orderId);
}
}
5)消费者监听处理:
@Component
public 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(@PathVariable String orderId) {
orderProducer.sendOrder(orderId);
return"订单异步创建中,请稍后查看结果~";
}
}
7)测试:
如果你看到这里,恭喜你已经掌握了一套真正能抗住高并发的硬核武器。
作者丨沉默聊科技
来源丨公众号:架构师沉默(ID:CM_IT-1024)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721