面试干货7——刁钻面试官:关于redis,你都了解什么?

面试高频问题

  • 推荐:在准备面试的同学可以看看这个系列
  • 一、问题1:
    • 1. 为什么要用redis?业务场景有哪些?
      • 追问1: 可以说说redis数据类型吗?
      • 追问2:说到命令,那你对redis的事务了解吗?
      • 追问3:如何防止数据丢失?对持久化有了解吗?
      • 深入追问:如果让你来选择一种持久化方式,你怎么选?
  • 二、问题2:
    • 1. redis为什么那么快呢?
      • 追问1:为什么redis要设计成单线程的?
      • 追问2:单机瓶颈,你是如何处理的?
  • 三、问题3:
    • 1. 什么是缓存穿透、缓存击穿与雪崩?
      • 追问1:你有遇到过上述情况吗?如何解决?


推荐:在准备面试的同学可以看看这个系列

        面试干货1——请你说说Java类的加载过程

        面试干货2——你对Java GC垃圾回收有了解吗?

        面试干货3——基于JDK1.8的HashMap(与Hashtable的区别、数据结构、内存泄漏…)

        面试干货4——你对Java类加载器(自定义类加载器)有了解吗?

        面试干货5——请详细说说JVM内存结构(堆、栈、常量池)

        面试干货6——输入网址按下回车后发生了什么?三次握手与四次挥手原来如此简单!

        面试干货7——刁钻面试官:关于redis,你都了解什么?

        面试干货8——面试官:可以聊聊有关数据库的优化吗?

        面试干货9——synchronized的底层原理


面试干货7——刁钻面试官:关于redis,你都了解什么?_第1张图片 面试干货7——刁钻面试官:关于redis,你都了解什么?_第2张图片

一、问题1:

1. 为什么要用redis?业务场景有哪些?

        使用Redis是为了缓解高并发、提升高可用,从而全面提升服务器性能的。在高并发环境下,我们所遇到的性能问题大多都在后台的数据处理环节,也就是数据库上,超大量的写操作,磁盘IO会无法承受,所以我们就需要在中间拦截一些请求处理掉,避免全部请求都打到数据库,而redis是基于内存的,读写操作要比MySQL的IO操作快得多的多,如此一来,不仅缓解了MySQL的高并发压力,又提升了系统整体性能。而redis作为非关系型数据库,其目的正是为了解决MySQL的瓶颈问题。
        简单总结: 因MySQL已经无法满足当下需求,很多高并发业务如秒杀扣除库存、热点词访问高峰等都会把MySQL打崩,所以引入缓存中间件。
应用场景:
        1、一些几乎不变的用户信息,可以放到Redis中,请求来了,先从缓存中拿,拿到就返回,拿不到再去数据库拿。
        2、计数器。redis的incr命令可以实现原子性的递增。具体可以应用于限制一个接口的请求频率、或秒杀活动、分布式序列号生成等
        3、另外还有一些热点词查询、验证码及限时活动等限时业务、队列等等

追问1: 可以说说redis数据类型吗?

        Redis常用的数据类型有五种,分别是String,Hash,List,Set,SortSet。它们都是基于键值的方式组织数据的。其提供的命令也比较丰富,我拿出几个常用的说一下
String常用命令

127.0.0.1:6379> set str 8
OK
127.0.0.1:6379> get str
"8"

        自加: incr

127.0.0.1:6379> incr str
(Integer) 9

        自减: decr

127.0.0.1:6379> decr str
(Integer) 8

        加: incrby

127.0.0.1:6379> incrby str 5
(Integer) 13

        减: decrby

127.0.0.1:6379> decrby str 3
(Integer) 10

Hash常用命令
        hset: 添加hash数据
        hget: 获取hash数据
        hmget: 获取多个hash数据

追问2:说到命令,那你对redis的事务了解吗?

        Redis事务的本质是一组命令的集合,相当于一个队列 ,一个事务中的所有命令都会被序列化,在事务执行过程中,按照顺序执行,且不会被中断,所以Redis的事务具有一致性(执行完当前事务,则事务结束,下次执行事务需要重新开启)、排他性(序列化后顺序执行,不允许中断),顺序性
        Redis是不存原子性的,在MySQL中,一个事务中的所有sql,要么都成功,要么都失败,这体现了原子性,但是Redis中,如果一个事务中的其中一条命令因非语法错误执行失败,是不会影响其他命令执行的。此外Redis的事务也不存在隔离性,因为在这里执行事务的时候是先将所有指令放入一个队列,然后统一序列化执行,所以不存在什么脏读、幻读不可重读的现象。在关于Redis事务的介绍一文中有详细说明。

追问3:如何防止数据丢失?对持久化有了解吗?

        持久化是Redis高可用中一个比较重要的环境,我了解到的持久化方式有两种
RDB (快照)
        RDB持久化机制是对redis进行周期性的备份,以数据快照的方式备份。比如5分钟备份一次,那么假如服务器宕机,我顶多丢失5分钟的数据。RDB原理 可以用两个词概括,fork、cow,fork即创建子进程做数据同步,cow即copy on write ,父子进程数据共享数据段,父进程依旧提供读写服务。
AOF (即时更新)
        AOF持久化机制是对redis进行即时性的备份。该机制会对每条数据的操作指令作为日志,然后追加到日志文件中,因为是追加的方式,所以不需要去寻址,比较快速。

