【redis】使用redis作为缓存时所注意事项

发布于:2025-03-10 ⋅ 阅读:(15) ⋅ 点赞:(0)

缓存更新策略

在 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 负载过重,甚至影响整体性能。
并发问题:多个并发请求可能会导致缓存不一致缓存击穿,需要加锁或使用双写策略。

常见实现方式:

  1. 数据库更新时主动更新缓存Write-through Strategy

    • 在 **数据更新(新增、修改、删除)**时,同时更新数据库和缓存
      public void updateData(Data data) {
          databaseService.updateData(data); // 更新数据库
          redisTemplate.opsForValue().set("cache:data:" + data.getId(), data); // 同步更新缓存
      }
      

    • 适用于数据变更不频繁,且一致性要求较高的场景。
  2. 缓存淘汰(Cache Eviction)

    • 在数据库更新后,删除缓存,让下一次查询时重新加载数据:
      public void updateData(Data data) {
          databaseService.updateData(data); // 更新数据库
          redisTemplate.delete("cache:data:" + data.getId()); // 删除缓存
      }
      

    • 适用于缓存数据不是热点,数据变更后不需要立即被查询的情况。
  3. 订阅数据库变更Event-based Strategy

    • 使用 消息队列(Kafka、RabbitMQ)Redis 订阅/发布机制,监听数据库变更事件,变更后更新缓存。
  4. 分布式锁(避免缓存并发写入问题)

    • 解决多个请求同时更新缓存导致数据不一致的问题:
      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 的 pipelinemset 命令批量写入缓存,提高加载效率。

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 失效,大量请求打到数据库 热点数据永不过期、分布式锁、提前刷新

这四个缓存问题都是分布式系统中必须重点关注的,合理的缓存策略可以有效提升系统的性能和稳定性。