黑马点评商户缓存

一致性

缓存更新策略

  1. 内存淘汰:Redis 内存不足时自动淘汰部分数据,下次请求时更新缓存
  2. 超时剔除:给缓存数据添加 TTL 时间,到期后自动删除缓存。下次请求时更新缓存。
  3. 主动更新:在修改数据库的同时更新缓存。

缓存数据库一致性

有请求并发到来时,不管是先更新缓存后更新数据库,还是先更新数据库后更新缓存,都可能出现缓存不一致问题。

那么,使用 Cache Aside 策略,也就是只有在读数据的时候,cache 才有可能被更新。
写策略:当我们修改数据时,更新数据库里的数据,直接删除缓存中的数据。
读策略:当读数据时,如果发现缓存里没有该数据,则从数据库里面读数据更新到缓存中。

先更新后删除(延迟双删)

线程A遵循读策略,线程B同时更新数据

  1. 线程A查缓存未命中,查数据库x=20
  2. 线程B更新数据库,x=21
  3. 线程B删除缓存(第一次删除)
  4. 线程A把旧数据写入缓存,x=20
  5. 线程B延迟一段时间后主动执行第二次删除(非被动检测)
  • 很小概率会出现这种情况。因为步骤一和步骤四之间,线程 B 查了数据库,但是实际上线程 A 查缓存写缓存都很快。

  • 延迟时间(如500ms)需大于"查询DB+写入缓存"的耗时(通常 < 100ms)

  • 只要延迟时间设置合理,可以覆盖线程A的旧数据写入操作(步骤4)

  • 即使没有发生并发冲突,重复删除也是安全的幂等操作

如果删除缓存操作失败了怎么办?

@Transactional 注解修饰方法。如果删除缓存失败,那么数据库更新的操作也需要回滚。
其它方法

商铺缓存

采用主动更新+超时剔除的策略。

  1. 根据 ID 查商铺,如果缓存没有命中,则查数据库,将结果写入缓存并设置超时时间
  2. 修改数据时,先把新的数据存入数据库,再删除缓存。

把 Shop 对象转换成 JSON 字符串,作为 value 存储到 Redis 中。这适用于对象的结构复杂,且更新操作较少的情况。

如果更新操作较多,可以在redis里面存hash

缓存工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Slf4j
@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }
}

StringRedisTemplate 是一个由 Spring Boot 自动配置的 Redis 访问工具类,它会在 Spring 启动时根据 application.yml 中的配置自动实例化。
Spring 会为每个 Redis 连接池和相关配置创建一个 StringRedisTemplate 的单例实例,并将它注入到需要的地方。

CacheClient:
CacheClient 是一个标注为 @Component 的类,这意味着 Spring 会自动将它注册为一个 bean 并将其注入到其他类中。 由于 CacheClient 使用构造器注入 StringRedisTemplate,因此 Spring 会先创建 StringRedisTemplate 的单例实例,然后将其传入 CacheClient 的构造函数,从而完成注入。

缓存穿透

用户请求的数据既不在缓存内,也不在数据库内。大量此类请求会给数据库造成巨大压力。

  1. 缓存空值。如果根据商铺 ID 去数据库里查这个商铺,但是返回为空时,则把null值作为 value存入Redis.
  2. 布隆过滤器: 将所有存在的商品ID预先加载到布隆过滤器中。
  • 请求到达时,先检查布隆过滤器。如果不存在,直接返回“商品不存在”。如果存在,继续查询缓存或数据库。

缓存雪崩

同一时间大量缓存 key 失效/Redis 宕机,造成大量请求到达数据库。
解决方案:给 key 设置随机过期时间;采用 Redis 集群。

缓存击穿

预热

被高并发访问、且缓存重建复杂的key失效。同一时间会有无数请求来访问数据库请求这个key。
解决方案:逻辑过期+互斥锁。

从Redis查商户缓存。如果发现缓存过期,则试图抢占锁,抢占锁后开启新线程去重建缓存,当前线程立即返回旧的商户信息。