文章目录
前言
本文对Redis的使用场景及使用过程中遇到的问题进行总结,重点对面试的问题进行介绍,祝愿每位程序员都能上岸!!!
一、你们项目中使用Redis都做了什么:
Redis 相信各位小伙伴在项目中都用到过,在我们的项目中 通常使用Redis 进行:
- 使用Redis 进行数据的缓存,避免每次都到数据库获取数据,达到高效数据获取和节约系统资源的目的;
- 在分布式服务中对公用资源进行修改,使用Redis 作为分布式锁,保证并发的安全性;
二、使用过程中遇到缓存穿透,缓存击穿,缓存雪崩你们如何处理:
既然将部分数据存入Redis 中,随之而来的问题就是,从缓存中获取不到数据,从而到数据库进行数据查询,
造成数据库的压力,此时需要针对不同的场景进行不同的处理。
2.1 缓存穿透:
当访问一个在redis 中不存在的key 时,此时就会到数据库进行数据产线,然而数据库也没有这个key 的数据,当然不会将改key
缓存到redis 中去,但是一直有大量请求来访问这个在数据库中不存在的key,从而击垮数据库,这种情况通常是服务受到了攻击。
2.1.1 通过缓存key值为null 进行处理:
当数据库中也查不到改key 值的信息,则在redis 对改key 设置null 值 如: {key:1,value:null},并且设置下改key 的过期时间,这样在key 过期之前,访问改key 可以获取到null 数据,避免直接访问数据库;改方法的优点是:实现起来简单;缺点是:会造成数据的在缓存和数据库的不一致性;
伪代码如下(示例):
String value = redisTemplate.get(key);
if (null == value){
// 从db 进性数据获取
String dbValue = getKeyValue()...
if (null == dbValue){
// 从数据库中获取不到数据设置key 为null
redisTemplate.set(key,null,30s);
}
}
2.1.2 使用布隆过滤器:
1) 通过多维护一份数据到布隆过滤器中,查询数据时先到布隆过滤器访问key 是否存在,如果存在在进行redis 的访问;
2) 布隆过滤器的原理:
布隆过期是一个存放0,1位图的数组结构数据,通过算法将给定key 进行 hash 运算然后与数组的长度取余数得到具体的数组下标,
经过多次运算就可以得到多个index 下标,此时就可以将对应数组小标的位置置为1;当查询的时候同样通过hash 运算得到几个
下标,如果数组下标位置全为1 则认为改key 在redis 可能存在,否则这个key 在redis 一定不存在;
因为不同的key可能产生相同的hashcode 值,所以布隆过滤器有一定概率的误判:
3) 布隆过滤器的优缺点
- 占用较少的内存;
- 存储redis中的数据也要在布隆过滤器存储一份
- 布隆过滤器的元素一般不能够进行删除
- 出现误判:布隆过滤器返回true 实际上这个key并不存在
4)在java 中的使用:
4.1) 引入Google Guava库的依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version> <!-- 请检查并使用最新版本 -->
</dependency>
4.2) 在将key 放入redis 的同时也放入到布隆过滤器一份:
我们首先指定了预期插入的元素数量(expectedInsertions)和期望的误报率(fpp)。然后,我们使用BloomFilter.create方法创建一个布隆过滤器,并使用字符串作为输入类型。我们使用Funnels.stringFunnel(StandardCharsets.UTF_8)来指定如何将字符串转换为字节数组,这是布隆过滤器所需的格式。
接下来,我们使用put方法向布隆过滤器中添加一些元素。然后,我们使用mightContain方法来检查一个元素是否可能存在于布隆过滤器中。请注意,由于布隆过滤器的性质,mightContain方法可能会产生误报(即返回true,但实际上元素并不存在于过滤器中),但永远不会产生误报(即返回false,但实际上元素存在于过滤器中)。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.StandardCharsets;
public class BloomFilterDemo {
public static void main(String[] args) {
// 预期插入的元素数量
int expectedInsertions = 1000000;
// 误报率,通常设置为一个非常小的值,如0.01或更低
double fpp = 0.01;
// 创建布隆过滤器
BloomFilter<CharSequence> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8),
expectedInsertions,
fpp);
// 插入一些元素
bloomFilter.put("element1");
bloomFilter.put("element2");
// ... 插入其他元素
// 检查元素是否存在(注意:可能会有误报)
boolean mightContainElement1 = bloomFilter.mightContain("element1");
System.out.println("mightContainElement1: " + mightContainElement1); // 预期输出: true
boolean mightContainElement3 = bloomFilter.mightContain("element3");
System.out.println("mightContainElement3: " + mightContainElement3); // 可能输出: false 或 true(误报)
// 插入一个新元素后,再次检查
bloomFilter.put("element3");
boolean definitelyContainsElement3 = bloomFilter.mightContain("element3");
System.out.println("definitelyContainsElement3: " + definitelyContainsElement3); // 预期输出: true
}
}
2.1.3 说说布隆过滤器的原理
布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是redisson实现的布隆过滤忝。
它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。
当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。
2.2 缓存击穿是什么:
2.2.1缓存击穿的场景
给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮。
2.2.1 你们项目中是怎么处理的:
1)针对时间要求低的场景可以使用互斥锁来解决:
每次进行缓存重建的这个过程就先获取锁,然后查询db ,然后在将数据进行redis 缓存。
2)针对时间要求比较高的场景 热点数据的key 不设置缓存失效时间:
热点key 本身不设置过期时间,永不失效,但是在业务上对key 对应的value 增加一个有效期,标识数据的失效时间,这样每次都可以获取到数据,
如果数据过期了,则开启一个新的线程去更新数据,在缓存重建过程中获取互斥锁失败则直接返回已过期的数据;
3) 两种方案对比:
- 互斥锁的可以保证数据的高一致性,但是效率低
- 逻辑互斥可以保证高可用,不能保证数据的绝对一致
2.3 缓存雪崩是什么:
缓存雪崩是redis 的key 在某一个时间段内,出现大量失效或者redis 服务崩溃,大量的key 直接访问数据库,击垮数据库
你们项目中怎么应对缓存雪崩问题:
- 在设置key的过期时间时增加一个随机值,防止key 批量过期;
- 使用redis集群保证redis 服务的高可用
三、你们项目中缓存与数据库数据不一致怎么办:
因为redis 和数据库是两个服务,在对数据进行更新的时候,因为redis 并没有实现XA协议,所以无法使用分布式事务,此时就要考虑,redis 和数据库
中的数据一致性的保持;
3.1 延时双删:
在大多数场景下都可以使用延时双删 进行处理,即先删除缓存数据,然后修改数据库数据,然后延时一定时间后再删除缓存:
这里为什么要删除两次?
因为无法保证线程执行顺序,所以只有进行两次删除,确保后面线程可以读到数据库中最新的数据;
第二次为什么要进行一定的延迟:
- 保证mysql 数据的落盘;
- 确保mysql数据的主从同步;
3.2 延时双删一定能保证数据的一致性吗:
在对Mysql 进行集群时,常常会用到主-从架构,在项目中也会用到Mysql 的读写分离,此时如果在 延时一定时间之后,数据依然没有从Master节点同步到Slave 节点
,此时线程访问redis 的某个key 发现没有数据,则会从数据库中加载 到脏数据,出现数据不一致性;
3.3 怎么保证数据的一致性:
3.3.1 强一致性场景使用锁:
1) 使用互斥锁:
2) 使用redisson读写锁:
代码实现参考:
3.3.2 最终一致性:
1) 更新数据库后发送MQ消息,然后消费MQ消息进行数据更新:
2)Canal 监听Mysql 特定表的某些修改:
总结
本位对Redis 常见的缓存穿透,缓存击穿,缓存雪崩,数据库和缓存数据一致性进行总结。
标签:缓存,redis,数据库,Redis,布隆,过滤器,key,一致性 From: https://blog.csdn.net/l123lgx/article/details/140033165