Redis 面试题总结

redis 简介

简单来说redis就是一个数据库,不过与传统数据库不同的是redis的数据是存在内存的,所以读写速度非常快,因此redis被广泛应用于缓存方向。另外,redis也经常用来做分布式锁。redis提供了多种数据类型来支持不同的业务场景。除此之外,redis支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案。

为啥Redis那么快?

Redis采用的是基于内存的单进程单线程模型的KV数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS。

  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快;
  • 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
  • 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,也不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  • 使用多路I/O复用模型,非阻塞IO;
  • 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

为什么要用 redis 而不用 map/guava 做缓存?

缓存分为本地缓存和分布式缓存。以Java为例,使用自带的map或者guava实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着jvm的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

使用redis或memcached之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持redis或memcached服务的高可用,整个程序架构上较为复杂。

redis和memcached的区别

  1. redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcached支持简单的数据类型,String。
  2. Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用,而memcached把数据全部存在内存之中。
  3. 集群模式:memcached没有原生的集群模式,需要依赖客户端来实现往集群中分片写入数据;而redis目前是原生支持cluster模式的。
  4. memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路I/O复用模型,非阻塞IO。
  5. memcached的一些限制:
    • key不能超过250个字节;
    • value不能超过1M字节;
    • key的最大失效时间是30天;
    • 只支持K-V结构,不提供持久化和主从同步功能。
对比项RedisMemcached
类型1. 支持内存 2. 非关系型数据库1. 支持内存 2.key-value键值对形式 3. 缓存系统
数据存储类型String,List,Set,Hash,Sort Set(俗称ZSet)1. 文本型 2. 二进制类型
查询(操作)类型1. 批量操作 2. 支持事务 3. 每个类型不同的CRUD1. CRUD 2. 少量的其他命令
附加功能1. 发布/订阅模式 2. 主从分区 3. 序列化支持 4. 支持脚本(Lua)1. 多线程服务支持
网络IO模型1. 单进程模式1. 多线程、非阻塞IO模式
事件库自封装简易事件库AeEvent贵族血统的LibEvent事件库
持久化支持1. RDB 2. AOF不支持

能说一下Redis数据类型以及特性和使用场景吗?

1. String: 最简单的类型,就是做简单的KV缓存。
使用场景:

  • 缓存功能: String字符串是最常用的数据类型,不仅仅是Redis,各个语言都是最基本类型,因此,利用redis作为缓存,配合其他数据库作为存储层,利用redis高并发的特点,可以大大加快系统的读写速度,以及降低后端数据库的压力。
  • 计数器: 许多系统都会使用功能redis作为系统的定时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其他存储介质当中进行永久保存。
  • 共享用户Session: 用户刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用redis将用户的session集中管理,在这种模式只需要保证Redis高可用,每次用户Session的更新和获取都可以快速完成,大大提高效率。

2. Hash: 这个是类似Map的一种结构,这个一般就是可以将结构化的数据,比如一个对象给存在缓存里,然后每次读写缓存的时候,就可以操作Hash里的某个字段。
使用场景: 场景单一了一些,比如会员推送的相关数据可以使用哈希,如版本号,手机型号,通知开关等等。

3. List: 有序列表,可以玩出很多花样。
使用场景: 比如可以通过list存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。

比如可以通过lrange命令,读取某个闭区间内的元素,可以基于List实现分页查询,这个是很棒的一个功能,基于Redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。

比如可以搞个简单的消息队列,从List头部入列,从尾部出列,如果有多个数据消费者,可以使用BRpop命令阻塞的抢列表尾部的数据。

4. Set: set是无序集合,会自动去重。
使用场景: 可以基于Set玩交集、并集、差集的操作,比如交集,可以把两个人的好友列表整一个交集,看两人的共同好友是谁。

5. Sorted Set: Sorted Set是排序的set,去重并且可以排序,写进去的时候给一个分数,自动根据分数排序。
使用场景: 有序集合的使用场景与集合类似,但set集合不是自动有序的,而sorted set可以利用分数进行成员间的排序,而且是插入时就排序好。所以当需要一个有序且不重复的集合列表时,就可以选择Sorted Set数据结构作为选择方案。

  • 排行榜。有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
  • 用Sorted Set来做带权重的队列,比如普通消息的score位1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务先执行。

如何解决缓存与数据库双写的数据一致性问题?

一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果系统不是严格要求“缓存+数据库”必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去

