二、Redis相关面试题

 1、支持哪几种数据类型


​        支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)zset(sorted set:有序集合)

 2、为什么读写速度很快


​              redis完全基于内存

​              数据结构简单

​              采用单线程,避免了加锁、释放锁、死锁、线程间切换等消耗

​              使用多路I/O复用模型,非阻塞IO

 3、在你们项目有哪些应用场景


计数器:对 string 进行自增自减运算,从而实现计数器功能。redis 内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。如每日登录次数计数。

热点数据缓存:将热点数据放到内存中。如首页排行榜数据,具有很大访问频次,使用zset可以实现基于score分数排序;。

会话缓存:用redis统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。

取数据交集、并集:基于redis  set 的特性可以对共同好友进行很方便的查询。

分布式事务锁的使用:基于set lock requestId nx ex time 模式可以很方便编写分布式事务锁


 4、基于什么协议


 Redis 的通信协议是 Redis Serialization Protocol,翻译为 Redis 序列化协议,简称 RESP

​        在 TCP 层

​        是二进制安全的

​        基于请求 - 响应模式

​        简单、易懂(人都可以看懂)

 5、线程模型是怎样的?


​    Redis 的线程模型:基于非阻塞的IO多路复用机制的线程模型,单线程

​    Redis 是基于 reactor 模式开发了网络事件处理器,这个处理器叫做文件事件处理器(file event handler)。由于这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。采用 IO 多路复用机制同时监听多个 Socket,根据 socket 上的事件来选择对应的事件处理器处理这个事件。模型如下图:

  

上图得知,文件事件处理器的结构包含了四个部分:

多个 Socket:客户端发起多个 socket,每个socket 会产生不同的事件,不同的事件对应着不同的操作

IO 多路复用程序:IO 多路复用程序监听着这些 Socket,当这些 Socket 产生了事件,IO 多路复用程序会将这些事件放到一个队列中。

文件事件分派器:通过队列,将事件以有序、同步、每次一个事件的方式向文件事件分派器中传送,文件事件分派器将事件按类型分派给不同的事件处理器进行处理。

事件处理器:分为连接应答处理器、命令请求处理器、命令回复处理器,每个处理器对应不同的 socket 事件。

 6、持久化机制是怎样的?


​        第一种:RDB,即 Redis 的内存快照,默认持久化机制,它是在某一个时间点将 Redis 的内存数据全量写入一个临时文件,当写入完成后,用该临时文件替换上一次持久化生成的文件,这样就完成了一次持久化过程,默认的文件名为dump.rdb。

​          1)、触发RDB机制:

​                (1)、save触发方式:该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。具体流程如下:

​                                

​                (2)、bgsave触发方式:执行该命令时,Redis会在后台异步fork出一个子进程进行快照操作,快照同时还可以响应客户端请求。具体流程如下:

​                            

​                 (3)、自动触发自动触发是由我们的配置文件来完成的。在redis.conf配置文件中,里面有如下配置,我们可以去设置:

​                             save配置 这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。

​           2)、优点:

​                      >由于 RDB 文件是一个非常紧凑的二进制文件,所以加载的速度会快于 AOF 方式;

​                      >fork 子进程方式,除了fork线程阶段,其他时候不会阻塞;

​                      >RDB 文件代表着 Redis 服务器的某一个时刻的全量数据,所以它非常适合做冷备份和全量复制的场景;

​           3)、缺点:

​                      >没办法做到实时持久化,会存在丢数据的风险。定时执行持久化过程,如果在这个过程中服务器崩溃了,则会导致这段时间的数据全部丢失。

​                

​        第二种:AOF,即 append only file,它是将每一行对 Redis 数据进行修改的命令以独立日志的方式存储起来。由于 Redis 是将“操作 + 数据” 以格式化的方式保存在日志文件中,他代表了这段时间所有对 Redis 数据的的操作过程,所以在数据恢复时,我们可以直接 replay 该日志文件,即可还原所有操作过程,达到恢复数据的目的。它的主要目的是解决了数据持久化的实时性

​         注意:AOF 默认关闭,需要在配置文件 redis.conf 中开启,appendonly yes。

​           1)、AOF 总共分为三个流程:

​                   (1)、命令写入:将命令写入缓冲区

​                   (2)、文件同步:命令写入到缓冲区,然后根据不同的策略刷到硬盘中。Redis 提供提供了三种不同的同步策略

