本页脑图:缓存高并发与锁
缓存与锁
穿透
- 数据不存在
- 参数校验
- 布隆过滤器
- 缓存空值
击穿
- 热点 Key 过期
- 互斥锁
- 逻辑过期
- 热点永不过期
雪崩
- 大量 Key 失效
- TTL 随机
- 多级缓存
- Redis 高可用
分布式锁
- 数据库
- Redis
- ZooKeeper
- 唯一 value
穿透查不存在,击穿打热点,雪崩倒一片。
缓存穿透、击穿、雪崩
| 问题 | 本质 | 触发原因 | 危害 | 解决方案 |
|---|---|---|---|---|
| 穿透 | 缓存没有,数据库也没有,请求直接打库。 | 恶意非法 ID、业务异常参数。 | 持续压垮数据库。 | 参数校验、布隆过滤器、缓存空值、鉴权限流。 |
| 击穿 | 单个热点 Key 失效,大量请求同时打库。 | 热点商品、热点 Token、缓存过期。 | 瞬间压垮数据库。 | 互斥锁、逻辑过期、热点 Key 永不过期、预热。 |
| 雪崩 | 大量 Key 同时失效或缓存集群宕机。 | TTL 集中过期、Redis 故障。 | 系统级崩溃。 | TTL 随机化、多级缓存、高可用集群、熔断降级。 |
口诀:穿透查不存在,击穿打热点,雪崩倒一片。
缓存防御体系:入口、缓存、数据库、服务四层
- 入口层:参数校验、鉴权、验证码、IP 限流,先拦明显异常请求。
- 缓存层:布隆过滤器防穿透,逻辑过期防击穿,TTL 随机化防雪崩。
- 数据层:读写分离、索引优化、分库分表,避免数据库成为唯一瓶颈。
- 服务层:熔断、降级、隔离、告警,确保数据库扛不住时系统不被拖垮。
案例题写法:按请求链路从前往后写,不要只写 Redis。高并发优化最好按 CDN/网关/应用/缓存/数据库/消息队列/监控这条链路组织答案。
高并发链路细化
| 链路层次 | 典型措施 | 解决问题 | 易错点 |
|---|---|---|---|
| 客户端/边缘 | 静态资源 CDN、页面静态化、客户端限流 | 减少源站压力,降低延迟。 | CDN 适合静态或可缓存内容,不适合强实时私密数据。 |
| 入口网关 | 鉴权、限流、黑名单、验证码、WAF | 挡住恶意流量和超过系统能力的流量。 | 限流不是越严越好,要结合业务优先级。 |
| 应用层 | 线程池隔离、熔断降级、异步处理、批量合并 | 防止局部慢调用拖垮整个服务。 | 重试要有限制,否则会放大流量。 |
| 缓存层 | 多级缓存、预热、逻辑过期、布隆过滤器 | 降低数据库读压力。 | 缓存要考虑一致性和失效策略。 |
| 数据库层 | 索引、读写分离、分库分表、分区 | 提升查询和写入能力。 | 分库分表会带来跨库事务和查询复杂度。 |
| 消息层 | Kafka/RabbitMQ 削峰、异步落库 | 把瞬时流量变成平滑处理。 | 要处理幂等、重复消费和消息堆积。 |
为什么需要分布式锁
单机锁只能保护一个 JVM 内部的线程安全;服务多实例部署后,不同机器上的线程会同时操作同一份资源,例如库存扣减、定时任务抢占、热点缓存重建,这时需要跨进程、跨机器的互斥机制。
| 实现 | 原理 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| 数据库 | 唯一索引或事务锁。 | 简单,依赖少,一致性强。 | 性能低,死锁清理麻烦,数据库压力大。 | 低并发、高一致批处理。 |
| Redis | SET key value NX PX timeout 原子加锁。 | 性能高,工程实践广。 | 主从切换极端情况下可能丢锁。 | 秒杀、限流、热点缓存重建。 |
| ZooKeeper | 临时顺序节点 + Watch。 | 一致性强,天然防死锁,公平。 | 吞吐较低,运维复杂。 | 任务调度、配置协调、强一致场景。 |
Redis 锁必须会写的细节
- 加锁原子性:必须用
SET key value NX PX timeout,不能先 setnx 再 expire,因为中间宕机会死锁。 - value 唯一:用 UUID 标识持锁客户端,释放锁时先比较 value 再删除,防止误删别人的锁。
- 释放原子性:比较和删除应使用 Lua 脚本保证原子。
- 锁过期:设置过期时间防止客户端宕机永久持锁。
- 锁续期:Redisson 看门狗可自动续期,防止业务未执行完锁先过期。
风险点:Redis 主从异步复制下,主节点加锁成功后尚未同步给从节点就宕机,从节点升主可能导致另一个客户端也拿到锁。强一致场景优先 ZooKeeper 或数据库事务方案。
Redis 锁 Lua 释放逻辑
释放锁不能直接 DEL key,因为可能误删别人后来获得的锁。正确逻辑是“先比较 value 是否等于自己的 UUID,再删除”。比较和删除要原子执行。
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end
缓存一致性常见方案
Cache Aside
读:先读缓存,未命中查库并回填。写:先更新数据库,再删除缓存。工程最常见。
延迟双删
更新数据库后删除缓存,延迟一小段时间再删一次,降低并发读写造成脏缓存的概率。
消息通知
数据库变更后发送消息,缓存服务订阅并失效或刷新缓存。
binlog 订阅
通过 Canal 等订阅数据库 binlog,异步刷新缓存或搜索索引。
论文写法:秒杀/高并发场景
流量入口削峰 → 热点数据缓存 → 库存扣减互斥 → 异步下单 → 监控降级。
示例:在秒杀场景中,我首先通过 CDN 和网关限流过滤无效流量;其次将商品、库存和活动配置预热到 Redis,并对热点 Key 采用逻辑过期和互斥锁重建;库存扣减通过 Redis 原子操作和分布式锁保证不超卖;下单流程通过消息队列异步落库,实现削峰填谷;当数据库延迟超过阈值时触发熔断降级,返回排队提示。