优缺点
RDB:
        优点: 以快照的方式备份,每个时期保留一份数据快照,一旦出问题,我想恢复什么时候的数据就拿几分钟前的数据就好。而且此种备份方式一般对redis也没什么影响,因为他会fork一个子进程去进行持久化,且数据恢复时要比AOF快(因为AOF需要按照命令执行一遍才能恢复)
        缺点: 默认情况下会5分钟一次备份,所以会丢失5分钟的数据,且如果需要备份的文件较大,那么就会对redis有一定影响,小的停顿几毫秒,严重可能停顿1s,如果秒杀项目用遇到此种备份情况,那就出大问题了。

AOF:
        优点: 数据相对比较完整,只会丢失1s的数据。在对日志文件进行操作时以追加方式,无需寻址,写入非常迅速。且备份的日志文件具有较强可读性,我们可以打开日志文件看到里面的命令,如果你执行了误操作指令,那么你可以在备份里将误操作指令删除,然后进行数据恢复即可。
        缺点: 备份文件较大,数据恢复较慢,且相对于RDB持久化方式来说,AOF开启后会影响redis的写入的qps,因为每秒要去进行持久化…

面试干货7——刁钻面试官:关于redis,你都了解什么?_第3张图片 面试干货7——刁钻面试官:关于redis,你都了解什么?_第4张图片

深入追问:如果让你来选择一种持久化方式,你怎么选?

        当然是两者都要,如果单用RDB,那么可能会丢失很多数据,如果单用AOF,那数据恢复来得慢,所以二者都要,出问题时,先用RDB快速恢复数据,再用AOF补全,这样才能取长补短二者完美结合,系统更加健壮。

二、问题2:

1. redis为什么那么快呢?

        Redis是基于内存的、单进程单线程的KV数据库,由C语言编写,理想状态下,QPS能达到10W+,QPS即每秒查询次数,每秒写入次数能达到8W+

  • 完全基于内存,绝大部分请求都是纯粹的内存操作(持久化方面的除外),其数据以KV方式存储于内存,这种结构的数据,读写操作的时间复杂的都为O(1),所以速度非常快
  • 数据结构简单,不像MySQL各个字段都是有关系的,所以处理起来更节省时间
  • 采用单线程,无需关注线程上下文与竞争条件,没有线程切换而产生的CPU消耗,不用考虑假锁释放锁
  • 采用多路复用I/O模型,非阻塞IO

        其中,多路复用指的是多个请求复用一个线程,当多个连接都有请求时,复用器会轮询所有请求,挨个处理。

追问1:为什么redis要设计成单线程的?

        官方说的是,CPU并不是Redis的瓶颈,因为Redis是基于内存的,所以它的瓶颈是机器内存以及网络带宽,所以没必要做成多线程,而且单线程已经够快了,多线程需要考虑的问题也更多,也没有理由做成多线程了。

追问2:单机瓶颈,你是如何处理的?

        是的,单机肯定是会有瓶颈的,那么可以采用redis-cluster集群来横向拓展,主从同步,读写分离,且可以有多个主节点,每个主节点又可以挂多个从节点。这个集群模式可用于横向拓展,当然还有主从同步、哨兵,都可以了解下,如果面试官问你如何保证高可用,你可以从这几个集群深入浅出地吹牛逼。

面试干货7——刁钻面试官:关于redis,你都了解什么?_第5张图片 面试干货7——刁钻面试官:关于redis,你都了解什么?_第6张图片

三、问题3:

1. 什么是缓存穿透、缓存击穿与雪崩?

        缓存穿透:针对于缓存与数据库中都没有的数据 key对应的值在redis与数据源都不存在,那么大量请求拿这个key来查时,请求都会打在数据库,增大了数据库压力,导致缓存失去意义的情况,就叫缓存穿透

        缓存击穿: 指缓存中没有但数据库有的数据 高热度的某个key,每个时刻都会扛着大量请求,突然某一时刻失效了,那这些请求就像锥子一样,直接打在了数据库,瞬间增大了数据库压力,这叫击穿。击在一个点

        缓存雪崩: 指缓存同一时间大量失效,大量key在同一时间一起失效,或redis集群整体崩溃,导致大量请求直接打在数据库,称之为雪崩

追问1:你有遇到过上述情况吗?如何解决?

面试干货7——刁钻面试官:关于redis,你都了解什么?_第7张图片
  • key的值为null也缓存,简单粗暴,设置缓存失效时间一般比较短
  • 接口层增加key的校验,比如统一规范,不符合规范的直接过滤
  • 布隆过滤器,后续会写文章介绍,布隆过滤器是个加分项,原理还是比较容易理解的,多少看看就能让你吹上一天

2. 解决缓存击穿:

  • 设置key永不过期

  • 双重检测同步锁,缓存击穿后,多个线程会同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

// 如果能从缓存中获取,那么直接返回
Blog blog = (Blog) redisTemplate.opsForValue().get(id.toString());
// 如果获取不到,那么接下来的逻辑线程之间互斥
if (blog == null) {
    synchronized (this) {
    	// 再次从缓存获取
        blog = (Blog) redisTemplate.opsForValue().get(id.toString());
        if (blog == null) {
            log.info("缓存中没有,需要从数据库中读取");
            Blog blogTemp = blogMapper.selectByPrimaryKey(id);
            redisTemplate.opsForValue().set(id.toString(), blogTemp);
            log.info("已放入缓存,下次可直接从缓存获取");
            return ResultVO.success(blogTemp);
        }
    }
}

3. 解决缓存雪崩:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
  • 保证redis集群高可用

        本篇文章到这里就结束了,计划下篇文章写布隆过滤器以及有关redis其他的面试细节~

面试干货7——刁钻面试官:关于redis,你都了解什么?_第8张图片

你可能感兴趣的:(Java面试总结,redis,缓存,java,面试,高频面试)