分布式缓存一致性

2023/2/17

# 1 更新策略

# 1.1 先更新缓存,再更新数据库

问题:

  • 更新缓存成功,更新数据库失败;

  • 并发问题

    时间 线程A 线程B 数据库 缓存
    0 v0 v0
    1 更新缓存为v1 v0 v1
    2 更新缓存为v2 v0 v2
    3 更新数据库为v2 v2 v2
    4 更新数据库为v1 v1 v2

# 1.2 先更新数据库,再更新缓存

问题:

  • 数据库更新成功,缓存更新失败,在缓存失效之前都有问题

  • 并发问题:

    时间 线程A 线程B 数据库 缓存
    0 v0 v0
    1 更新数据库为v1 v1 v0
    2 更新数据库为v2 v2 v0
    3 更新缓存为v2 v2 v2
    4 更新缓存为v1 v2 v1

# 2 删除策略

# 2.1 先删除缓存,再更新数据库

问题:

  • 删除缓存成功,更新数据库失败(这不是什么大问题)

  • 并发问题:

    时间 线程A 线程B 数据库 缓存
    0 删除缓存v0 v0
    1 读取数据库v0 v0
    2 更新数据库为v1 v1
    3 更新缓存v0 v1 v0
    4 删除缓存(延时双删策略) v1
public void write(String key,Object data){
    redis.delKey(key);
    db.updateData(data);
    // 这个时间不太好确定
    Thread.sleep(1000);
    redis.delKey(key);
}

要自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可;但是如果第二次删除缓存失败,还是会产生脏数据

# 2.2 先更新数据库,再删除缓存

问题:

  • 更新数据库成功,删除缓存失败,在缓存失效之前都有问题
  • 并发问题:
    时间 线程A 线程B 数据库 缓存
    0,缓存刚好失效 读取数据库v0 v0
    1 更新数据库为v1 v1
    2 删除缓存 v1
    3 更新缓存为v0 v1 v0
    4 删除缓存(延时双删策略) v1

# 3 删除缓存失败(采用删除策略)

  • 2.1 与 2.2 延迟双删可能删除缓存失败
  • 2.2 相对于 2.1 来说发生并发问题的可能性更低,因为刚好读缓存、写数据库、缓存失效三者并发;而且写数据库不太可能比读数据库慢

删除策略还有问题,就是缓存击穿问题;可以考虑使用两级缓存来解决不一致问题;一级和二级失效的时间不一样,让读请求永远打不到数据库

# 3.1 第一种删除重试机制

64.png

流程如下所示

  • 更新数据库数据
  • 缓存因为种种问题删除失败
  • 将需要删除的key发送至消息队列
  • 自己消费消息,获得需要删除的key
  • 继续重试删除操作,直到成功

# 3.2 第二种删除重试机制

上面方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作

65.png

流程如下图所示:

  • 更新数据库数据
  • 数据库会将操作信息写入binlog日志当中
  • 订阅程序提取出所需要的数据以及key(订阅binlog程序在mysql中有现成的中间件叫canal)
  • 另起一段非业务代码,获得该信息
  • 尝试删除缓存操作,发现删除失败
  • 将这些信息发送至消息队列
  • 重新从消息队列中获得该数据,重试操作

# 4 其他

# 4.1 Cache Aside Pattern

Cache Aside Pattern 是处理缓存一致性问题的一种模式,其实就是 2.2 的策略

# 4.2 Read/Write Through Pattern

将缓存服务作为主要的存储,应用的所有读写请求都是直接与缓存服务打交道,而不管最后端的数据库了,数据库的数据由缓存服务来维护和更新。不过缓存中数据变更的时候是同步去更新数据库的,在应用的眼中只有缓存服务,流程就相当简单了:(LevelDB、RocksDB、TiDB )

  • 读取:应用要读数据和更新数据都直接访问缓存服务
  • 更新:缓存服务同步的将数据更新到数据库

# 4.3 Write Behind Caching Pattern

是Read/Write Through模式的一个变种。区别就是Read/Write Through模式的缓存写数据库的时候是同步的,而Write Behind模式的缓存操作数据库是异步的,流程如下:

  • 应用要读数据和更新数据都直接访问缓存服务
  • 缓存服务异步的将数据更新到数据库(通过异步任务)