大家好,我是 Snow Hide,作为《左耳听风》这个专栏的学员之一,这是我打卡的第 29 天,也是我第 29 次进行打卡这种操作。
今天我温习了该专栏里一篇叫《性能设计篇之“缓存”》的文章。
关键词总结:Cache Aside 更新模式(失效、命中、更新、问题、解决方案)、Read/Write Through 更新模式(Read Through、Write Through)、Write Behind Caching 更新模式(Write Back、套路、问题、复杂度)、缓存设计的重点(外部缓存集群、数据分片、命中率、一致性问题、缓存生命周期、LRU 策略、锁竞争、爬虫保护机制)。
所学总结:
Cache Aside 更新模式
最常用的设计模式。
失效
先从缓存读数据,没有的话再从数据库中读取,然后保存一份到缓存中。
命中
从缓存中读取到数据,直接使用缓存的数据。
更新
将新的数据存入数据库,然后让缓存失效,可以将新数据更新至缓存中。
问题
读取操作在写操作之前发生,会造成脏数据。
解决方案
通过 2PC 或 Pzxos 协议保证一致性。Facebook 使用了降低并发时脏数据的概率,因为 2PC 太慢,而 Paxos 太复杂。
Read/Write Through 更新模式
这个模式的套路是把更新数据库的操作由缓存自己代理了,应用程序可以认为后端就是一个单一的存储名,而存储自己维护自己的缓存。
Read Through
当缓存失效的时候(过期或 LRU 换出),由缓存服务自己来加载,对应用方是透明的。
Write Through
在更新数据时发生。没有命中缓存的话直接更新数据库,然后返回。如果命中了则更新缓存,然后由缓存自己更新数据库(同步操作)。
Write Behind Caching 更新模式
Write Back
Write Behind 又被称为 Write Back。就是 Linux 文件系统的 pagecache 算法。
套路
更新数据时只更新缓存不更新数据库,缓存会异步地批量更新至数据库。让数据库的 I/O 操作飞快无比(因为操作的是内存)。这个模式可以合并对同一个数据的多次操作,性能是比较客观的。
问题
数据不是强一致的,还可能会丢失。强一致性和高性能的组合或者高可用和高性能的组合是有冲突的,需要做折衷。
复杂度
需要跟踪那些数据库被更新了,并刷新至持久层。操作系统会在仅当缓存需要失效时,才将它持久化。例如内存不够或进程退出,属于 Lazy Write 的范畴。
缓存设计的重点
外部缓存集群
需要保证集群的内存足够大,网络带宽足够流畅,缓存本质上是个内存和 IO 密集型应用。
数据分片
将不同的缓存分布至不同的机器上。保证缓存集群可以不断地进行扩展操作。
命中率
命中率达到 80% 以上就挺高了。毕竟热点数据只是少数。
一致性问题
不是所有业务都适合用缓存。用缓存提高性能会有数据更新的延迟。
缓存生命周期
时间太短的话会导致应用程序不断从数据库检索数据。时间太长的话会导致内存中积攒过多的冷门数据。
LRU 策略
在内存不足时优先清除最少人访问的缓存数据。开启 LRU 策略后,缓存的每一个数据都在前面,从后往前开始淘汰数据。
锁竞争
LRU 在读写时需要加锁(单线程无并发的情况除外),所以 LRU 会导致更慢的缓存读写时间。
爬虫保护机制
爬虫爬取的数据不一定会是最热门的,所以会导致很多没有人访问的数据被缓存起来。我们需要避免爬虫来爬取那些数据,或是可以给外部提供 API,让第三方开发者拥有自己的调用接口(也是有缓存的)。
末了
重新总结了一下文中提到的内容:加速数据访问、典型的缓存模式、Cache Aside 更新模式、Read/Write Through 更新模式、Write Behind Caching 更新模式、各模式的优缺点、缓存设计重点、缓存集群、数据一致性、LRU 锁竞争、爬虫问题。