Cache Aside Pattern (旁路缓存模式)
旁路缓存模式是平常使用较多的一个缓存读写模式,比较适合读请求比较多的场景。在旁路缓存模式中服务端需要同时维系DB和Cache,并且是以DB的结果为准。
旁路缓存模式下的缓存读写步骤:
写:
- 先更新DB
- 再直接淘汰(删除)Cache
读:
- 从Cache中读取数据,读取到数据就直接返回;
- Cache中读取不到数据的话,就从DB中读取数据返回,
- 再把数据放到Cache中。
旁路缓存模式中为什么建议淘汰缓存,而不是更新缓存?如果使用更新缓存,在并发写的时候,可能出现数据不一致。例如:操作1和操作2并发写时,由于无法保证时序,此时不管先操作缓存还是先操作数据库,都可能出现①请求1先操作数据库,请求2后操作数据库;②请求2先set缓存,请求1再set缓存,导致了数据库与缓存之间的数据不一致。所以旁路缓存模式中建议delete缓存,而不是set缓存。
旁路模式中为什么建议先操作数据库,再操作缓存?如果先操作缓存,在读写并发时,可能出现数据不一致的问题。例如:操作1和操作2并发读写发生时,由于无法保证时序,可能出现:写请求淘汰缓存;写请求操作了数据库(主从同步没有完成);读请求读取了缓存(miss);读请求读了从库(读了一个旧数据);读请求set回缓存(set了一个旧数据);数据库主从同步完成。导致了数据库与缓存的数据不一致。所以,旁路模式中建议先操作数据库,再操作缓存。
旁路缓存模式方案存在什么问题?如果先操作数据库,再淘汰缓存,在破坏原子性时:①修改数据库成功;②淘汰缓存失败,导致,数据库与缓存的数据不一致。
旁路缓存模式的缺陷
- 首次请求数据一定不在Cache的问题
- 可以将热点数据提前放入Cache中。
- 写操作频繁的话导致Cache中的数据会频繁被删除,将影响缓存命中率
- 数据库和缓存数据强一致场景:更新DB的时候同样跟新Cache,不过需要加一个锁/分布式锁来保证更新Cache的时候不存在线程安全问题。
- 可以短暂地允许数据库和缓存数据不一致场景:更新DB的时候同样更新Cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。
Read/Write Through Pattern(读写穿透模式)
读写穿透模式中服务端把Cache视为主要数据存储,从中读取数据并将数据写入其中。Cache服务负责将此数据读取和写入DB,从而减轻了应用程序的职责。
写:
- 先查Cache,Cache中不存在,直接更新DB;
- Cache中存在,则先更新Cache,然后Cache服务自己更新DB。
读:
- 从Cache中读取数据,读取到直接返回,
- 读取不到,先从DB加载,写入到Cache后返回响应。
读写穿透模式只是在旁路缓存之上进行了封装。在旁路缓存模式下,发生读写请求的时候,如果缓存中不存在对应的数据,由客户端自己负责把数据写入Cache中,然而在读写穿透模式则是Cache服务自己来写入缓存,这对客户端是透明的。和旁路缓存模式一样,读写穿透模式也有首次请求数据一定不在Cache的问题,对于热点数据可以提前放入缓存中。
Write Behind Pattern(异步缓存写入)
异步缓存写入模式和读写穿透模式很相似,两者都是由Cache服务来负责Cache和DB的读写。但是,两者又有个很大的不同:读写穿透模式是同步更新Cache和DB,而异步缓存写入则是只更新缓存,不直接更新DB,而是改为异步批量的方式来更新DB。
这种方式对数据一致性带来了更大的挑战,比如Cache数据可能还没异步跟新DB的话,Cache服务可能就挂掉了。在消息队列中消息的异步写入磁盘、MySQL的InooDB Buffer Pool机制都用到了这种策略。异步缓存写入模式下DB的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。