缓存设计
Posted 宣之于口
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了缓存设计相关的知识,希望对你有一定的参考价值。
缓存设计
缓存处理流程:先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果
名称 | 说明 |
---|---|
缓存穿透 | 查询缓存和数据库中都没有的数据 |
缓存雪崩 | 查询缓存中没有但数据库中有的数据(一般是缓存时间到期) |
缓存击穿 | 缓存中数据大批量到过期时间,而查询数据量巨大 |
一、缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透
1.缓存空对象
如果一个查询返回的数据为空,我们把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟
问题:
-
空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
-
缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
2.布隆过滤器
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
注意使用counting Bloom filter
使其支持删除操作。用来判断某个元素是否在某个集合中, 算法判断key在集合中时,有一定的概率key其实不在集合中。
流程
a. 初始化:布隆过滤器是一个长度为n的bit数组,每个bit位初始化为0。同时我们需要准备k个hash函数,每个函数可以把key散列成为1个整数。
b. 增加:某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的bit位置为1
c. 查询:判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的bit位,如果所有的bit位都是1,认为在集合中。
下图可以看出Hash1的值为0,可以确定world这个值不存在。
计数布隆过滤器
counting Bloom filter
用于解决删除问题,即计数删除。
但是计数删除需要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。这样的话,增加一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。
二、缓存雪崩
缓存雪崩指如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩
1. 随机过期时间
设置随机过期时间,让缓存失效的时间点尽量均匀,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
2. 互斥锁
只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据
单机情况下可以用synchronized
来处理
if key存在
synchronized
分布式环境下可以用分布式锁来处理,即memcache的add
, redis的setnx
等。其中SETNX,是「SET if Not eXists」
的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果
存在的问题
- 吞吐量降低
- 可能出现死锁,需要设置锁时间
3. 二级缓存
A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
4. 热点缓存永不过期
为热点 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据。
三、缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决办法有以下:
- 互斥锁
- 热点缓存用不过期
以上是关于缓存设计的主要内容,如果未能解决你的问题,请参考以下文章