​                   (3)、文件重写:随着命令的不断写入,AOF 文件会越来越庞大,直接的影响就是导致“数据恢复”时间延长,而且有些历史的操作是可以废弃的(比如超时、del等等),为了解决这些问题,Redis 提供了 “文件重写”功能,该功能有手动和自动两种方式触发。

​                      重写AOF主要做了以下事情:    

​                            1、已过期的数据不在写入文件。            

​                            2、保留最终命令。例如 set key1 value1 、set key1 value2、....set key1 valuen,类似于这样的命令,只需要保留最后一个即可。

​                            3、删除无用的命令。例如 set key1 valuel;del key1,这样的命令也是可以不用写入文件中的。

​                            4、多条命令合并成一条命令。例如 lpush list a、lpush list b、lpush list c,可以转化为 lpush list a b c                    

​           2)、优点

​                     > 相比于 RDB,AOF 更加安全,默认同步策略为 everysec 即每秒同步一次,所以顶多我们就失去一秒的数据;

​                     > 根据关注点不同,AOF 提供了不同的同步策略,我们可以根据自己的需求来选择;

​                     > AOF 文件是以 append-only 方式写入,相比如 RDB 全量写入的方式,它没有任何磁盘寻址的开销,写入性能非常高

​            3)、缺点

​                    > 由于 AOF 日志文件是命令级别的,所以相比于 RDB 紧致的二进制文件而言它的加载速度会慢些。

​                    > AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低。

​        第三种:RDB-AOF 混合模式(鱼和熊掌可兼得的方案)

​            通过上面的介绍我们知道了 RDB 和 AOF 各有自己的优缺点,选择任意其一都需要接受他的缺点:

​                  RDB 能够快速地存储和恢复数据,但是在服务器宕机时会丢失大量的数据没有保证数据的实时性安全性

​                   AOF 能够实时持久化数据并且提高了数据的安全性,但是在存储和恢复数据方面又会消耗大量时间

​            Redis 4.0 推出了 RDB-AOF 混合持久化方案,该方案是在 AOF 重写阶段创建一个同时包含 RDB 数据 AOF 数据AOF 文件,其中 RDB 数据位于AOF 文件的开头,他存储了服务器开始执行重写操作时 Redis 服务器的数据状态(RDB 快照方案),重写操作执行之后的 Redis 命令,则会继续 append 在 AOF 文件末尾,一般这部分数据都会比较小。这样在 Redis 重启的时候,则可以先加载 RDB 的内容,然后再加载 AOF 的日志内容,这样重启的效率则会得到很大的提升,而且由于在运行阶段 Redis 命令都会以 append 的方式写入 AOF 文件,保证了数据的实时性和安全性。


 7、慢查询如何排查?


​    Redis 执行命令分为四个步骤:发送命令、命令排队、执行命令、返回结果。慢查询只关注步骤 3执行命令 的时间,所以没有慢查询并不代表客户端没有超时问题

​    Redis 慢查询可通过配置两个参数进行:

slowlog-log-slower-than:设置慢查询预设的超时阈值,单位是微秒

slowlog-max-len:表示慢查询日志存储的条数
Redis 中有两种修改配置的方法,一种是修改配置文件,另一种是使用 config set 命令动态修改:


​    slowlog-log-slower-than:默认是 1000 微秒,QPS太小,实际生产建议把这个参数调的更小一些

​            它表示的是慢查询预设的超时阈值。它所阐述的意思是如果某条命令(如 keys *) 执行”很慢“,执行时间超过了设置的阈值,那么这条命令将会被记录到慢查询日志中。

​             若设置 slowlog-log-slower-than = 0,则会记录所有命令

​             若设置 slowlog-log-slower-than < 0,则不会记录任何命令

​    slowlog-max-len:实际生产中这个参数可以设置得大一些,如1000以上,可以减缓慢查询被剔除的可能

​            Redis 会使用一个列表来存储慢查询日志,slowlog-max-len 就是该列表的最大长度。一个命令如果满足慢查询阈值条件则会加入到该列表来,但是如果该列表已经

​            处于最大长度时,那么会删除最开始的一条记录,然后将最新的命令插入到末尾,所以慢查询日志列表是一个有限的先进先出列表

​    通过slowlog get [n]命令获取慢查询日志:

​    

返回的慢查询日志由 4 个属性组成:1、日志的标识 id     2、发生的时间戳     3、命令耗时     4、执行的命令和参数


用户发起请求

redis   

mysql   ->  redis

 8、缓存穿透?缓存击穿?缓存雪崩?


缓存穿透:是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大

