Cache Aside Pattern
1、读的时候,先读缓存,缓存没有的话,就读数据库,取出数据后放入缓存,同时返回响应。
2、更新的时候,先删除缓存,然后更新数据库
3、缓存设置ttl
问题一:为什么第二步不是更新缓存,而是删除缓存
数据库更新后,缓存数据的更新操作不是必须的。有可能频繁的更新了缓存,但在此期间并没有读操作,所以使用lazy的思想,可以把缓存更新放在读操作流程里。
问题二:第二步为什么先删除缓存,见下图

主要是考虑操作失败的情况:
1、如果采用先更新库再删除缓存的方式,如果删除缓存失败,则缓存里是脏数据
2、如果采用先删除缓存再更新库的方式,无论是哪种操作失败,都不会有脏数据
选择先删除缓存,然后更新数据库的方式,读取和更新并发执行仍有可能不一致 见下图

上图的步骤执行完,缓存里是旧数据。虽然几率很小,不过仍然有可能发生。
简单版解决方法:
更新操作优化为:先删除缓存,再更新数据库,1秒后,再删除缓存。这样就把有可能造成的脏缓存删除掉
优点:实现简单
缺点:1秒内有可能有脏缓存,虽然几率很小
问题:延时删除操作失败,仍然是脏缓存数据。
解决方案?
将删除失败的key放入消息队列,业务从消息队列消费,重试删除key的操作,直到删除成功。
复杂版解决方案:
把读操作和更新操作串行化操作。
一、程序实现
根据操作数据的唯一表示,把对数据的操作路由到某一个程序的内部队列里。
1、读操作时,如果缓存没有读到数据,则将“读取数据库,更新缓存”的操作放到内部队列里
2、更新操作,直接放到内部队列里
优点:不需要借助第三方消息队列
缺点:负载均衡处需要把数据的读操作和更新操作做路由处理
二、消息队列实现
将“程序实现”步骤中的内部队列替换成消息队列(rabbitmq、kafka等)
优点:保证一致性,不需要路由
缺点:流程比较长,处理时间较长
参考:
网友评论