首页 > 数据库 >面试篇-Redis-1缓存三兄弟+数据一致性

面试篇-Redis-1缓存三兄弟+数据一致性

时间:2024-07-04 18:57:41浏览次数:18  
标签:缓存 redis 数据库 Redis 布隆 过滤器 key 一致性

文章目录


前言

本文对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

相关文章

  • [数据库][Redis]
    redis在项目中的主要作用缓存,速度比较快计数器,incrdecr消息队列,消息的订阅和发布机制排行榜,zset分布式锁redis用作消息队列RabbitMQ和Redis都可以用作消息队列,但它们在设计、功能和适用场景上有显著的不同。理解这些差异有助于在项目中做出恰当的选择。RabbitMQ类型:......
  • 没有使用Redis相关的代码或依赖,但在 `application.yaml` 配置文件中配置了Redis参数,项
    个人名片......
  • Ubuntu / Debian 进行缓存软件,加速 APT 下载
     参烤:https://zhuanlan.zhihu.com/p/585124448为Ubuntu/Debian进行缓存软件,加速APT下载在不使用APTProxy的时候,我们想要更新和安装软件(比如 vim),会使用下面的命令:apt-getupdateapt-getinstallvim-y为了方便后边进行效果对比,我们在命令前添加一个 time 命令......
  • 实现返利App中的数据缓存与预加载机制
    实现返利App中的数据缓存与预加载机制大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!数据缓存是指将经常访问的数据临时存储在高速存储器中,以提高数据访问速度和系统性能。在返利App中,数据缓存和预加载机制能......
  • K8S学习教程(二):在 PetaExpress KubeSphere容器平台部署高可用 Redis 集群
    前言Redis是在开发过程中经常用到的缓存中间件,为了考虑在生产环境中稳定性和高可用,Redis通常采用集群模式的部署方式。在制定Redis集群的部署策略时,常规部署在虚拟机上的方式配置繁琐并且需要手动重启节点,相较之下,使用PetaExpress提供的Kubernetes(k8s)服务进行Redis集......
  • 关于redis锁的详解
    引用   https://www.jb51.net/article/251428.htmLocklock=newReentrantLock();@AutowiredStringRedisTemplateredisTemplate;publicstaticfinalStringg01="good:01";publicstaticfinalStringREDIS_LOCK="good_lock";......
  • 数据特征采样在 MySQL 同步一致性校验中的实践
    作者:vivo互联网存储研发团队-ShangYongxing本文介绍了当前DTS应用中,MySQL数据同步使用到的数据一致性校验工具,并对它的实现思路进行分享。一、背景在MySQL的使用过程中,经常会因为如集群拆分、数据传输、数据聚合等原因产生流动和数据复制。而在通常的数据复制过程中,因......
  • NoSQL 之 Redis 集群部署
    前言:(1)主从复制:主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。(2)哨兵:在主从复制的基础上,哨兵实......
  • 使用Redis实现消息队列:List、Pub/Sub和Stream的实践
    摘要Redis是一个高性能的键值存储系统,它的多种数据结构使其成为实现消息队列的理想选择。本文将探讨如何使用Redis的List、Pub/Sub和Stream数据结构来实现一个高效的消息队列系统。1.消息队列的基本概念消息队列是一种应用程序之间进行通信的机制,允许应用程序以异步的......
  • Redis缓存穿透
    现象:1、应用服务器压力变大2、redis命中率降低3、一直查询数据库产生原因;1、redis查询不到数据2、出现很多非正常URL访问解决方案:1、对空值做缓存---如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过......