缓存一致性:数据库操作与Redis事务回滚

今日开发需求是“保证数据库操作与Redis缓存操作的一致,在CRUD出错时,保证回滚”。

问题:
1、数据库开启事务,即可在操作失误时,回滚之前的已经做过的操作,保证操作原子性。
2、Redis的事务并不支持回滚功能,Redis命令在事务处理期间发生错误,原先的操作并不会回滚
3、我们都知道,需要先操作数据库再操作缓存,这样大概率会避免并发和很多错误问题。但是MySQL操作成功,而Redis异常,此时缓存数据不一致,如何回滚Mysql操作又是一个问题。

那么问题来了,针对2,3出现的问题,我们如何保证两个操作的一致性?

解决方案:

1、在Service方法上使用spring的@Transactional注解回滚MySQL异常

注:@Transactional只会回滚MySQL的异常,其后发生的Redis异常并不会让其回滚数据库。Redis也不会回滚(这个和Redis采用的设计策略有关:不对回滚支持,保证操作的简单快速)
在这里插入图片描述

2、对Redis操作进行解耦,并且进行判断操作有无成功,如果出错,抛出unchecked异常(比如runtimeException)

这样:
问题3:Redis出错,MySQL回滚的问题。配合@Transactional注解就解决了
而问题2 就需要根据自身的情况,对Redis中已经产生的数据进行删除即可。

注意事项:(如果你的上述操作都没有问题,那么不必看。如果出现了问题,可以看看)

1、@Transactional 注解只能应用到 public 修饰的方法上。如果你在 protected、private 或者package-visible 的方法上使用@Transactional 注解,它也不会报错,但是这个被注解的方法将不会展示已配置的事务设置。

2、默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。

3、千万,千万,千万不要捕获异常,如果将异常捕获了,那么会被spring认为程序执行完好,根部会触发@Transactional 事务的回滚!
如果你一定要捕获异常,我在网上看到可以采用setRollbackOnly()进行手动回滚,但是本人测试中发现存在一些问题,读者可以自行实验效果

   TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();					

4、在同一个类中一个方法调用另一个有事务的方法,事务是不会起作用的。因为@Transactional 的事务开启 ,是基于接口的或者是基于类的代理被创建。但是你可以反过来,
开启事务的方法A 调用 未开启的方法a, a中报错setRollbackOnly(),此时方法A事务会生效,进行rollback。
5、Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用@Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

更多推荐