一、缓存的优缺点
- 优点 (加速读写,降低后端负载)
-
加速读写:因为缓存通常都是全内存的(例如
Redis
、Memcache
),而存储层通常读写能力差,通过缓存的使用可以有效的加速读写 -
降低后端负载:缓存可以减少访问量和复杂计算(例如复杂的SQL语句),在很大程度降低后端的负载
- 缺点 (数据不一致、代码维护成本和运维成本)
- 数据不一致:缓存层和存储层有一定时间窗口的不一致性
- 代码维护成本和运维成本:加入缓存后,需要同时处缓存层和处理层的逻辑,增加了代码的复杂性,同样也增加了运维成本
二、缓存常见的问题及解决方案
- 缓存穿透
定义:
缓存穿透是指查询一个不存在的数据,由于缓存不存在该数据,因此该请求会直接去数据库查询(直接穿透了缓存层)。若有人恶意多次进行该操作可能导致数据库瘫痪
解决方案: (缓存空对象和布隆过滤器)
1> 缓存空对象
如果一个查询返回的数据为空,就将这个空结果进行缓存,设置较短的过期时间。
缓存空对象的存在的问题:
(1) 空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有 效的方法就是针对这类数据设置一个较短的过期时间,让其自动剔除。
(2) 缓存层和存储层会有一段时间窗口的不一致,可能对业务有一定的影响。可以通过设置过期时间或者利用消 息系统清除缓存中的空对象。
2> 布隆过滤器
类似于哈希表的一种算法,用所有可能的查询条件生成一个bitmap,在进行数据库查询之前会使用这个bitmap 进行过滤,如果不在其中则直接过滤。
优点:查询快,占用空间小。
缺点:存在误判,删除困难。(如何减少布隆过滤器的误判:增大位数组的长度,增加哈希函数的数量。)
-
缓存雪崩
定义:
在普通的缓存系统中一般例如
redis
、memcache
等中,我们会给缓存设置一个失效时间,但是如果所有的缓存的失效时间相同,那么在同一时间失效时,所有系统的请求都会发送到数据库层,数据库可能无法承受如此大的压力导致系统崩溃。解决方案: (线程互斥 和 交错失效时间)
1> 线程互斥: 只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据才可以,每个时刻只有一个线程在执行请求,减轻了数据库的压力,但缺点也很明显,降低了系统的qps。
2>交错失效时间: 既然在同一时间失效会造成请求过多雪崩,那我们错开不同的失效时间即可从一定长度上避免这种问题,在缓存进行失效时间设置的时候,从某个适当的值域中随机一个时间作为失效时间。
-
缓存击穿
定义:
缓存击穿实际上是缓存雪崩的一个特例,大家使用过微博的应该都知道,微博有一个热门话题的功 能,用户对于 热门话题的搜索量往往在一些时刻会大大的高于其他话题,这种我们成为系统的“热点“,由于系统中对这些热点的 数据缓存也存在失效时间,在热点的缓存到达失效时间时,此时可能依然会有大量的请求到达系统,没有了缓存层 的保护,这些请求同样的会到达数据库从而可能引起故障。击穿与雪崩的区别即在于击穿是对于特定的热点数据来 说,而雪崩是全部数据。
解决方案:
1> 对热点数据进行二级缓存,并对不同级别的缓存设定不同的失效时间
2> 采用LRU算法,即最近最少使用。依据“长期不被使用的数据,未来用到的概率也不大“,因此当内存达到阈值时,首先淘汰最近最少被使用的数据,具体实现过程为:
(1) 创建一个缓存哈希链表用于存储数据(相比哈希表多了一个指针用于链表排序)
(2) 添加新节点的时候,将新节点添加到链表尾部
(3) 若该节点以存在,把该节点移动到链表的尾部
(4) 当缓存链表已满时,添加新节点的时候,会把链表头部移除,然后将新节点添加到尾部