redis使用redis作为缓存时所注意事项
【redis】使用redis作为缓存时所注意事项
缓存更新策略
在 Redis 缓存中,缓存的更新策略主要有**定期生成(定时更新) 和 实时生成(即时更新)**两种方式。不同的策略适用于不同的业务场景,涉及性能、数据一致性和系统负载等方面的权衡。
1. 定期生成(定时更新)
是什么?
定期生成指的是按照 固定的时间间隔 ,主动更新缓存,而不是在数据发生变化时立即更新。这种方式适用于数据变化不频繁、对实时性要求不高的场景。
优点:
✅ 降低数据库压力 :缓存可以批量更新,避免频繁查询数据库。
✅ 提高查询性能 :查询时直接读取缓存,响应速度快。
✅ 数据一致性较好 (相对于长期不更新的缓存):定期更新可以保证数据不会长期过时。
缺点:
❌ 数据可能不够实时 :在缓存下一次更新前,数据可能已经变化,但缓存仍然返回旧数据。
❌ 不适合高实时性业务 :如果业务需要频繁变更数据,定期更新可能导致缓存数据滞后。
❌ 可能会引起短时流量冲击 :如果所有缓存数据同时更新,可能会对数据库造成瞬间压力。
常见实现方式:
定时任务更新缓存 ( Time-based Refresh )
使用 Spring Task、Quartz、Crontab 等定时任务,每隔一段时间刷新缓存。
例如,每 10 分钟更新一次缓存:
@Scheduled(fixedRate = 600000) // 每 10 分钟执行一次 public void updateCache() { // 查询数据库并更新缓存 List<Data> dataList = databaseService.getData(); redisTemplate.opsForValue().set("cache:data", dataList); }
数据库变更时触发缓存更新 ( Database-triggered Refresh )
- 监听 数据库变更事件(MySQL Binlog、PostgreSQL 触发器) ,检测到数据变化后批量刷新缓存。
异步任务更新
- 使用消息队列(Kafka、RabbitMQ)通知服务更新缓存,避免定时任务导致的瞬时数据库压力过大。
适用场景:
📌 统计数据、排行榜、热门商品列表等(更新频率较低,数据稍有延迟也无大问题)。
📌 日志分析、报表数据等(数据量大,但对实时性要求不高)。
2. 实时生成(即时更新)
是什么?
实时生成指的是 数据发生变更时立即更新缓存 ,确保缓存数据始终是最新的。这种方式适用于 对数据一致性要求高、变更较频繁 的场景。
优点:
✅ 数据实时性高 :缓存的数据始终与数据库保持一致,适用于高实时性需求的应用。
✅ 避免缓存不一致问题 :数据库变更后立即同步缓存,减少数据不匹配的情况。
缺点:
❌ 更新成本高 :每次数据变更都需要更新缓存,可能会导致数据库压力增大。
❌ 可能导致缓存频繁更新 :对于高频变更的数据,频繁更新可能会导致 Redis 负载过重,甚至影响整体性能。
❌ 并发问题 :多个并发请求可能会导致 缓存不一致 或 缓存击穿 ,需要加锁或使用双写策略。
常见实现方式:
数据库更新时主动更新缓存 ( Write-through Strategy )
在 **数据更新(新增、修改、删除)**时, 同时更新数据库和缓存 :
public void updateData(Data data) { databaseService.updateData(data); // 更新数据库 redisTemplate.opsForValue().set("cache:data:" + data.getId(), data); // 同步更新缓存 }
适用于数据变更不频繁,且一致性要求较高的场景。
缓存淘汰(Cache Eviction)
在数据库更新后, 删除缓存 ,让下一次查询时重新加载数据:
public void updateData(Data data) { databaseService.updateData(data); // 更新数据库 redisTemplate.delete("cache:data:" + data.getId()); // 删除缓存 }
适用于 缓存数据不是热点 ,数据变更后不需要立即被查询的情况。
订阅数据库变更 ( Event-based Strategy )
- 使用 消息队列(Kafka、RabbitMQ) 或 Redis 订阅/发布机制 ,监听数据库变更事件,变更后更新缓存。
分布式锁(避免缓存并发写入问题)
解决多个请求同时更新缓存导致数据不一致的问题:
RLock lock = redissonClient.getLock("cache:lock:data:" + data.getId()); try { if (lock.tryLock(5, TimeUnit.SECONDS)) { databaseService.updateData(data); redisTemplate.opsForValue().set("cache:data:" + data.getId(), data); } } finally { lock.unlock(); }
适用于高并发写入场景,防止缓存同时被多个请求覆盖。
适用场景:
📌 订单系统、支付系统、库存管理等(数据必须实时更新,不能有延迟)。
📌 直播、弹幕系统(数据实时变化,需要确保一致性)。
总结:定期生成 vs. 实时生成
策略 | 定期生成(定时更新) | 实时生成(即时更新) |
---|---|---|
数据实时性 | 低(有一定延迟) | 高(数据库更新即缓存更新) |
数据库压力 | 低(定期批量更新) | 高(频繁更新缓存) |
缓存命中率 | 高(查询时直接命中缓存) | 可能较低(某些情况需删除缓存) |
适用场景 | 排行榜、统计数据、报表等 | 订单、库存、支付等高一致性业务 |
总结
- 定期生成(定时更新) 适用于 数据变化不频繁 、 对实时性要求不高 的场景,如排行榜、日志分析等。
- 实时生成(即时更新) 适用于 数据变化频繁 、 对一致性要求高 的场景,如支付、库存、订单管理等。
- 在实际应用中,可以
结合两种策略
,例如:
- 定期更新 + 变更触发更新 :大部分数据定期刷新,关键数据实时更新。
- 读时更新 + 写时淘汰 :查询时自动更新缓存,写入时删除缓存,防止数据不一致。
合理选择缓存更新策略,可以有效提升系统性能,降低数据库压力,并保证数据的一致性。
Redis 作为缓存,存储空间有限,因此需要淘汰数据来保证新数据的存入。Redis 提供了多种 缓存淘汰策略(Eviction Policy) ,用于决定哪些数据需要被删除。下面介绍几种常见的淘汰策略,包括它们的适用场景和优缺点。
缓存淘汰策略
1. 不淘汰策略
1.1 noeviction(拒绝写入)
概念:
当 Redis 内存占满时,不会删除任何已有数据,而是直接返回错误,拒绝新的写入请求。
适用场景:
- 适用于 严格不能丢数据 的场景,如任务队列(消息队列)、金融交易等。
- 适用于 Redis 作为 纯数据存储 而非缓存时。
优缺点:
✅ 数据不会被误删除 ,保证数据完整性。
❌ 可能导致写入失败 ,影响系统稳定性。
2. 基于 TTL(过期时间)的淘汰策略
2.1 volatile-lru(最近最少使用,TTL 限定)
概念:
- 只淘汰**设置了过期时间(TTL)**的键。
- 在这些键中,优先删除最近最少使用(LRU, Least Recently Used)的数据。
适用场景:
- 适用于 部分数据可丢弃 的场景,比如 session、短期缓存数据。
- 适用于需要 自动过期控制 ,但仍希望尽可能保留热点数据的情况。
优缺点:
✅ 优先保留常用数据,减少缓存击穿的概率。
❌ 如果大部分 key 没有 TTL,可能导致 Redis 直接拒绝写入(相当于 noeviction)。
2.2 volatile-ttl(优先淘汰即将过期的键)
概念:
- 只淘汰**设置了过期时间(TTL)**的键。
- 其中 剩余寿命最短 的键优先被删除。
适用场景:
- 适用于对数据有明确的生命周期需求的业务,如订单缓存、验证码缓存等。
优缺点:
✅ 优先删除即将过期的数据,保证短期缓存的更新。
❌ 可能误删仍然有价值的热点数据。
3. 基于数据访问频率的淘汰策略
3.1 allkeys-lru(全局最近最少使用)
概念:
- 无视 TTL ,从所有键中(包括没有设置 TTL 的键),优先淘汰 最近最少使用 的键。
适用场景:
- 适用于 热点数据更新频繁 的场景,如推荐系统、排行榜、搜索结果缓存等。
优缺点:
✅ 可以确保常用数据长期保留,提高缓存命中率。
❌ 如果热点数据突然减少访问,可能会被错误淘汰。
3.2 allkeys-random(全局随机淘汰)
概念:
- 无视 TTL ,在所有 key 中 随机删除 某些数据。
适用场景:
- 适用于 缓存数据均匀访问 ,不需要特定优先级的场景。
优缺点:
✅ 简单高效,减少淘汰策略的计算开销。
❌ 不够智能,可能淘汰热点数据,降低缓存命中率。
4. 基于数据访问频次的淘汰策略
4.1 volatile-lfu(基于访问频率,TTL 限定)
概念:
- 只淘汰设置了 TTL 的 key 。
- 访问次数最少的键 优先被删除(LFU, Least Frequently Used)。
适用场景:
- 适用于 需要根据访问次数保留数据 的业务,如热点文章缓存、用户历史记录等。
优缺点:
✅ 能够长期保留高频访问数据,淘汰低频数据。
❌ 如果大部分数据没有 TTL,可能导致 Redis 拒绝写入(类似 noeviction)。
4.2 allkeys-lfu(全局最不常使用淘汰)
概念:
- 无视 TTL ,从所有键中 优先淘汰访问次数最少的键 。
适用场景:
- 适用于 热点数据访问有明显差异 的情况,如新闻热点推荐、热门产品缓存等。
优缺点:
✅ 能保留长期热点数据,提高缓存命中率。
❌ 短期热点数据可能无法及时替换,导致数据更新滞后。
总结
策略 | 机制 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|
noeviction | 拒绝写入 | 不能丢数据(消息队列、金融) | 数据安全 | 容易写满导致错误 |
volatile-lru | 仅淘汰 TTL 数据,LRU | 需自动过期,保留热点数据 | 减少缓存击穿 | 仅适用于部分数据有 TTL |
volatile-ttl | 仅淘汰 TTL 数据,剩余寿命短的优先 | 订单缓存、验证码 | 优先清理即将失效的缓存 | 可能误删热点数据 |
allkeys-lru | 全局 LRU 淘汰 | 访问频率高的缓存(推荐系统) | 提高缓存命中率 | 可能误删突然冷却的热点数据 |
allkeys-random | 随机淘汰 | 数据访问均匀的缓存 | 计算开销小 | 可能淘汰重要数据 |
volatile-lfu | 仅淘汰 TTL 数据,访问最少的优先 | 需要根据访问频率保留数据 | 长期热点数据保留 | 仅适用于有 TTL 的 key |
allkeys-lfu | 全局 LFU 淘汰 | 热点明显的数据(新闻、直播) | 缓存命中率高 | 短期热点更新慢 |
如何选择淘汰策略?
1. 数据不能丢失(消息队列、金融)
✅ noeviction (拒绝写入)
2. 仅淘汰过期数据(业务数据自动失效)
✅ volatile-lru (保留热点)
✅ volatile-ttl (优先清理快过期数据)
3. 需要智能保留高频访问数据
✅ allkeys-lru (最近最少使用淘汰)
✅ allkeys-lfu (最少使用淘汰)
4. 访问数据均匀,不关心淘汰顺序
✅ allkeys-random (随机删除)
5. 业务需要权衡 LRU 和 LFU
- 短期热点多 ,选 LRU
- 长期热点多 ,选 LFU
结论
- 如果数据有 TTL,且希望优先淘汰冷数据 ,选 volatile-lru / volatile-lfu 。
- 如果所有数据都可以被淘汰 ,选 allkeys-lru / allkeys-lfu 。
- 如果只允许写满后拒绝写入 ,选 noeviction 。
- 如果对淘汰规则无特别要求 ,选 allkeys-random 。
正确选择淘汰策略,可以有效提高缓存命中率,降低数据库压力,保障系统稳定性。
常见缓存问题
在 Redis 中,缓存预热、缓存穿透、缓存雪崩和缓存击穿是常见的缓存问题。下面分别描述它们的概念及解决方案:
1. 缓存预热(Cache Warming)
是什么?
缓存预热是指在系统启动或运行之前,提前将热点数据加载到缓存中,以减少数据库的查询压力,提高系统访问速度。
如何解决?
- 手动加载 :在服务启动时,手动将热点数据写入缓存。
- 定时刷新 :通过定时任务(如 Spring Task、Quartz 等)定期加载热点数据到缓存。
- 数据变更同步 :监听数据库更新(如 MySQL binlog、Redis 订阅发布机制),在数据变化时同步更新缓存。
- 批量加载
:使用 Redis 的
pipeline
或mset
命令批量写入缓存,提高加载效率。
2. 缓存穿透(Cache Penetration)
是什么?
缓存穿透指的是大量请求查询 不存在的数据 ,导致每次请求都要查询数据库,缓存完全失效,给数据库带来巨大压力。
如何解决?
- 缓存空值
:如果查询的数据不存在,可以将空值(如
null
或{}
)存入缓存,并设置较短的过期时间,避免重复查询数据库。 - 布隆过滤器(Bloom Filter) :使用布隆过滤器提前判断某个 key 是否可能存在,如果一定不存在,则直接返回,不查询数据库。
- 参数校验 :在请求层对参数进行校验,避免无效请求进入系统。
- 限流与黑名单 :对异常请求 IP 进行封禁或限流,避免恶意攻击。
3. 缓存雪崩(Cache Avalanche)
是什么?
缓存雪崩指的是 大量缓存同时失效 ,导致短时间内大量请求直接打到数据库,造成数据库压力激增,甚至宕机。
如何解决?
- 缓存过期时间随机化
:为缓存设置
不同的过期时间
,避免大量缓存同时失效,例如使用
TTL = 基础时间 ± 随机时间
。 - 热点数据提前预加载 :在缓存即将过期前,主动刷新缓存,保证热点数据始终可用。
- 双层缓存 :使用 Redis + 本地缓存(如 Caffeine、Guava Cache) ,降低对 Redis 的依赖。
- 流量削峰
:
- 限流 :使用限流算法(如令牌桶、漏桶)限制访问速率。
- 降级 :当数据库压力过大时,返回默认值或降级处理。
4. 缓存击穿(Cache Breakdown)
是什么?
缓存击穿指的是 某个热点 key 突然失效 ,导致大量并发请求直接打到数据库,造成数据库短时间内压力剧增。
如何解决?
- 设置热点数据永不过期 :对于热点数据,直接不设置过期时间,而是由业务逻辑主动更新缓存。
- 互斥锁(分布式锁)
:
- 当缓存失效时,多个请求只允许 一个线程查询数据库并更新缓存 ,其他线程等待缓存更新完成后再读取。
- 具体实现:使用
SETNX
(Redis 分布式锁) 或 Redisson。
- 提前刷新缓存
:
- 通过异步线程提前更新即将过期的热点缓存,防止突然失效。
- 例如:使用
Redis + 过期监听
,在 key 即将过期前主动更新缓存。
总结
问题 | 现象 | 解决方案 |
---|---|---|
缓存预热 | 缓存刚启动时,没有数据 | 手动加载、定时刷新、监听数据变更 |
缓存穿透 | 查询的 key 在数据库中不存在,每次都查数据库 | 缓存空值、布隆过滤器、参数校验、黑名单 |
缓存雪崩 | 大量 key 同时失效,数据库压力激增 | 过期时间随机化、双层缓存、限流、降级 |
缓存击穿 | 某个热点 key 失效,大量请求打到数据库 | 热点数据永不过期、分布式锁、提前刷新 |
这四个缓存问题都是分布式系统中必须重点关注的,合理的缓存策略可以有效提升系统的性能和稳定性。