服务热线
135-6963-3175
背景
先操作缓存,在写数据库成功之前,如果有读请求发生,可能导致旧数据入缓存,引发数据不一致。
先说方案:更新的时候,先删除缓存,再更新数据库,然后再删除缓存。
为何先删除缓存而不是更新呢?比如:
1、对于比较复杂的缓存数据计算的场景,如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算。
2、A把缓存更新成功了,B操作需要读取缓存并读取到了A更新后的缓存,但是A更新数据库失败了,这时候B读取的缓存可能就是错误的(因为缓存和数据库操作不是一个原子操作)。
为何最后还要删除缓存呢?
因为可能在更新之前另一个请求线程又把旧的数据写入缓存了。
那么我们可以采取延迟双删方式。
redis.delKey(key); db.updateData(data); Thread.sleep(500);//因为可能别的请求线程刚读的旧数据还没写入成功或者 //更新数据库还没真正成功所以可延时等待 redis.delKey(key);
如果你用了mysql的读写分离架构怎么办?可以增加延时时间
降低吞吐量怎么?第二次删除可作为异步的。自己起一个线程,异步删除。
第二次删除失败怎么办?
方案:
老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出:
失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从cache中取数据,取到后返回。
更新:先把数据存到数据库中,成功后,再让缓存失效。
步骤:
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功
如果对一致性要求不是很高,直接在程序中另起一个线程通过消息队列实现,每隔一段时间去重试即可。但是会造成代码入侵,也可通过订阅数据库binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
步骤:
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。
订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。
待补充...