一.面试准备
二.Redis缓存
2.1 缓存穿透
缓存穿透:指的是 当在数据库里查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库(DB),可能会导致DB挂掉
解决方案一:缓存空数据,查询返回的数据为空,仍然把这个空结果进行缓存
比如:查询一个id为1的数据,它的value为null,此时依然把null缓存。
这种解决方法的优点是很简单就可以实现,但当你有大量的value都是null,缓存的压力就会很大,而且有可能会发生数据不一致的问题。
解决方案二:布隆过滤器
布隆过滤器:主要用于检索一个元素是否存在一个集合中,它的底层主要是先去初始化一个比较大的数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后,经过三次hash计算,根据数组长度找到数据的下标然后将数组原来的0改成1,这样的话,三个数组的位置就能标明一个key的存在.查找的过程也是一样的。
但是会有误判问题出现,可以设置这个误判,让它大概不会超过5%
优点:内存占用小,没有多余key
缺点:实现复杂,存在误判
2.2 缓存击穿
缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量并发请求过来,这些并发的请求可能会瞬间把DB压垮
解决方案一:互斥锁
当缓存失效时,不立刻去load db,先使用Redis的setnx去设置一个互斥锁,当操作成功返回时再进行load db的操作并回设缓存,否则重试get缓存的方法。
特点:强一致,性能差
解决方案二:逻辑过期
- 在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间.
- 当查询的时候,从redis中取出数据后判断时间是否过期
- 如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,但这个数据不是最新数据.
特点:高可用,性能优,但是数据不能保证绝对一致
2.3 缓存雪崩
缓存雪崩:指的是同一时间段内大量的缓存key同时失效或者Redis服务宕机 ,导致大量请求到达数据库,带来巨大压力.(Key同时失效主要原因因为大量的key设置了相同的过期时间 )
解决方案一:增加随机失效时间
解决方案二:利用Redis集群提高服务的可用性(解决Redis宕机 哨兵模式,集群模式)
解决方案三:给缓存业务添加降级限流策略(ngxin或Spring cloud gateway)
**解决方案四:给业务添加多级缓存(Guava caffeine) **
2.4 缓存-双写一致性
双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存的数据库和数据要保持一致.
延迟双删不可以解决同步问题(有脏读风险)
面试官:redis作为缓存,mysql的数据如何与redis进行同步呢?
答:一定要设置前提,先介绍自己的业务背景,如果业务的一致性要求高,是一种方案,如果业务允许延迟一致,则是另一种方案
解决方案一:业务要求强一致性:采用redisson提供的读写锁
共享锁:读锁readLock,加锁之后,其他线程可共享读操作,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。
排他锁:独占锁writeLock,加锁之后,阻塞其他线程读写操作,底层使用setnx,保证了同时只能有一个线程操作的方法。
解决方案二:业务允许延迟一致:利用canal中间件
利用canal中间件,不需要修改业务代码,伪装mysql的一个节点,canal通过读取binlog数据更新缓存。
2.5 持久化
Redis作为缓存,数据持久化方式分为RDB和AOF
RDB也被叫做Redis数据快照。把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF在Redis中是默认关闭的,需要修改redis.conf配置文件来开启AOF:
AOF的命令记录的频率也可以通过redis.conf文件来配:
RDB执行原理:
RDB与AOF对比
2.6 数据过期策略
面试官:假如Redis的key过期之后,会立刻删除吗?
答:Redis设置数据的有效时间,数据过期之后,就需要将数据从内存中删掉,可以按照不同的规则进行删除,这种删除规则就被称为数据的删除策略即数据的过期策略
分为惰性删除和定期删除
惰性删除:设置该key过期时间后,不对该key进行任何操作,当需要该key时,检查它是否过期,如果过期,则删掉,如果未过期,返回该key.
优点:对CPU友好,使用该key的时候才会检查,对于很多用不到的key不用浪费时间进行检查
缺点:对内存不友好,如果一个key很久没有使用,就会一直保存在内存里,内存永远不会被释放。
定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key.
定期清理的两种模式: SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的 hz 选项来调整这个次数
FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms
优点:可以通过限制删除操作执行的时长和频率来减少删除操作对cpu的影响.另外定期删除,也能释放过期key占用的内存
缺点:难以确定删除操作的执行时长和频率
Redis的过期删除策略:惰性删除+定期删除两种策略进行配合使用。
2.7 数据淘汰策略
问:假如缓存过多,内存是有限的,内存被占满了怎么办?(Redis的数据淘汰策略)
当Redis中的内存不够用时,此时再向Redis中添加新的key,那么Redis就会按照某种规则将内存中的数据删掉,这种数据的删除规则被称为数据的淘汰策略
Redis支持8种不同的淘汰策略:
LRU和LFU较为主要,面试中需要描述出来.
数据淘汰策略建议:
- 优先使用allkeys-lru,把最近最常访问的数据留在缓存中。如果业务有明显冷热数据区分,建议使用
- 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用allkeys-random,对全体key进行随机淘汰
- 如果业务中有置顶的需求,可以使用volatile-lru策略,对需要置顶的数据不设置TTL,这些数据就一直不会被删除,只会淘汰其他设置过期时间的数据
- 如果业务中有短时高频访问的数据,使用allkeys-lfu或者volatile-lfu策略
关于数据淘汰策略其他的面试题
问:数据库中有1000万数据,Redis只能缓存20万数据,如何保证Redis中的数据都是热点数据
答:数据淘汰策略使用allkeys-lru策略,挑选最近最少使用的数据进行淘汰,剩下的都是热点数据
问:Redis中的内存用完了会发生什么?
答:主要看给Redis设置的什么数据淘汰策略,如果数据淘汰策略是默认的noeviction,那么会报错
三.Redis分布式锁
3.1 Redis分布式锁的使用场景
集群情况下的定时任务、抢单、幂等性
抢券场景实现:
但是这种方法是存在问题的;如果恰巧此时库存就剩一个了,此时两个线程同时读取库存,线程1读取到之后,进行扣减库存,此时库存已经为0,但线程2之前查询到库存不为0,线程2也会进行扣减,此时库存为-1,这种现象叫超卖
可以使用分布式锁来解决这个问题
3.2 Redis分布式锁的实现原理
面试官:redis分布式锁,是如何实现的?
先按照自己简历上的业务进行描述分布式锁使用的场景
当时使用的是redisson实现的分布式锁,底层是setnx和lua脚本 (lua脚本保证原子性)
面试官:Redisson实现分布式锁如何合理控制锁的有效时长
在redisson的分布式锁重,提供了一个WatchDog(看门狗),一个线程获取锁成功后,WatchDog会给持有锁的线程续期(默认是每隔10秒续期一次)
面试官:Redisson实现的锁,可以重入吗?
可以重入,多个锁重入需要判断是否是当前线程,在redis中进行存储的时候使用的hash结构,来存储线程信息和重入次数.
面试官:Redisson锁能解决主从数据一致问题吗?
不能解决,但是可以使用redisson提供的红锁来解决,但是一旦使用红锁,性能会大大下降,如果业务中非要保持数据的强一致性,建议使用zookeeper实现的分布式锁.
3.3 Redis集群方案
Redis提供的集群方案总共有三种
- 主从复制
- 哨兵模式
- 分片集群
主从复制(主从同步)
问:介绍一下redis的主从同步
答:单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就要搭建主从集群,实现读写分离.一般都是一主多从,主节点负责写数据,从节点负责读数据.
问:主从同步的流程
答:
Redis集群的哨兵模式
哨兵模式
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵结构和作用如下:
哨兵是如何实现监控服务状态呢?
Sentinel基于心跳机制监测服务状态,每隔一秒向集群的每个实例发送ping命令:
- 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
- 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好设置成超过Sentinel实例数量的一半。
哨兵选主规则:
- 首先判断主与从节点断开时间长短,如超过指定值(即从节点和主节点断开时间过长)就不选择该从节点为主节点,因为断开时间太长丢失数据太多
- 然后判断从节点的slave-priority值,越小优先级越高
- 如果slave-prority一样,则判断slave节点的offset值,越大优先级越高
- 最后判断slave节点的运行id大小,越小优先级越高.
面试题:
问:怎么保证Redis的高并发高可用
详细回答:首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式 可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动 故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。 当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的 服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用
问:你们使用redis是单点还是集群,哪种集群?
答:我们当时使用的是主从(1主1从)加哨兵。一般单节点不超 过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节 点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务
集群脑裂
集群脑裂:由于redis master节点和redis salve节点和sentinel处于不同的网络分区(或者直接说主节点挂了),使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve master,这样就存在了两个master,就像大脑分裂了一样
解决办法:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失
问:redis集群脑裂,该怎么解决呢?
答:有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redismaster节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没 有能够心跳感知到master,所以通过选举的方式提升了一个salve为master, 这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在 old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会 将old master降为salve,这时再从新master同步数据,这会导致old master中 的大量数据丢失。 关于解决的话,我记得在redis的配置中可以设置:第一可以设置最少的salve 节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主 从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失
分片集群
分片集群的作用
分片集群主要解决的是,海量数据存储的问题,集群中有多个 master,每个master保存不同数据,并且还可以给每个master设置多个slave 节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点
分片集群的读写
Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放 置哪个槽,通过槽找到对应的节点进行存储。取值的逻辑是一样的
3.4 redis是单线程,但为什么那么快
- 完全基于内存的,C语言编写
- 采用单线程,避免不必要的上下文切换可竞争条件
- 使用多路I/O复用模型,非阻塞IO
例如:bgsave 和 bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