Cache Common
[TOC]
缓存
缓存同步策略
典型的缓存模式,一般有如下几种:
Cache Aside
Read/Write Through
Write Behind
Cache Aside
经常用到的一种策略模式。这种模式主要流程如下:
失效:应用程序先从 Cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从 Cache 中取数据,取到后返回。
更新:先把数据存到数据库中,成功后,再删除缓存。
这种缓存策略会在如下情况出现脏数据:
线程 A 查询一个数据,在缓存中没有找到,于是去数据库查,拿到数据之后还没放到缓存中。
这时候线程 B 过来更新这一条数据,先更新数据库之后准备令缓存失效,由于缓存还没放进来所以不需要失效。
线程 A 把数据放到了缓存中。但是这个数据其实已经是脏数据了,跟数据库中不同步。
这种情况发生概率要稍微低,因为需要在数据库的查询操作和缓存的更新操作之间,插入一个数据库的修改和缓存的修改操作,一般情况下,后者需要的时间是要远高于前者的。当然也不能忽视这种情况确实存在。
Read/Write Through
Read/Write Through 套路是把更新数据库的操作由缓存自己代理了,对应用层来说是透明的,应用不再需要关心数据同步问题。
Read Through
Read Through 就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或 LRU 换出),Cache Aside 是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载,从而对应用方是透明的。
Write Through
Write Through 套路和 Read Through 相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由 Cache 自己更新数据库(这是一个同步操作)。
这种方式的风险也显而易见,如果追求落库之后再返回成功,效率必然降低很多,缓存的意义就不大了。如果追求落到缓存上就算成功,那问题又抛给了缓存的丢失风险上。
Write Behind
Write Behind 又叫 Write Back。就是 Linux 文件系统的 Page Cache 的算法。Write Back 套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的 I/O 操作飞快无比(因为直接操作内存嘛)。
因为异步,Write Back 还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。性能很强悍,问题也很明显,数据不是强一致性的,而且可能会丢失。另外 Write Behind 实际上的逻辑还比较复杂,因为需要追踪定位哪些数据需要做持久化。
缓存常见问题
一致性问题
首先需要明确,除了把整个缓存和持久化存储都做串行化,或者操作一个的时候直接锁死另一个,或者通过消息队列操作缓存以达到最终一致性,不然必然会有产生数据不一致的可能。选择哪种缓存策略是根据自己的业务场景进行取舍。
强一致性
利用锁机制,串行化Cache、Db,且做好事务、缓存回滚机制,具体思路可以参考分布式事务的实现。
最终一致性
一般缓存偏向于最终一致性,常用的解决方案有:
基于Cache Aside策略
延迟双删
消息删除缓存
订阅业务消息
订阅Db的Log消息
PS:当然Cache的主要目的是为了性能,如果必须强一致性,则一定会带来性能损失,未必是个好主意,凡事需要结合业务以及实际应用场景来平衡。
缓存穿透
请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去。
可能引发的问题:数据库压力过大,扛不住。
解决办法:
缓存短时间的空值
布隆过滤器:可以用来判断超大数据量中单个数据的存在性问题。但是存在误差,如果布隆过滤器判断不存在,那肯定不存在,但是如果判断存在,也有一定的可能误判。
缓存雪崩
是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到数据库,数据库瞬时压力过重雪崩。
可能引发的问题:数据库压力过大,扛不住。
解决办法:
将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值。
缓存击穿
对于一些设置了过期时间的Key,如果这些Key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一 key 缓存,雪崩则是很多 key。
可能引发的问题:单个 key 过期的瞬间大量请求过来,导致数据库压力过大崩了。
解决办法:
加锁,单个请求去更新缓存就行了,别的排队等着。
双层的超时值,保存一个用于更新缓存的超时值,到达这个值之后去延长一下真是的超时时间。
热点Key
缓存中的某些Key(可能对应用与某个促销商品)对应的 value 存储在集群中一台机器,使得所有流量涌向同一机器,成为系统的瓶颈。
解决办法:
客户端缓存(多级缓存),将热点 key 对应 value 并缓存在客户端本地,并且设置一个失效时间。
将单个热点分散为多个子 key,时期请求的时候 hash 到不同的机器上处理。
Last updated