缓存的更新策略

更新数据库和Redis本身就不是原子操作,所以无论采取何种方式都不能保证强一致性,只能保证最终一致;
只能尽可能降低不一致性发生的几率,不能从根本上完全避免。

先更新数据库,在更新缓存

业务角度对于读少的场景,浪费性能资源
线程安全容易产生脏读。比如A线程更新数据库,B线程更新数据库;B线程更新缓存,A线程更新缓存

先删除缓存,再更新数据库

线程安全
请求A进行更新操作,先删除缓存;
请求B查询缓存,发现缓存失效,读取数据库,写入缓存;
请求A将数据写入数据库.
长时间读取到的都是脏数据
解决方法
采用延时双删除:在A写入数据库后,延时一定时间,再次删除缓存
延时时间设置:读写同步–读数据的业务逻辑+几百毫秒;读写分离–主从同步延时+几百毫秒

先更新数据库,再删除缓存

错误情况
缓存失效,请求A查询数据库;
请求B写入数据库,请求B删除缓存;
请求A将查询的数据写入缓存;
只有当读比写慢时,才会发生这种情况

解决方案
异步延迟删除

https://www.jianshu.com/p/85a66af8a3f3

暂时不讨论缓存更新和数据更新为一个事务,或者失败的情况

Cache Aside Pattern

缓存失效
先从缓存中获取数据,缓存失效,从数据库获取后,加载进缓存
缓存命中
从缓存中获取数据,取到后返回
缓存更新
先把数据存至数据库,成功后,失效缓存

读请求
命中缓存,返回数据;
未命中缓存,从数据库读取数据,更新缓存,返回数据。
写请求
更新数据库,删除就缓存

分析 该策略相对先删除缓存再更新数据库的方式,少了删除缓存的动作。而是先更新了数据库,此时缓存依然有效。所以,并发操作拿到的是老数据,但是更新操作完成后,后续的查询操作都是获取的新数据。不会导致,长时间使用老数据进行业务处理。
问题
一个读操作,没有命中缓存。从数据库中获取了结果;
此时,一个写操作,写完数据库之后,将缓存失效;
然后,读操作将老数据放入缓存。
解读
这种问题出现的概率比较低。因为需要一个读操作,并发一个写操作。而且要求读操作,在写操作之前进入数据库获取到数据。实际上,写操作要远远慢于读操作,且会发生锁表。读操作读在写操作之前,但是更新缓存在写操作之后,所以这些条件都具备的概率,基本不大
建议给缓存设置失效时间

Read/Write Through(读写穿透)

Read/Write Through中,是将缓存作为主要数据存储。应用程序与数据库缓存交互,通过抽象缓存层完成

Read Through

读请求,从缓存获取数据,如果未获取到,从数据库加载,存入缓存;
分析Read Through其实是在Cache-Aside之上,将缓存和数据库之间的交互进行了封装。

Write Through

写请求,写入数据时,先更新数据库,再更新缓存
分析将Cache-Aside的写请求处理上,将先写入数据库,再删除缓存;变更为先写入数据库,后写入缓存;

Write Behind(异步缓存写入)

读写操作仅针对内存,再由封装的工具进行数据库的批量更新。缓存与数据库的一致性较低,适合多写少读的场景。

问题

操作缓存时,删除缓存呢?还是更新缓存呢?

第一种情况
A线程操作写入,先更新了数据库;
B线程操作吸入,先更新了数据库,更新缓存;
A线程更新缓存
此种情况下,造成缓存与数据库中数据不一致
第二种情况
如果缓存中存储的是加工过的数据,更新频率过高会影响服务性能
在写多读少的情况下,更新缓存的话,可能数据还没有被读取,又被更新了。(其实写多读少的情况下,用缓存的意义也不是很大)

双写的情况下,先操作数据库还是先操作缓存? 为什么不先操作缓存呢?

先操作缓存的情况
A线程发起写操作,删除缓存;
B线程发起读操作,未命中,读取数据库,写入缓存;
A线程将数据写入数据库

延时双删策略
删除缓存重试机制
写请求,写入数据库,删除缓存;
删除缓存时失败,将删除失败的key放入消息队列中;
获取到缓存后,继续进行缓存操作

读取binlog异步删除缓存
重试删除会造成很多的代码侵入。
所以考虑读取binlog进行异步删除缓存