缓存设计

Posted 宣之于口

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了缓存设计相关的知识,希望对你有一定的参考价值。

缓存设计

参考文章:redis缓存穿透,瞬间并发,缓存雪崩的解决方法

缓存处理流程:先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果

名称说明
缓存穿透查询缓存和数据库中都没有的数据
缓存雪崩查询缓存中没有但数据库中有的数据(一般是缓存时间到期)
缓存击穿缓存中数据大批量到过期时间,而查询数据量巨大

一、缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透

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 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据。

三、缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决办法有以下:

  • 互斥锁
  • 热点缓存用不过期

以上是关于缓存设计的主要内容,如果未能解决你的问题,请参考以下文章

CATIA宏:批量将零件转为设计模式插件,轻轻松松使用高速缓存

亿级系统的Redis缓存如何设计???

亿级系统的Redis缓存如何设计???

高并发Redis缓存如何设计

高并发系统设计 概述

由负载均衡至django缓存,浅谈对架构设计的理解