Redis面试题
Redis的使用场景
- 根据自己简历上的业务回答
- 缓存 (缓存穿透、击穿、雪崩、双写一致、持久化、数据过期、淘汰策略)
- 分布式锁 (setnx、redisson)
- 注意:这里必须要写自己能回答得上来的,避免给自己挖坑
什么是缓存穿透,怎么解决?
缓存穿透:缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 数据库去查询,可能导致 数据库挂掉。这种情况大概率是遭到了攻击。
解决方案一:缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存
优点:实现简单
缺点:消耗内存
解决方案二:使用布隆过滤器
优点:内存占用少
缺点:实现复杂,有一定的误判几率
什么是缓存击穿,怎么解决?
缓存击穿:缓存击穿是指给某一个热点key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的的并发请求过来,这些并发请求可能会瞬间把数据库压垮
解决方案一:使用互斥锁:当缓存失效时,不立即去查询数据库,先使用如 Redis 的 setnx 去设置一个互斥锁,当获取锁成功时,再去查询数据库在重建缓存,其他没有获取到锁的线程就会重试获取缓存,当重建缓存完成之后,就会缓存命中
解决方案二:设置当前key的逻辑过期时间,大概思路如下:
①:在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间
②:当查询的时候,从redis取出数据后判断时间是否过期
③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新
什么是缓存雪崩,怎么解决?
缓存雪崩:设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到数据库,D数据库瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。
解决方案:
主要是可以将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
这里要看自己做项目的业务情况而定
方案一:追求数据的强一致性,可以采用redisson实现的分布式读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥,在更 新的时候添加排他锁,它是读读,读写都互斥的,这样就能保证在写数据的同时是不会让其他线程读数据的,避免脏数据
排它锁底层使用setnx来保证读读,读写互斥的,使用的是lua脚本来保证操作的原子性
延迟双删是指在更新数据的时候,先删除缓存中的数据,然后更新数据库,更新数据库完成之后需要延迟一定的时间,然后再删除缓存,这样可以降低数据不一致的情况的概率,但是不能保证数据的强一致性,因为那个延迟时间不好控制
优点:可以保证数据的强一致性
缺点:但是性能底
方案二:不追求数据的强一致性,可以允许数据有一定的延迟,异步更新redis的数据,我这里想到的有两个方式,一种是可以使用mq,在更新数据的时候,发送一条消息给mq,mq异步更新redis的数据,但是这里需要保证mq的可靠性,第二种就是采用的阿里的canal组件实现数据同步:不需要更改业务代码,部署一个canal服务。canal服务把自己伪装成mysql的一个从节点,当mysql数据更新以后,canal会读取binlog数据,然后在通过canal的客户端获取到数据,更新缓存即可。
Redis是怎么做的持久化
在redis中提供两种持久化方式:分别是rdb和aof
RDB:全称Redis Database Backup file(Redis数据备份文件),也叫做Redis数据快照,简单来说就是将内存的数据保存到磁盘,当redis出现故障,重启的时候就会读取rdb文件进行数据恢复
AOF:全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
RDB和AOF,哪种恢复的比较快呢?
RDB恢复较快,RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令
RDB的bgsave执行原理?
bgsave开始时会fork主进程得到子进程,子进程会复制主进程的页表,所以子进程共享主进程的内存数据,完成fork后读取内存数据并写入rdb文件,fork采用的是copy-on-write技术,即当主进程执行读操作时,访问共享内存;当主进程执行写操作时,则会拷贝一份数据,执行写操作
AOF记录命令的频率
RDB和AOF对比
Redis的过期删除策略有哪些
redis中有两种过期删除策略,一种是惰性删除,一种是定时删除
惰性删除:是在访问该key的时候判断过期时间,如果已经过期则删除该key,如果没有过期则返回
定时删除:分为两种SLOW和FAST两种模式
SLOW:是定时任务,执行的频率默认是10HZ,每次执行时间不会超过25毫秒,这个值可以通过配置文件的hz参数进行配置
FAST:执行频率不固定,每次执行间隔不会超过2ms,每次执行时间不会超过1ms
Redis的数据淘汰策略有哪些 ?
redis中数据淘汰策略有8种,默认的是noeviction,不会删除任何数据,内存不足会报错,redis中数据淘汰策略有两个重要的概念,分别是LRU和LFU
LRU:最近最少使用,用当前时间减去最后一次使用的时间,数值越大,淘汰的优先级就越高
LFU:最少频率使用,会统计每个key的访问频率,值越小淘汰优先级越高
Redis分布式锁如何实现 ?
redis采用setnx命令来实现分布式锁,使用lua脚本来保证操作的原子性
因为redis本身是单线程的,使用了setnx命令只能有一个线程能给key设置值成功,成功的线程获取锁,失败的线程获取锁失败
如何控制Redis实现分布式锁有效时长呢?
这个本身是不好控制的,因为不好确定业务的执行时间,所以我们采用的是redis的一个框架redisson来实现的
redisson在获取锁的时候,可以指定锁的有效时长,也可以不指定,如果不指定的话,redisson底层会引入一个看门口机制,实际就是开一个线程,来不断重置锁的有效时长,默认有效时长是30秒,每10秒重置一次,直到业务执行完,释放锁为止
使用redisson实现的分布式锁还有一个好处,就是在高并发的情况下,如果线程获取不到锁,并不会立即失败,redisson底层会自旋重试,直到超过重试的最大时间,一般业务执行时间就在几毫秒,这样就能大大提高锁的效率
redisson实现的分布式锁是可重入的吗?
是的,redisson实现的锁是可以重入的,这样做是为了避免死锁的产生。重入锁其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数,当出现了锁重入value值就加一,当释放了一个锁value就减一,知道value值为0 就释放锁
redisson实现的分布式锁能解决主从一致性的问题吗
是可以解决的,主从一致性问题就是主节点和从节点的数据不一致导致的,redisson解决主从一致性问题,是用红锁来解决的,红锁是对多个redis实例加锁,对多个redis实例同时获取锁,超过(n/2)+1一个实例获取锁成功,也就是超过redis实例数一半以上获取锁成功,才能获取到锁,但是这样做降低了锁的效率,提高了维护的成本,所以一般不会建议使用红锁
如果业务非要保证数据的强一致性,这个该怎么解决呢?
redis本身追求的就是高可用的,如果非要保证数据的强一致性,必然会影响性能,如果业务非要保证数据的强一致性,可以采用Zookeeper来实现分布式锁,它是可以保证数据的强一致性的
Redis集群有哪些方案
主从集群、哨兵集群、分片集群
什么是主从同步
单节点redis的处理速度是有上限的,要进步提升redis的处理速度,可以搭建主从集群,主节点负责写操作,从节点负责读操作,一般都是一主多重,在主节点写数据之后,需要将数据同步到从节点,保证数据的一致性,这个过程就叫做主从同步
主从同步数据的流程
主从同步分为两种模式,一种是全量同步一种是增量同步,从节点第一次请求主节点同步数据是全量同步,从节点重启之后请求同步数据是增量同步
全量同步流程:
1、从节点请求主节点同步数据,从节点会携带自己的replication id和offset偏移量
2、主节点根据replication id判断是不是第一次请求,如果replication id不相同,则是第一次同步,主节点会将replication id和 offset发送给从节点,让主节点和从节点信息保持一致
3、同时主节点还会执行bgsave命令,生成rdb文件,将rdb文件发送给从节点执行,从节点先把数据清空,然后执行主节点发送过来的rdb 文件,这样就保持了一致
4、主节点在进行数据同步的时候,依然会接受客户端的写请求,主节点会将请求以命令的方式放入缓存区,其实这个缓冲区就是一个日志文件,最后把这个日志文件发送给主节点,这样就能保证主节点和从节点的数据完全一致
增量同步:
当从节点重启之后,从节点会向主节点发送数据同步的请求,会将replication id和offset发送给主节点,主节点收到时候根据replication id判断是否是增量同步,如果是增量同步,就会同步offset相差部分的数据
怎么保证Redis的高并发高可用
可以搭建redis的主从集群和哨兵模式,哨兵模式可以实现对主从集群的自动故障恢复,如果检测到master出现故障,sentinel会将一个slave选举为新的master节点,当旧的master节点恢复以后,会以新的master节点会主节点,自己变为从节点,并且sentinel会将主从集群的最新信息推送给客户端,这样客户端就能对主从节点进行切换,所以一般情况下会使用哨兵模式来保证主从集群的高可用
redis集群脑裂,该怎么解决呢?
redis集群脑裂问题是指master和sentinel、slave节点网络连接出现故障,sentinel认为master出现故障,会选举新master作为主节点,但是客户端的写请求还是会发送到旧的master节点,一旦旧的master节点和sentinel网络连接恢复正常,sentinel就会向旧的master节点作为新的master节点的从节点,这样会导致旧的master节点的数据丢失
解决方案:
在redis配置文件中可以配置:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能写入数据,第二个可以设置主节点与从节点通信的ACK消息延迟时间,达不到要求主节点就拒绝写入,就可以避免大量的数据丢失
redis的分片集群有什么作用
redis的分片集群主要解决的是海量数据的存储,集群中有多个master,每个master保存不同的数据,每个master也会有多个slave节点可以做主从集群,增加redis的高并发能力,每个master之前会通过ping进行心跳检测,就类似于sentinel,当客户端的请求到来之后,会根据计算的hash槽值路由到正确的节点
Redis分片集群中数据是怎么存储和读取的?
redis分片集群引入了hash槽的概念,有16384个hash槽,集群为每一个节点保存一定范围的hash槽,key通过CRC16校验后对16384取余来决定放入哪个槽,通过槽找到对应的节点来进行存储和读取
Redis是单线程的,但是为什么还那么快?
1、因为redis是基于内存存储的,,C语言编写
2、redis是单线程,避免了多线程之间的上下文的切换,不用担心并发安全问题
3、使用的是多路复用模型,非阻塞IO模型
解释一下Redis的IO多路复用模型?
IO多路复用模型是指使用单个线程来监听多个Socket事件,并在某个socket可读、可写时得到通知,从而避免无效的等待,充分利用cpu的资源。目前的io多路复用都是采用epoll模式来实现的,它会在通知用户线程Socket就绪的同时,会将已经就绪的Socket写入用户空间,不需要挨个遍历每个socket来判断是否已经就绪,提升了性能
其实Redis网络模型就是使用I/O多路复用加事件处理器来应对多个Socket请求,比如连接应答处理器、命令回复处理器、命令请求处理器
在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程