缓存与数据库一致性问题

47 篇文章 0 订阅

业务场景

       抓拍到的人脸图片需要推送到第三方系统,但不是所有的网点都需要推送图片信息。也就是要做到不同的网点可以根据配置来决定是否推送,前端页面需要有推送配置功能,手动配置后,把配置的推送信息保存到数据库。抓拍到人脸照片后,读取配置的推送信息,再判断是否需要推送图片。由于网点多抓拍的人脸数据量较大,推送信息配置后不常修改,也就是读多写少,所以考虑把配置的推送信息保存数据库后再保存一份至redis中。

实现方案

       缓存和数据库的数据一致性有多种实现方式,各实现方式可以自行了解,以及他们各自的优缺点。在此使用的是先更新数据库,再删除缓存的方式,为了防止删除缓存失败,增加了重试机制,同时为了保证数据的最终一致性,给缓存增加了过期时间。

代码如下: 保存推送配置信息

可以看到代码中是先删除已有配置,接着批量添加新的配置,最后删除缓存。

 

删除缓存

删除缓存增加了重试机制,防止删除失败,出现数据不一致问题。

 

接着,读取推送配置信息:

可以看到,当第一次读取数据的时候先从缓存中查询,当不存在时再查询数据库,数据库也不存在的时候,生成默认的推送配置信息,更新到redis中,然后返回。

 

更新缓存操作

给缓存增加了过期时间

 

问题现象

在一次自测时发现,有一个网点配置的不推送,竟然接收到图片推送。

于是查询redis中的数据,确实是推送配置

Redis:

再次查看数据库

发现数据库中的结果是正确的,不推送。

因此出现了数据库与缓存中的数据不一致现象

 

问题排查

  1. 首先查看运行日志,看是不是缓存删除失败导致的。结果没有发现删除失败以及重试删除的日志,也没有redis异常连接的任何信息。
  2. 然后测试其他网点配置,多次测试验证也没有出现缓存与数据库不一致的现象。因此判定是一个偶现的情况,根据以往经验,偶现的问题或有可能出现问题的代码,在现场一定会再次出现。继续排查
  3. 最后只能查看代码,是哪个环节有问题。

 

最后发现可能跟数据库的事务有关,在保存推送配置的方法上有@Transactional注解,有该注解的方法在执行时会被事务拦截器拦截,在方法前设置是否自动提交为false,方法执行后提交事务。也就是推送配置在方法执行完成后数据才会被保存到数据库中。仔细看方法内的实现,删除redis缓存是在方法内执行的。

本来的实现方式是:先更新数据库,再删除缓存,现在却是先删除缓存,再更新数据库。因此在删除缓存后,提交事务之前这短暂的时间如果有查询推送信息的操作,则会查询到旧数据,并更新到缓存,当提交事务后,就会出现缓存与数据库不一致的现象

 

debug验证

在删除缓存之后的代码打上断点,此时事务还未提交,查看redis,发现redis中的缓存已被删除,此时查询推送配置信息,会把旧数据重新更新的redis中,然后放开断点继续执行代码,就出现了缓存与数据库不一致的现象。

 

解决方案

找到问题的原因后,解决方式就很简单了。就是在数据库的事务提交之后,再执行删除缓存的操作就可以。有多种实现方式,下面介绍三种:

  1. 把操作数据库的操作单独放到一个事务方法中

 

  1. 使用编程式事务的方式,在同一个方法中就可以完成

 

  1. 使用TransactionSynchronizationManager在事务提交之后操作

 

总结

        在数据库的事务方法内部最好不要执行远程调用,因为远程调用成功后事务提交失败,则远程调用不能回滚,即使都执行成功也要考虑远程调用的结果与事务代码中的结果是否有业务关联,否则可能会出现数据不一致的问题,并且排查非常困难。

尽可能的让远程调用在事务开始之前或结束之后执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值