先介绍一下常规的几种做法
1、先删缓存,在改数据库
2、先改数据库,在删缓存
3、先改数据库,在改缓存
4、延迟双删(先删缓存,再改数据库,延迟几百毫秒,再删缓存),此方法可以解决在那先删缓存,再改数据库这个过程中,别的线程读到的数据库脏数据,又把脏数据回写的缓存里面了,此时数据库是新数据,缓存是脏数据。延迟几百毫秒,再删缓存的话,就解决了刚刚前面那步的缓存脏数据(有几百毫秒时间的脏数据存在)。
此方法一般是多个玩家线程在同一个方法或者不同的方法,操作同一个缓存结果数据或者数据库结果数据。(互联网行业比较多)。
一般游戏行业,各玩家数据都是有一个玩家标识隔离的,每一张表都是如此,几乎不会有多个玩家线程在同一个方法存在多线程问题,单个玩家,同一个方法,一般会加防重复请求处理。所以游戏也业务,一般都是同一个玩家不同的前端请求任务,在不同的方法操作了同一个缓存结果数据或者数据库结果数据。存在并发双写不一致问题。
解决双写不一致最靠谱的两个方案:(以游戏行业为例)
这两个接口几乎是一个玩家在同一秒请求,可能是因为checkOrder太耗时了(写了一个大事务),然后前端是一个异步进行levelHomePage接口。所以导致checkOrder接口还没跑完,就调用了levelHomepage接口。导致checkOrder接口里面删除缓存,和修改数据库之后,事务还没提交。levelhomePage接口就请求数据,拿到了数据库的脏数据(因为事务还没提交),又把脏数据回写到缓存里面了。(此时数据库和缓存就出现了不一致)。
这种问题,如果是小事务的话,或者资源不是很重要的,并且没有事务的话,直接可以使用延迟双删。
最后那一次删缓存,可以使用一个异步线程来删除,因为这前面的删缓存和删数据库还是同步操作,这一步需要延迟几百毫秒左右,所以需要一个异步线程池,并且他就算删除失败,也影响不大。
当然这种延迟双删也还是可以解决问题的,只是如果这里的事务卡这这个方法太长时间了,可能在最后那一次删除的时候,事务还没有提交,所以另外的levelhomePage玩家接口又把老的数据库数据回写到缓存里面了,当然这种做法能避免绝大多数问题了。不能说百分比保证。(所以对于数据要求不是非常高的可以采用这种做法)。
如果我们这种订单资源非常重要的话,可以采用另一种做法,就是事务提交之后,再删除缓存,至少可以保证只是在事务还没提交的时候,可能把脏数据库数据写到缓存,但是只要事务一提交,再删缓存的话,之后,就一定没有脏数据了。
这里的方法是如果当前线程最外层或者当前线程存在事务,救在事务提交之后执行,如果不存在事务的话,就立马执行。(其实可以理解为延迟双删的加强版)。