缓存学习笔记

266 阅读4分钟

业务缓存的基本概念与整体设计思路

缓存的使用流程

请求数据时,先判断缓存是否存在,存在则反序列化数据,然后返回数据,如果缓存不存在,则回源拿到数据 ,序列化后将数据更新到缓存,并返回数据

image.png

缓存的4种模式:

Cache Aside:

业务程序先请求cache,如果命中直接返回,不命中,就从db加载数据,然后回写到cache中

image.png

Read Through:

业务程序不会直接访问db,而是由cache负责从db加载数据

image.png

Write Through(同步写db):

业务程序写cache,然后由cache更新db

image.png

Write Behind(异步写db):

跟Write Through类似,业务程序写cache,然后由cache更新db,区别在于Write Behind是异步写db

image.png

业务缓存设计应尽量采用Read Through,整体设计思路如下:

image.png

其中用户接口我们应该提供最基础的Get和MGet,获取数据和批量获取数据的能力

实现方式1: image.png

实现方式2:

image.png

不推荐:方式1,实现起来复杂,用起来有点摸不着头脑 推荐:方式2,清晰明了

image.png

缓存设计的例子:

image.png

在设计时,应该考虑到轮子的更新迭代速度较快,我们的基础组件设计不应该过于复杂,方便替换,所以在设计之初应该依赖于接口,而非实现 image.png 缓存接口设计示例:

image.png

序列化设计实例:

image.png

接口设计的一些好处:

image.png

小结:

image.png

业务缓存基础库选型与回源

缓存库很多,我们应该怎么选?只需抓住两个重点,命中率性能

命中率:

用来做缓存的数据,总量是有限的,如果能全存下来,命中率就是100%,如果不能,就要靠逐出算法来筛选出价值最高的数据,常见的逐出算法如下:

image.png

FIFO、ARC、Tiny-LFU是对LRU和LFU的改良算法。

性能:

第一个影响性能的关键因素是

锁的颗粒度: image.png

锁的类型: image.png

另一个影响性能的指标是GC

从存储对象上看,缓存分为:

image.png

如下是两个带指针和不带指针的例子

例子1:

image.png

可以看出紫色的是map[int]*int,携带了指针,GC耗时明显比不带指针的map[int]int要高

例子2:

image.png

这个例子要稍微复杂一点,可以看到gc的耗时猛然飙五六十甚至一百多毫秒,而在实际业务中,可能更加复杂,带来的GC耗时也成倍增长。

如何选择相应的存储对象?

image.png

字节缓存和对象缓存对内存的控制

image.png

序列化包的选择 image.png

小结:

image.png

回源:

抽象,同样的函数,参数,返回值,屏蔽内部实现细节 image.png

如何不跟类型绑定? image.png

生成缓存key image.png

回源函数参数 image.png

image.png

image.png

什么是缓存穿透?

一个不存在的key,总能绕过缓存,直接请求数据源。如果遇到恶意请求,可能会把数据源打崩

常见解决方案:

1、布隆过滤器

特点: 如果不存在就一定不存在

正好可以前置检查key是否存在,不存在的话就不请求数据源,可以借助Redis的BitMap实现

缺点: 维护比较麻烦,需要在写操作时更新布隆过滤器,如果我们无法获取全量数据,或者监听写操作,就不太好构建布隆过滤器

2、缓存nil

特点: 简单,更常见

如果key不存在,就在缓存中写一个空值或者nil,空值不一定是0或者字符串,可以是业务含义上的空值

什么是缓存击穿?

一个热key突然失效,导致大量的请求回源,给数据源带来较大的压力

常见解决方案:

1、singleflight

golang.org/x/sync/singleflight:程序内多个goroutine同时回源,只允许一个goroutine回源,其他goroutine阻塞,共享回源结果,借此减少单实例的回源请求数量

分布式singleflight:

image.png

2、多层缓存

使用多级缓存,每层的过期时间不一样,缓存miss后,优先从下一层缓存加载数据,都miss再回源

还可以做缓存异构:Redis + 其他分布式KV

小结:

image.png

总结:

image.png