​                

​           解决方案:

​                        1)、从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击;

​                        2)、引入布隆过滤器,过滤一些异常的请求。

​                        3)、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;


缓存击穿:是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

​                解决方案:

​                          1)、设置热点数据不过期;

​                          2)、第一时间去数据库获取数据填充到redis中,但是这个过程需要加锁,防止所有线程都去读取数据库,一旦有一个线程去数据库获取数据了,其他线程取锁失败后可设置一个合理睡眠时间之后再去尝试去redis中获取数据;

public class SingnObjectUtil{
    private Student s;

    public Student getStudent1(){
       if(s != null){
           return s;
       }
       return getStudent2();
    }

    private synchronized Student getStudent2(){
       if(s != null){
            return s;
       }
       s = new Student;
       return s;
}

缓存雪崩:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是大批量数据都过期了,大量数据都从redis中查不到,从而查数据库。

​                

​        解决方案:

​            1)、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

​            2)、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的数据库中。

​            3)、允许的话,设置热点数据永远不过期。    

​            4)、要保证redis的高可用,可以使用主从+哨兵或redis cluster,避免服务器不可用;

​            5)、使用redis的持久化RDB+AOF组合策略,防止缓存丢失并且可以快速恢复数据;


 9、相比memcached有哪些区别?


​        redis支持丰富数据类型,支持字符串、链表、哈希、集合和有序集合,Memcache对数据类型支持相对简单,只支持字符串

​        Redis将数据存在内存和硬盘上,这样能保证数据的持久性,Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小

​        redis支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

​        

 10、键过期删除如何实现的?


redis中可以设置键的过期时间,到期后自动进行删除,那么redis中是怎么实现过期删除的?

定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量

惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存

定期过期:每隔一定的时间,会扫描一定数量数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果


 11、分布式事务锁怎么实现的?会有什么问题?


 通过setnx上锁方式实现,但是不注意写法很可能会出现很多问题;

​        错误用法:先通过setnx上锁,再通过expire设置过期时间,最后执行完任务后手动del释放锁;

​        场景一问题(死锁):通过setnx上锁后出现异常,导致无法去expire设置锁的过期时间,更无法最后去手动释放锁,造成死锁

​        解决:使用上锁最新写法,保证上锁、设置过期时间一步完成的原子性:          set(lockKey,value,nx,ex,exporeTime);

​        场景二问题(误删锁):A机器上锁并设置过期时间完成以后后,系统出现了阻塞,导致锁到了过期时间并自动删除了,这时还没有执行手动释放锁的操作,这个时候B机器上锁成功,并去执行任务,任务还未执行完,A机器反应过来了,继续执行了手动释放锁的操作,把B机器上的锁给误删了

​        解决:上锁同时加上一个锁id,如当前线程ID,将锁id存入value值并记录在变量中,手动释放锁的时候比较一下value中的锁id跟变量中id是否一致,也就是判断一下是否自己还在持有锁,如果不是,就不执行删除操作了。

​        场景三问题(误删锁):这种误删锁是基于场景2判断锁id释放锁操作这两步没有保证原子性所导致的。

​         具体为:A机器带锁id方式取锁、设置过期时间并执行完任务后,希望通过判断比较锁id之后去释放锁,判断通过后系统出现阻塞,阻塞到锁也到了过期时间自动释放了锁,这时还未进行手动释放锁操作,这个时候B机器上锁成功,并去执行任务,任务还未执行完A机器反应过来了,继续执行了手动释放锁的操作,把B机器上的锁给误删了

​        解决:保证判断锁和释放锁的原子性:使用redis执行LUA脚本,保证一步执行判断锁和释放锁
        String luaScript = 'if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end';

​        redisClient.eval(luaScript , Collections.singletonList(key),Collections.singletonList(threadId));

​        场景四问题(锁续命):这种场景是指线程中任务还没执行完,锁就已经到过期时间,这种情况可以给任务执行线程添加守护线程,守护线程负责对锁的expire时间进行监控,每当到过期前一秒就对过期进行判断,如果任务还在进行且锁马上过期,则对过期时间重新进行设置。

​        综上:正确使用redis分布式事务锁需要保证两个原子性:

​                        1、上锁和设置过期时间需要保证原子性;

​                        2、 判断锁ID是否为自己所有和解锁需要保证原子性;
       补充:其实关于上述四个场景的问题,使用redis客户端Redisson都能够得到很好的解决,redisson内部已经实现了上述几点问题的解决机制,原理同上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值