Redis的缓存更新策略

一. 概念

1. Redis缓存策略

业务场景需求:

1. 在基本不会更新数据的情况下可以使用内存淘汰机制
2. 在频繁更新数据的情况下可以使用主动更新,并以超时剔除作为兜底方案。

2. 主动更新的3种方法

1. Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存

2. Read/Write Through Pattern:缓存和数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。
-优点:整合的服务保证了数据的一致性
-缺点:维护和开放成本高

3. Write Behind Caching Pattern:调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,最终保持一致。
-优点:异步更新缓存数据,效率高。例如缓存多次更新,但是更新到的缓存并没有被使用,多次将数据持久化到数据库就相当于进行了无用的操作,异步更新相当于将前几次的更新合并为一次更新,因而提高了效率。
-缺点:无法保证一致性,维护成本高

目前主流使用的Redis缓存主动更新的方法是Cache Aside Pattern

二. Redis的读写实践

1. Redis的读

如果缓存在Redis中存在,即缓存命中,则直接返回数据

 

 

如果Redis中没有对应缓存,则需要直接查询数据库,然后存入Redis,最后把数据返回

 通常情况下,我们会为某个缓存设置一个key值,并针对key值设置一个过期时间,如果被查询的数据对应的key过期了,则直接查询数据库,并将查询得到的数据存入Redis,然后重置过期时间,最后将数据返回,伪代码如下:

/**
 * 根据用户名获取用户详细信息
 * @author*/
public User getUserInfo(String userName) {
      User user = redisCache.getName("user:" + userName);
      if (user != null) {
          return user;
      }

      // 从数据库中直接搜索
      user = selectUserByUserName(userName);
      // 将数据写入Redis,并设置过期时间
      redisCache.set("user:" + userName, user, 30000);
      // 返回数据
      return user;
}

2. Redis的写

一致性问题: 

在Redis的key值未过期的情况下,用户修改了个人信息,我们此时既要操作数据库数据,也要操作Redis数据

到底是先更新数据库还是先更新缓存?

从本质上讲,无论是先写数据库还是先写缓存,都是为了保证数据库和缓存的数据一致,也就是我们常说的数据一致性。

操作缓存和数据库时需要考虑的三个问题:

1. 删除缓存还是更新缓存?

更新缓存:每次更新数据库都更新缓存,无效写操作较多
删除缓存:更新数据库时让缓存失效,查询时再更新缓存

结论:推荐直接使用「删除」操作

2. 如何保证缓存与数据库的操作的同时成功或者失败

对于单体系统:将缓存与数据库操作放在一个事务中
对于分布式系统:利用TCC等分布式事务方案

3. 先操作缓存还是先操作数据库?

3-1. 先删除缓存,再操作数据库

这种方式可能存在以下两种异常情况:

1. 删除缓存失败,这时可以通过程序捕获异常,直接返回结果,不再继续更新数据库,所以不会出现数据不一致的问题
2. 删除缓存成功,更新数据库失败。在多线程下可能会出现数据不一致的问题

 这时,Redis中存储的旧数据,数据库的值是新数据,导致数据不一致。这时我们可以采用 延时双删 的策略,即更新数据库数据之后,再删除一次缓存。

 用伪代码表示就是: 

/**
 * 延时双删
 * @author*/
public void update(String key, Object data) {
    // 首先删除缓存
    redisCache.delKey(key);
    // 更新数据库
    db.updateData(data);
    // 休眠一段时间,时间依据数据的读取耗费的时间而定
    Thread.sleep(500);
    // 再次删除缓存
    redisCache.delKey(key);
}

3-2. 先操作数据库,再删除缓存

这种方式可能存在以下两种异常情况:

1. 更新数据库失败,这时可以通过程序捕获异常,直接返回结果,不再继续删除缓存,所以不会出现数据不一致的问题
2. 更新数据库成功,删除缓存失败。导致数据库是最新数据,缓存中的是旧数据,数据不一致

这里, 我们有两种方式来解决数据不一致问题:失败重试 和 异步更新

方式1. 失败重试

如果删除缓存失败,我们可以捕获这个异常,把需要删除的 key 发送到消息队列。自己创建一个消费者消费,尝试再次删除这个 key,直到删除成功为止。

 这种方式有个缺点,首先会对业务代码造成入侵,其次引入了消息队列,增加了系统的不确定性。

方式2. 异步更新

因为更新数据库时会往 binlog 中写入日志,所以我们可以启动一个监听 binlog变化的服务(比如使用阿里的 canal开源组件),然后在客户端完成删除 key 的操作。如果删除失败的话,再发送到消息队列。

总之,对于删除缓存失败的情况,我们的做法是不断地重试删除操作,直到成功。无论是重试还是异步删除,都是最终一致性的思想。

如上图所示,两种方案在多线程的情况下都会产生数据不一致的问题

先操作数据库再删除缓存的情况下,要发生数据不一致的问题,需要在缓存写入之前完成更新数据库和删除缓存的操作,而写入缓存的耗时非常短。因而发生的概率相对于另一种方案更低。所以优先选择先操作数据库,再删除缓存。

结论:推荐直接使用「先更新数据库再删除缓存」操作

三. 总结

缓存策略的最佳实践是 Cache Aside 模式。分别分为读缓存最佳实践和写缓存最佳实践。

读缓存最佳实践

先读缓存,命中则返回;
未命中则查询数据库,再写到缓存中。

写缓存最佳实践

先更新数据库,再操作缓存;
操作缓存采用直接删除缓存,而不是修改。

 

 

----------------------------------------------------------------------------------------------------------

参考博文:

1. 到底是先更新数据库还是先更新缓存?

2. 到底先修改MySQL还是先修改Redis?

3. Redis缓存更新策略

posted @ 2022-08-21 20:05  Sherlock先生  阅读(2476)  评论(1编辑  收藏  举报