缓存:是什么?要不要?怎么样?

学而不思则罔,思而不学则殆。 -《论语》

一、缓存概念

“A cache is a hardware or software component that stores data so that future requests for that data can be served faster”,对来自wikipedia的定义,我们使用亚里士多德的“四因说”方法论来分解一下缓存的定义得到如下视图:

图1-1 缓存定义

使用缓存的目的是提升性能,缓存的表现形式通常是用于存储数据的硬件和或软件组件。

二、缓存使用策略

缓存的具体使用方式有多种策略,下面科普一下常见的缓存使用策略。

2.1 Read Through/Write Through VS Cache Aside

这里把Read Through/Write Through和Cache Aside放在一起讲,Cache Aside是相对于Read Through来说的,且他们是正交关系的两件事情,怎么理解呢,看如下坐标:

图2-1 Read Through/Write Through VS Cache Aside

Read Through指当缓存MISS的时候读取数据和更新缓存的策略,回答的是HOW的问题,而Cache Aside指的是这个事由谁来干,回答的是WHO的问题,也就是读数据更新缓存这个事儿由缓存服务来保证还是由应用来保证,如果由应用服务来保证,就是Cache Aside,如果由缓存服务来保证,就是非Cache Aside,两者对立统一。

1)Read Through

在Read Through模式下,当读缓存的时候发现数据不在缓存,则应用或缓存服务从database中读取数据然后更新缓存。

图2-2 Read Through

2)Write Through

在Write Through模式下,应用把缓存作为主数据写,缓存服务根据如下判断来决定执行的写逻辑:

  • 如果数据不在缓存中,则直接写database;

  • 如果数据在缓存中,则先写缓存,再写databse,且两步要确保在同一个事务中;

图2-3 Write Through

2.2 Refresh Ahead VS Read Through

Refresh Ahead和Read Through回答的都是在缓存MISS的时候什么时机将数据写入缓存的问题,Refresh Ahead特指在读取操作之前将数据写入缓存,而Read Through特指在读的时候发现数据没有的时候才去读取数据写入缓存,他们的主要区别在于写缓存的时机,示意图如下:

图2-4 Refresh Ahead VS Read Through

2.3 Write Behind VS Write Through

Write Behind和Write Through回答的都是写入缓存的数据什么时机写入database的问题,Write Behind指的是写完缓存异步写database,而Write Through指的是写完缓存同步写database,他们的区别在于是同步还是异步写database,示意图如下:

图2-5 Write Behind VS Write Through

三、什么时候引入缓存

3.1 响应延迟

对服务的响应延迟诉求是决定是否引入缓存的核心因素之一,当从原数据源读取数据的响应延迟满足不了外部对自己的响应延迟要求的时候,这个时候可以引入缓存来满足响应延迟,举个例子:上游服务要求自己20ms内响应,而自己从外部数据源读取数据的延迟本身已经是100ms了,此时可以引入缓存,响应延迟角度考量会相对容易一些,公式比较简单:

if (当前响应延迟 > 要求延迟) return “引入缓存”;

else return “不引入缓存”;

3.2 扩展成本

除了响应延迟之外,扩展成本也是用于决定是否要引入缓存的一个核心因素,在相同成本的情况下,缓存服务的吞吐量远大于服务(以Web服务为例)的吞吐量,怎么说呢?下面举个具体的例子:

假设:一台Web服务器成本4000元/年,容量是500QPS,扩展成本是4000元,一台缓存服务器成本是5000/年,容量是10000QPS,存储是5G,一个节点需要3台,一主两从,总计扩展成本是15000元;

再假设:当前业务场景流量是X,涉及数据量是Y,基于成本相等有如下等式:

X / 500 * 4000 = MAX(X / 10000 * 15000,Y/5 * 15000)

再再假设:不考虑带宽、算力及其他因素,我们分别取X=500,Y=1G和X=1000,Y=1G代入公式如下:

当X=500,Y=1G时,Web服务成本=4000元,缓存服务成本=3000元;

当X=1000,Y=1G时,Web服务成本=8000元,缓存服务成本=3000元;

可以得出,在以上假设的情况下,缓存的扩展成本小于Web服务,当然具体还和业务场景有关,也就是X和Y这两个变量,他们的关系如下示意图所示(业务量=F(X)* F(Y)):

图3-1 扩展成本与缓存引入

3.3 其他场景

不建议使用缓存。

四、怎么设置缓存策略

4.1 过期时间

缓存过期是维持缓存数据与存储数据一致性的一种手段,而缓存过期时间指的是缓存数据什么时候被认为是过期的数据,缓存过期时间不一定要设置,举两个例子:

例一:A场景数据量非常小,且是静态数据,数据量少意味着缓存成本不是问题,数据是静态的意味着不需要更新,为减少服务响应延迟和提升系统扩展性,我们可以选择数据常驻缓存,完全不需要设置过期时间;

例二:B场景数据量非常小,且该场景数据会变化的,且基于业务判断,该场景数据不一致的最大容忍度是1h,数据量少意味着缓存成本不是问题,数据是会变化的意味着存在一致性问题,为减少服务响应延迟和提升系统扩展性,我们引入缓存,使用read through模式,且缓存过期时间设置为1h;

上面举个两个例子,分别是不同的业务场景,我们能发现业务诉求会决定最终使用缓存的姿势,使用缓存的姿势决定是否要设置过期时间,业务对不一致的容忍度决定缓存的过期时间设置多少。

4.2 淘汰策略

缓存淘汰策略指的是当缓存存储空间达到某个最大限制阈值的时候选择哪个缓存数据淘汰,从而让出位置给后来的数据,缓存淘汰的基本假设是:缓存存储空间是有限的。因此核心关注的问题是如何充分利用好这有限的缓存存储资源,那么如何选择缓存淘汰策略呢?这个问题可以从“保留哪些数据更有价值”这个问题来拆解,答案不唯一,拆解如下:

图4-1 缓存淘汰策略选择

4.3 其他策略

篇幅有限,其他策略不再讨论。

五、总结

缓存如何使用是一个复杂的问题,缓存使用策略多,决策因素多,两个集合笛卡尔积之后是一个非常大的问题空间,本文认为使用缓存的第一个问题是要不要引入缓存,第二个问题才是怎么使用缓存,要不要引入缓存的核心考量因素是响应延迟和扩展成本,而如何使用缓存则和具体的业务场景有关(这是一句正确的废话),问题虽然复杂,但也不是不可解,假设不考虑缓存存储成本,本文根据经验总结缓存核心策略决策树如下,基于这颗决策树我相信可以解决80%的缓存使用问题:

图5-1 缓存使用决策树

六、参考文献

【1】azure/architecture/patterns/cache-aside

【2】wikipedia.org/wiki/Cache_(computing)

【3】Cache-Cache-Design-Patterns