串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

把一系列的操作都放到队列里面,顺序肯定不会乱,但是并发高了,队列很容易阻塞,反而会成为整个系统的弱点,瓶颈
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkriCh2d-1595921095196)(en-resource://database/1231:1)]

你了解最经典的KV、DB读写模式吗?

最经典的缓存+数据库读写模式,就是 Cache Aside Pattern

  • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先删除缓存,然后再更新数据库。

为什么是删除缓存,而不是更新缓存?

原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。

比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值。

另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新,但是问题在于,这个缓存到底会不会被频繁访问到?

举个栗子:一个缓存涉及的表的字段,在一分钟内就修改了20次,或者是100次,那么缓存更新20次、100次,但是这个缓存在1分钟内只被读取了1次,有大量的冷数据。

实际上,如果你只是删除缓存的话,那么在1分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。用到缓存才去算缓存。

其实删缓存,而不是更新缓存,就是一个Lazy计算的思想,不要每次都做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。

Mybatis,Hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的list,没有必要说每次查询部门,都里面的1000个员工的数据也要同时查出来。80%的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里查询1000个员工。
《吊打面试官》系列-Redis双写一致性、并发竞争、线程模型

如何从1亿个key中找出以固定的已知的前缀开头的10w个key

使用 keys命令可以扫出指定模式的key

如果这个redis正在提供服务,那么使用keys命令会有什么问题

Redis是单线程的。keys命令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完,服务才能恢复。这个时候可以使用 scan命令,scan命令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复率,在客户端做一次去重就可以了,但整体所花费的时间会比直接用keys指令长。

不过,增量式迭代命令也不是没有缺点:举个例子,使用SMEMBERS命令可以返回集合键当前包含的所有元素,但是对于SCAN这类增量式迭代命令来说,因为在对键进行增量式迭代的过程中,键可能被修改,所有增量式迭代命令只能对被返回的元素提供有限的保证。

使用Redis做过异步队列吗,怎么用的

一般使用 list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试,也可以不用sleep,使用blpop,在没有消息的时候,它会阻塞直到等待超时或新消息到来。

能不能一次生产多次消费?

使用pub/sub发布订阅模式,可以实现1对N的消息队列。

pub/sub有什么缺点?

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RocketMQ等。

Redis如何实现延时队列?

可以使用sortedset,用时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

Redis是单线程的,而现在服务器都是多核的,那不是很浪费?

Redis是单线程的,但我们可以在单机开多个Redis实例。

单机会有瓶颈,那你们是这么解决这个瓶颈的?

用到了集群的部署方式也就是Redis Cluster,并且是主从同步读写分离,类似MySQL的主从同步,Redis Cluster支撑N个Redis master node,每个master node都可以挂载多个slave node

这样整个Redis就可以横向扩容了。如果要支撑更大数据量的缓存,那就横向扩容更多的 master节点,每个 master节点就能存放更多的数据了。

Redis是怎么持久化的?

RDB做镜像全量持久化,AOF做增量持久化。因为RDB耗费时间较长,不够实时,在停机时会导致大量丢失数据,所以需要AOF来配合使用。在Redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。

可以把RDB理解为一整个表全量的数据,AOF理解为每次操作的日志就好,服务器重启的时候先把表的数据全部搞进去,但是可能不完整,再回放下日志,数据就完整了。不过Redis本身的机制是AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF关闭或者AOF文件不存在时,加载RDB文件;加载AOF/RDB文件之后,Redis启动成功;AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。
面试被问哭:Redis 如何做持久化与恢复?
redis 两种持久化方式以及数据备份与恢复方案
Redis中AOF持久化的配置
《我们一起进大厂》系列-Redis哨兵、持久化、主从、手撕LRU

对方追问那如果突然机器掉电会怎样?

取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都snyc是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

Pipeline有什么好处,为什么要用pipeline?

可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果关系。

Redis的同步机制了解吗?

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binglog。

是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?

Redis Sentinal 着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。参考

Redis Cluster 着眼于扩展性,在单个redis内存不足时,使用cluster进行分片存储。

Redis的线程模型了解吗?

Redis内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型。它采用IO多路复用机制同时监听多个Socket,根据Socket上的事件来选择对应的事件处理器进行处理。

文件事件处理器的结构包含4个部分:

  • 多个Socket
  • IO多路复用程序
  • 文件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

多个Socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,会将Socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值