缓存面试解析:穿透、击穿、雪崩,一致性、分布式锁、Redis过期,海量数据查找
为什么使用缓存
在程序内部使用缓存,比如使用map等数据结构作为内部缓存,可以快速获取对象。通过将经常使用的数据存储在缓存中,可以减少对数据库的频繁访问,从而提高系统的响应速度和性能。缓存可以将数据保存在内存中,读取速度更快,能够大大缩短数据访问的时间,提升用户体验。
在业界中,通常在数据库之前添加一层Redis缓存,这样可以避免数据库的性能被大量的请求耗费。当有大量的并发请求时,数据库可能会成为瓶颈,而使用缓存可以有效地缓解数据库的压力。Redis作为一种高效的缓存解决方案,可以将热门数据存储在内存中,以快速响应用户的请求。这种缓存层的引入不仅可以提高系统的性能和吞吐量,还可以提高系统的可靠性和稳定性,因为即使数据库出现故障,缓存仍然可以提供部分服务。
缓存还可以减少网络传输的负载,特别是在分布式系统中。通过将计算结果或频繁访问的数据缓存起来,可以避免重复计算和重复访问数据库,节省了网络带宽和服务器的资源消耗。这对于海量数据的查找和计算密集型任务尤为重要,可以大大提升系统的效率和可扩展性。
总之,使用缓存可以优化系统的性能、提高响应速度、降低数据库负载、节省网络传输和服务器资源,从而提升用户体验和系统的可靠性。
缓存穿透、击穿、雪崩
缓存穿透:
缓存穿透指的是当一个请求查询的数据不在缓存中,也不在数据库中,导致每次请求都直接访问数据库,增加了数据库的负载。这可能是由于恶意攻击或者异常情况导致的。为了解决缓存穿透问题,可以采取以下措施:
- 在缓存中存储一个空值或者默认值,且设置成一定过期时间,以避免重复的无效查询,但是这种方案有缺陷就是redis会多出无用的key,浪费内存资源;
- 使用布隆过滤器等技术来过滤掉无效的请求,将可能不存在的数据快速过滤掉,布隆过滤器可以有效防止不存在的key进入业务调用数据库,但是需要提前将数据库数据预热到布隆过滤器中,并且他也有一种缺陷就是由于他的数据结构和算法导致无法删除热键,只能新增;
缓存击穿
缓存击穿指的是当某个热点数据过期或者被删除时,大量的请求同时涌入,导致数据库负载过高。这通常发生在高并发环境下。为了避免缓存击穿问题,可以采取以下措施:
- 第一种就是将热点数据永久缓存进redis,并另起一个线程定时的去更新这个热点数据,那么就热点数据永远不会失效,但是缺陷是在定时任务启动前可能存在数据错误的情况;
- 第二种情况那么就是加锁,使用互斥锁或者分布式锁来保护对数据库的访问,确保只有一个请求能够重新加载数据到缓存中。但是这种虽然解决了数据库问题,但同时也带来了性能下降;
缓存雪崩
缓存雪崩指的是当缓存中大量的数据同时过期时,导致大量的请求直接访问数据库,造成数据库负载过高。这通常是由于缓存服务器故障、网络故障或者缓存数据过期时间设置不合理等原因导致的。为了避免缓存雪崩问题,可以采取以下措施:
- 就是在给缓存数据设置过期时间的时候请加一个随机值使用不同的过期时间来分散缓存的失效时间,避免大量数据同时过期。
- 使用热点数据预加载技术,在缓存数据即将过期之前,提前加载数据到缓存中,确保数据的可用性。
如何保证缓存与数据库之间的数据一致性
保证缓存与数据库之间的强一致性是一个相对复杂的问题。尽管没有绝对的解决方案,但可以采取一些策略来尽可能地提高数据一致性。以下是几种常见的策略:
第一种就是先删除缓存还是先写数据库,这两种都一样,我就说下先删除缓存带来的问题,先删除缓存确实可以在写完数据库后后续的操作都会更新缓存值,但是扛不住并发高,如果删除完缓存后还没来得及写入又被另一个线程读取了旧值更新缓存,那么这缓存白删除了,
第二种就是先写数据库呢?如果数据库写完后,一是在删除缓存之前的读操作读取的仍然是旧值,二是,如果写操作完成后,缓存删除操作由于网络原因丢失了怎么办,以后读取操作都是旧值了;
第三种也就是业界最常用的延时双删;但同时他也无法一定保证数据的一致性
- 在操作数据库之前先删除缓存:首先,你需要先删除缓存中对应的数据,确保下一次读取请求不会命中旧的缓存数据。
- 更新数据库:然后,你可以更新数据库中的数据,确保数据库中的数据是最新的。
- 再次删除缓存:最后,在延时之后,再次删除缓存中的数据。这样可以确保在延时结束后,读操作仍然可以从缓存中获取最新的数据。
如果写操作很频繁,那么缺陷就很明显:很容易产生脏数据并且也无法满足缓存与数据库之间的一致性;
第四种:引入MQ,当我们有两个消费者的时候,一个消费者只管消息的数据库操作,一个消费者只管消息的缓存操作,这样可以确保操作是原子操作。确保了不会删除缓存失败的问题。
但是以上四种都无法保证缓存与数据库之间的强一致性,只能保证数据库与缓存之间的最终一致性;
如何设计分布式锁?如何对锁性能进行优化?
首先分布式锁主要应用场景就是应对多节点部署下如何控制资源的并发保护,那么单纯的jvm锁已经无法满足需求,所以引入了分布式锁,那么常见的有数据库、zookeeper、redis;通常分布式锁的要求的就是性能高、与业务无关;设计分布式锁时,常见的选择是使用Redis作为分布式锁的存储介质。下面将介绍如何设计分布式锁,并对锁性能进行优化。
首先,我们需要掌握Redis的基本命令:
SETNX:设置键值对,如果键不存在则返回1,如果键已存在则返回0。
EXPIRE:设置键的过期时间。
GETSET:先获取旧值,然后将新值设置进去;如果键不存在,则返回null。
DEL:删除一个键。
接下来,我们将讨论几种常见的分布式锁设计方式:
- 使用SETNX和DEL操作:在当前业务执行完毕后,使用DEL操作删除锁。但是如果获取锁的进程执行失败,它将永远不会主动解锁,导致锁被死锁。
- 使用SETNX和EXPIRE操作:这是最常见的分布式锁设计方式。但是存在一个问题,如果在设置过期时间之前节点挂掉,其他服务将永远无法获取到锁,因为SETNX和EXPIRE不是原子操作。
- 使用SETNX和GETSET操作:在设置锁时,将过期时间作为值存储在Redis中。当其他线程争取锁失败时,可以通过GETSET操作检查当前锁是否已经失效。如果锁已失效,则可以使用自己的过期时间来替换旧的值,并与之前的过期时间进行比较,以确定是否成功获取锁。下面给出伪代码示例:
public boolean tryLock(RedisConnection conn) {
long nowTime = System.currentTimeMillis();
long expireTime = nowTime + 1000;
if (conn.SETNX("mykey", expireTime) == 1) {
conn.EXPIRE("mykey", 1000);
return true;
} else {
long oldValue = conn.get("mykey");
if (oldValue != null && oldValue < nowTime) {
long currentValue = conn.GETSET("mykey", expireTime);
if (oldValue == currentValue) {
conn.EXPIRE("mykey", 1000);
return true;
}
return false;
}
return false;
}
}
上述代码实现了一种比较高效的分布式锁。然而,上述优化的根本问题在于SETNX和EXPIRE两个指令无法保证原子性。为此,Redis 2.6版本引入了执行Lua脚本的功能,通过Lua脚本可以保证原子性。Redission工具就是基于此原理提供的分布式锁工具。
如何设置过期时间,实现原理是什么?
redis有两种命令可以进行对key设置过期时间:expire和setex。这两种命令都可以用来给key设置过期时间。
实现过期时间的原理可以分为两个部分。
首先是主动删除。Redis会有一个定时任务,定期检查数据库中的key是否已经过期。如果发现某个key已经过期,那么Redis会直接将其删除。
其次是被动删除。当应用程序尝试获取一个已经设置了过期时间的key时,Redis会检查该key是否已经过期。如果已经过期,Redis会在返回结果之前将该key删除。
这样,通过主动删除和被动删除的组合,Redis实现了对key的过期时间的管理。这种混合实现的方式可以保证Redis中的数据始终是最新的,并且不会出现过期的数据。
需要注意的是,Redis并不会为每个key都启动一个单独的定时任务去检查过期时间。相反,Redis会根据实际情况动态调整定时任务的执行频率,以提高性能和效率。这种设计可以有效地减少对系统资源的占用,提高Redis的性能和稳定性。
海量数据下,如何快速查找一条记录?
当前这道题目考验的是对redis整体的理解,所以也要全方位考虑,可以考虑以下优化策略:
使用布隆过滤器:布隆过滤器是一种概率型数据结构,可以用于判断某个元素是否存在于集合中。在海量数据下,可以先使用布隆过滤器将不存在的key过滤掉,这样可以减少部分请求,提高查询效率。
合理选择存储结构:在缓存记录时,可以考虑使用适合的存储结构。如果存储的是大对象,使用key+value(json)形式,那么key可能会很大,不建议使用。而如果使用hash结构存储,可以充分利用Redis的哈希表特性,提高存储效率。此外,可以根据实际情况选择其他存储结构,如列表、有序集合等。
查询优化:如果Redis是集群部署的,数据根据槽位进行分配。如果我们自己对key进行了定位,可以直接访问对应的Redis节点,而不需要通过集群路由。这样可以减少Redis集群的机器计算,提高查询性能。
总结
本文提供了一些保证数据一致性和设计分布式锁的策略。这些策略可以在实际应用中帮助开发人员解决相关的问题,确保系统的数据一致性和并发访问的正确性。同时,通过合理地使用缓存和分布式锁,可以提高系统的性能和可靠性。希望对你在面对Redis相关面试题时有所帮助!
缓存面试解析:穿透、击穿、雪崩,一致性、分布式锁、Redis过期,海量数据查找的更多相关文章
- redis整理:常用命令,雪崩击穿穿透原因及方案,分布式锁实现思路,分布式锁redission(更新中)
redis个人整理笔记 reids常见数据结构 基本类型 String: 普通key-value Hash: 类似hashMap List: 双向链表 Set: 不可重复 SortedSet: 不可重 ...
- 和 chatgpt 聊了一会儿分布式锁 redis/zookeeper distributed lock
前言 最近的 chatGPT 很火爆,听说取代程序员指日可待. 于是和 TA 聊了一会儿分布式锁,我的感受是,超过大部分程序员的水平. Q1: 谈一谈 java 通过 redis 实现分布式 锁 ch ...
- Redis(七)缓存穿透、缓存击穿、缓存雪崩以及分布式锁
应用问题解决 1 缓存穿透 1.1 访问结构 正常情况下,服务器接收到浏览器发来的web服务请求,会先去访问redis缓存,如果缓存中存在数据则直接返回,否则会去查询数据库里面的数据,然后保存在red ...
- 缓存穿透、雪崩、热点与Redis
(拼多多问:Redis雪崩解决办法) 导读:互联网系统中不可避免要大量用到缓存,在缓存的使用过程中,架构师需要注意哪些问题?本文以 Redis 为例,详细探讨了最关键的 3 个问题. 一.缓存穿透预防 ...
- 《Redis - 穿透/击穿/雪崩/集中失效》
一:什么是缓存穿透? - 定义 - 正常情况下,我们在理想的条件下去查询缓存数据都是存在的. - 那么请求去查询一条数据库中不存在的数据,也就是缓存和数据库都查询不到这条数据. - 所以请求每次都会打 ...
- Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流
1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...
- 分布式锁--Redis小试牛刀
参考文章: Redis分布式锁的正确实现方式 分布式锁看这篇就够了 在这两篇文章的指引下亲测 Redis分布式锁 引言 分布式系统一定会存在CAP权衡问题,所以才会出现分布式锁 什么是CAP理论? 为 ...
- 分布式锁----Redis实现
分布式锁 为什么需要有分布式锁呢,在单点的时候synchronized 就能解决,但是服务拆分之后,每个服务都是单独的机器,无法解决,所以出现了分布式锁,其实也就是用各种手段,实现获取唯一锁,别人无法 ...
- NoSQL & Redis 介绍、缓存穿透 & 击穿 & 雪崩
1. NoSql 简介 2. Redis 简介 2.1 Redis 的起源 2.2 缓存过期 & 缓存淘汰 3. 缓存异常 1)缓存穿透 2)缓存击穿 3)缓存雪崩 4)总结 1. NoSQL ...
- Redis中几个简单的概念:缓存穿透/击穿/雪崩,别再被吓唬了
Redis中几个“看似”高大上的概念,经常有人提到,某些好事者喜欢死扣概念,实战没多少,嘴巴里冒出来的全是高大上的名词,个人一向鄙视概念党,呵呵! 其实这几个概念:缓存穿透/缓存击穿/缓存雪崩,有一个 ...
随机推荐
- 2021-02-18:给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文。arr每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来。返回需要至少多少张贴纸可以完成这个任务。例子:str= "babac",arr = {"ba","c","abcd"}。a + ba + c 3 abcd + abcd 2 abcd+ba 2。所以返回2。
2021-02-18:给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文.arr每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来.返回需要至少多少张贴 ...
- 2021-11-23:规定:L[1]对应a,L[2]对应b,L[3]对应c,...,L[25]对应y。 S1 = a, S(i) = S(i-1) + L[i] + reverse(invert(S(
2021-11-23:规定:L[1]对应a,L[2]对应b,L[3]对应c,-,L[25]对应y. S1 = a, S(i) = S(i-1) + L[i] + reverse(invert(S(i- ...
- 2021-09-27:Pow(x, n)。实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x**n)。力扣50。
2021-09-27:Pow(x, n).实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x**n).力扣50. 福大大 答案2021-09-27: 遍历n的二进制位. 时间复杂度:O( ...
- JS 数组常用操作全集
文章目录 1.push()方法 2.unshift()方法 3.pop() 方法 4.shift() 方法 5.filter() 方法 6.join()方法 7. indexOf() 方法 8.rev ...
- vue使用import()提示语法错误
一.使用import 引入 组件 二.编译时提示语法检测错误 三.解决方法 第一种方式:直接安装 D:\YLKJPro\CMWEB\03Implement\CustomMapWeb>npm in ...
- Python基础 - 第一个python程序
Python程序是什么? Python源程序就是一个特殊格式的文本文件,可以使用任意文本编辑器软件做python的开发,python的文件扩展名为 .py 执行python程序的三种方式 直接调用解释 ...
- Request类源码分析、序列化组件介绍、序列化类的基本使用、常用字段类和参数、反序列化之校验、反序列化之保存、APIVIew+序列化类+Response写的五个接口代码、序列化高级用法之source、序列化高级用法之定制字段的两种方式、多表关联反序列化保存、反序列化字段校验其他、ModelSerializer使用
目录 一.Request类源码分析 二.序列化组件介绍 三.序列化类的基本使用 查询所有和查询单条 四.常用字段类和参数(了解) 常用字段类 字段参数(校验数据来用的) 五.反序列化之校验 六.反序列 ...
- R 包初学者指南
由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中链接. 基于 11 个最常见的用户问题介绍 R 软件包. R 包是由社区开发 (developed b ...
- shell学习总结
shell教程 第一个shell脚本 打开文本编辑器(可以使用 vi/vim 命令来创建文件), 新建一个文件 test.sh,扩展名为 sh(sh代表shell) #!/bin/bash echo ...
- DataX入门教学
B站学习网址: https://www.bilibili.com/video/BV1H44y1x76X/?p=5&spm_id_from=pageDriver&vd_source=5f ...