redis知识整理
- 什么是缓存穿透,怎么解决
- 布隆过滤器
- 什么是缓存击穿,怎么解决
- 互斥锁和分布式锁
- 什么是缓存雪崩,怎么解决
- Redis作为缓存,mysql如何与redis进行同步呢?(双写一致)
- 一致性要求高
- 排他锁底层
- 允许延迟一致
- 延时双删
- 优化
- Redis数据持久化怎么做的
- RDB对比AOF
- Reids的过期策略
- Redis的淘汰策略
- 数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?
- 内存用完了会发生什么
- 分布式锁如何实现
- 如何控制分布式锁有效时间
- redisson
- lua
- redisson实现的分布式锁是可重入的吗?
- 参考
什么是缓存穿透,怎么解决
缓存穿透是指查询一个一定不存在的数据,导致每次请求都到DB查询,可能导致DB异常,大概率是遭到攻击,可以使用布隆过滤器来解决。
布隆过滤器
布隆过滤器用于检索一个元素是否在一个集合中,可以使用redisson实现,原理是通过多次hash计算将key映射到一个位数组(以位为单位存储信息。每个位只能存储0或1两种状态)中,如果一个元素对应的所有位都是1,那么它可能存在于集合中;但如果任何一个位是0,就可以肯定这个元素不在集合中。
当然这种方法有一定的误报率,非常适合于那些可以容忍偶尔误报的场景,因为与此带来的高效查询相比,这是可以接受的。
什么是缓存击穿,怎么解决
缓存击穿是指对于设置了过期时间的key,缓存在过期时,恰好有大量对这个key的请求过来,请求发现缓存过期就会从DB加载,这些请求可能会导致DB异常。
可以通过锁解决
使用SETNX设置一个互斥锁。
判断是否设置成功:
如果设置成功,说明当前没有其他请求在加载数据,那么这个请求就负责加载数据(load db)。
如果设置失败,说明已经有其他请求在加载数据了,当前请求则可以等待一小段时间后重试获取缓存。
完成数据加载后:
将数据回设到缓存中。
释放锁,让其他请求可以获取到最新的缓存数据。
互斥锁和分布式锁
都是通过某种形式的SETNX命令来实现,但它们的概念和使用场合有所不同:
**互斥锁(Mutex Lock)**通常用于确保某个时间点只有一个进程或线程可以执行某个代码段。
**分布式锁(Distributed Lock)**是用于在分布式系统中防止多个进程同时访问某个共享资源。
**排他锁(Exclusive Locks)**或写锁在读写锁的上下文中使用,是一种特别的锁,用于写操作。提及排他锁时,我们往往是在讨论读写锁的一部分,它和另一种锁——共享锁或读锁——共同作用,以维持数据的完整性。
互斥锁和排他锁在概念上是相似的
什么是缓存雪崩,怎么解决
缓存雪崩指的是缓存使用了相同的过期时间,导致在某一时刻同时失效,请求全部转到DB,可能导致DB异常。
在原有失效时间增加随机值,分散失效时间
Redis作为缓存,mysql如何与redis进行同步呢?(双写一致)
一致性要求高
结合业务,车辆登录状态同步,(看车辆点火时间)时效性比较高,使用读写锁保证一致性
采用redisson的读写锁,读的时候添加共享锁,不影响并发读取;更新时添加排他锁,保证写操作的独占性。
非常适合读多写少的场景,需要注意的是,无论是读还是写操作,都应该使用同一把锁(例如,如果有一个锁对象叫做myLock,那么所有尝试读的线程都应该通过这个myLock获取共享锁(读锁),而所有尝试写的线程则应该通过相同的myLock获取排他锁(写锁))
排他锁底层
setnx
允许延迟一致
延时双删
并不能绝对保证数据的强一致性
**写数据库:**首先,将新的数据写入数据库。
**删除缓存:**然后,立即删除缓存中的旧数据。
**延时:**等待一段时间(延时),这个时间足够长以确保所有从库里携带了旧数据的请求都完成,
**再次删除缓存:**在延时之后,再次删除缓存,以处理在延时期间,新的读请求将旧的数据又写回缓存的情况。
(再次删除后,缓存再次被写入就一定是新数据了,不在乎延时期间的请求是新是旧)
优化
综上所述,延迟双删延时期间的数据很不稳定,延迟时间不好确定,所以可以使用异步读写锁的情况,数据被修改时,通过rabbitmq异步实现数据同步
Redis数据持久化怎么做的
redis提供了两种持久化方式
RDB是一个快照文件,把redis数据写到磁盘,宕机后从快照恢复
AOF是追加文件,redis写命令时,存储到文件,宕机后再执行一遍命令
RDB对比AOF
RDB是二进制文件,体积小,恢复快,但是可能会丢数据
AOF虽然慢,但是丢数据风险小,可以设置刷盘策略,每秒批量写入一次命令
Reids的过期策略
惰性删除:设置过期时间后,需要时检查是否过期,过期删除,反之返回key
定期删除:每隔一段时间,删除过期key
Redis的清理策略是定期清理和惰性删除两者并存
Redis的淘汰策略
当 Redis 的内存使用超过设定的 maxmemory 时,就回采取一些策略来清理数据,释放内存,这些策略就被称为淘汰策略。具体有以下几种:
noeviction: 达到最大内存后,不会主动淘汰数据,所有写入操作(包括del、set等)都将返回错误信息。
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰。
volatile-lru:从已设过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
allkeys-random:随机移除某个 key。
volatile-random:从已设过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
volatile-ttl:根据键值对的剩余生存时间移除(越早过期,越先被移除)。
其中 “volatile” 的策略会尝试只淘汰已经设置了过期时间的键,而 “allkeys” 则会在所有键上进行淘汰。
默认的淘汰策略是 noeviction,并且默认不设定最大内存(maxmemory),这意味着默认配置下 Redis 几乎不会进行内存的淘汰操作,当内存不足时,新的写入会失败。所以,根据应用场景的不同,需要我们去合理设置淘汰策略,以实现不同的业务需求。
数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?
可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据
内存用完了会发生什么
根据淘汰策略,默认情况下直接报错
allkeys-lru会把最常访问的数据留在缓存
分布式锁如何实现
redis提供了setnx命令,只有在关键词不存在的情况下才设置值。
如何控制分布式锁有效时间
redisson
单纯依赖setnx不好控制,采用redisson实现,redisson默认开启看门狗,设置30秒超时,每隔10秒自动检测,增加到30秒,直到锁释放
或者直接指定超时时间,指定超时事件后看门狗默认失效
lua
if redis.call("setnx", KEYS[1], ARGV[1]) == 1 thenreturn redis.call("expire", KEYS[1], ARGV[2])
elsereturn 0
end
setnx 功能用于尝试获取锁,如果锁不存在(也就是还没有被其他客户端获取),那么设置锁并返回 1。如果锁已经存在,那么不做任何操作并返回 0。如果成功获取到锁,然后立即使用 expire 命令设置锁的有效时间。
redisson实现的分布式锁是可重入的吗?
在 Redisson 中,RLock 对象实现了 java.util.concurrent.locks.Lock 接口,因此它具有可重入的特性。这意味着同一个线程可以多次获取同一个锁,而不会被自己阻塞住。
每次当同一个线程再次获得同一把锁时,重入次数会增加;反过来,当释放锁时,重入次数则会减少。只有当重入次数减少到 0 时,该锁才真正被释放,其他线程才有机会获得该锁。
这种可重入的特性使得我们在使用 Redisson 的分布式锁时,可以像使用 Java 中的 synchronized 关键字或 ReentrantLock 锁一样,避免了死锁的发生,使得编程模型更为简洁和安全。
但是需要注意的是,虽然 RLock 支持可重入,但是在分布式环境中,由于网络等原因,可能会出现一些不可预见的情况。因此在实际使用过程中,可能还需要结合其他手段,如设置合理的锁超时时间,以防止死锁的发生。
参考
新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题)(https://www.bilibili.com/video/BV1yT411H7YK)