基础
事务
Redis 支持事务。单个命令具有原子性;多个命令可以通过 MULTI 和 EXEC 组成事务,但 Redis 事务不提供传统关系型数据库事务的回滚能力。
命令
SETNX key value:只在 key 不存在时将值设为 value
分布式锁
- Redisson


优化:
- 分段锁:Hash 运算 分段锁,或者商品id锁
存在问题:
- 主从架构可能会存在主节点数据未同步从节点导致锁重复
- Redlock
- ZooKeeper:CP 一致性,数据同步时会短时间不可用
缓存淘汰
当 Redis 使用的内存达到 maxmemory 后,会按照 maxmemory-policy 决定拒绝写入还是淘汰部分 key。Redis 使用的是近似 LRU/LFU 算法,不是传统的“双向链表 + 哈希表”精确实现,也不提供 FIFO 或 LRU-K 淘汰策略。
| 策略 | 候选范围 | 行为 |
|---|---|---|
noeviction | 无 | 不淘汰 key,可能增加内存的写命令返回错误 |
allkeys-lru | 所有 key | 淘汰最近最少使用的 key |
allkeys-lfu | 所有 key | 淘汰访问频率最低的 key |
allkeys-random | 所有 key | 随机淘汰 key |
volatile-lru | 设置了过期时间的 key | 淘汰最近最少使用的 key |
volatile-lfu | 设置了过期时间的 key | 淘汰访问频率最低的 key |
volatile-random | 设置了过期时间的 key | 随机淘汰 key |
volatile-ttl | 设置了过期时间的 key | 优先淘汰剩余生存时间较短的 key |
缓存场景通常从
allkeys-lru或allkeys-lfu中选择;不能容忍数据被自动删除时使用noeviction。具体选择应依据访问分布和数据可恢复性。


过期删除
Redis 通过两种机制配合删除过期 key:
- 惰性过期(Passive expiration):客户端访问 key 时检查其是否过期;如果已过期,先删除再按 key 不存在处理。
- 主动过期(Active expiration):Redis 周期性地从设置了过期时间的 key 中抽样检查并删除过期项,避免长期不被访问的过期 key 一直占用内存。
Redis 不会为每个 key 创建一个到期定时器,因为大量定时器会带来显著的 CPU 和内存开销。过期删除与内存淘汰也不是一回事:前者处理已经到期的 key,后者在达到 maxmemory 时根据策略释放空间。
缓存一致
Cache Aside 的核心思想就是,当缓存的数据有更新值了,它采用的不是更新缓存数据,而是删除缓存数据
保证最终一致性
Cache Aside 常用的写入顺序是:先更新数据库,事务提交成功后再删除缓存。如果删除失败,需要通过重试、消息队列或 Binlog/CDC 补偿。下面的延迟双删主要用于解释“先删缓存、再更新数据库”时的并发补偿方案。
延迟双删
先删除缓存、再更新数据库时,可能出现以下并发问题:
- 写线程删除缓存。
- 读线程发现缓存不存在,从数据库读到旧值。
- 写线程更新数据库为新值。
- 读线程把之前读到的旧值写回缓存。
最终数据库是新值,缓存却长期保留旧值。延迟双删会在数据库更新后等待一小段时间,再删除一次缓存,把并发读线程可能回填的旧值清理掉。
删除缓存 → 更新数据库 → 延迟一段时间 → 再次删除缓存public void updateProduct(Product product) {
String key = "product:" + product.getId();
// 第一次删除,避免后续请求继续读取旧缓存
redisTemplate.delete(key);
productRepository.update(product);
// 实际项目应交给延迟队列或异步任务,不要阻塞业务线程
delayQueue.send(() -> redisTemplate.delete(key), 500);
}延迟时间不能随意写死,应大于一次并发读请求完成“查询数据库并回填缓存”的最长耗时,同时结合接口延迟监控动态评估。第二次删除还必须具备重试能力,否则 Redis 超时或消息丢失后仍可能留下脏数据。
| 优点 | 局限 |
|---|---|
| 实现简单,能缩短脏缓存存在时间 | 只能保证最终一致性,不能保证强一致性 |
| 适合读多写少、允许短暂不一致的场景 | 依赖合理的延迟时间和可靠的第二次删除 |
| 不需要同时更新数据库和缓存 | 写操作回滚、进程宕机、删除失败都需要额外处理 |
生产环境通常使用消息队列、延迟队列或定时补偿执行第二次删除,并记录失败任务。对一致性要求更高时,可采用“更新数据库后删除缓存 + 删除失败重试”,或监听 Binlog/CDC 异步失效缓存。不要把延迟双删当成强一致性方案。

其他缓存更新模式

- Read/Write Through

- Write Behind

缓存击穿
查询某个数据的值,该值在缓存中无,在数据库中有
- 一般缓存都会设置个数据过期时间,所以缓存击穿的情况比较常见
- 缓存击穿带来的问题:如果对该值查询突然特别大,由于缓存中无该数据,所以请求就会打到后端数据库上
- 处理
- 从MySQL角度出发:减少击穿后的直接流量

- 从Redis角度出发:
- 设置热点数据永不过期
- 热点数据后台启动一个异步线程,重新把数据回填缓存层
缓存穿透
查询一个数据库也不存在的数据,即缓存查不到,数据库也查不到所以透了
- 拦截非法查询请求
- 缓存空对象
- 布隆过滤器
- 设计思路:设计k个hash映射函数,这k个映射函数各不相同,把在数据库中的有效数据通过这个映射函数得到数组指定位置,并置为1
- 查询思路:输入查询数据,通过这一系列hash函数,判断对应位置是否全为1。如果是,则很大概率就是我们的有效请求了,如果有一位为0,则该查询一定是无效查询
- 提前查询数据库做一个布隆表

缓存雪崩
一大批被缓存的数据,同时失效,此时对于这一批的数据请求就全打到数据库上,导致数据库宕机
和缓存击穿类似
- 从MySQL角度出发

- Redis
- 设置热点数据永不过期
- 分析失效时间,尽量让失效时间点分散
- 缓存预热,即在上线前,根据当天的情况分析,将热数据直接加载到缓存系统