缓存一致性笔记

90 阅读4分钟

这是我参与青训营-后端场的第3篇笔记。

先更新数据库还是先更新缓存

\

由于引入了缓存,在数据库更新的时候,不仅要更新数据库的数据,还要更新缓存的数据,这两个更新操作存在前后顺序问题:

  • 先更新数据库,再更新缓存
  • 先更新缓存,再更新数据库

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

请求A先将数据库的数据更新为1,然后更新缓存,请求B将数据库的数据更新为2,接着也更新缓存,然后请求A也更新缓存,此时出现了缓存不一致现象

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

A请求先将缓存数据更新为1,然后在更新数据库前,B请求将缓存的数据更新为2,接着再将数据库更新为2,然后A请求再将数据库的数据更新为1,此时出现了缓存和数据库中数据不一致的现象

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

不更新缓存,而是直接删除缓存中的数据。然后当读取数据时,发现缓存中不存在数据,再从数据库中读取数据,更新到缓存中,这个策略就是Cache Aside策略

\

写策略的步骤:

  • 更新数据库中的数据
  • 删除缓存中的数据

读策略的步骤:

  • 如果读取的数据命中了缓存,直接返回数据
  • 如果读取的数据没有命中缓存,则从数据库中读取数据再写入缓存中,再返回给用户

但是写策略这里又可以分为两种情况:

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

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

假设某个用户的年龄是20,请求A要更新用户年龄是21,所以它将会删除缓存中的内容,这时候另一个请求B要读取这个用户的年龄,它查询缓存没有命中后,会从数据库中读取到年龄为20,并且写入到缓存中,然后请求A更新了数据库,现在数据库中的年龄更新为21,此时就出现数据不一致现象

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

假设某个用户的数据在缓存中不存在,请求A读取数据时从数据库中查询到年龄为29=0,在没有写入缓存时另一个请求B更新数据,它更新数据库中的年龄为21,并且清空缓存,这时候请求A将从数据库中读到的年龄为20的数据写入到缓存中。此时还是出现了数据不一致现象\

但是在实际上,这个问题出现的概率较低,因为缓存的写入通常都要远远快于数据库的写入,所以在实际中很难出现请求B已经更新数据库并且删除了缓存,请求A才更新完缓存的情况,而一旦请求A早于请求B删除缓存之前更新了缓存,接下来的请求就会因为不命中缓存从数据库中重新读取数据,所以不会出现这种不一致的情况

而且可以给缓存数据加上过期时间,就算出现了数据不一致的问题,也会有过期时间来兜底。但是这个方案还是存在一些问题,如何保证更新数据库和删除缓存两个操作都能执行成功

如何保证两个操作都能执行成功

重试机制

我们可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列中,由消费者来操作数据:

  • 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制,如果重试超过一定次数就应该向业务层发送错误消息了
  • 如果删除缓存成功,就应该将数据从mq中删除,防止重复删除

\

订阅Mysql binlog,再删除缓存

先更新数据库,再删除缓存的策略第一步是更新数据,那么更新数据库成功就会产生一条变更日志,记录在binlog中,于是我们可以订阅binlog日志,获取到具体的操作数据,然后再执行缓存删除,阿里的Canal中间件就是基于这个实现的

Canal模拟Mysql主从复制的交互协议,将自己伪装成一个Mysql从节点,向Mysql主节点发送dump请求,Mysql接收到请求后,就会开始推送binlog给Canal,Canal解析binlog字节流之后,就可以供服务程序使用