Redis相关面试题总结

一、简介

本文总结一些常见的Redis相关的面试题,方便在后面有需要的时候查看和复习。

二、面试题

【1】redis和memcached有什么区别?为什么高并发下有时单线程的redis比多线程的memcached效率要高?

  • memcached可以缓存图片和视频,redis支持除了k/v键值对外更多的数据结构
  • redis可以使用虚拟内存,redis支持持久化和aof灾难恢复,redis通过主从支持数据备份
  • redis可以做消息队列
  • memcached多线程模型引入了缓存一致性和锁,加锁带来了性能损耗
  • redis是纯内存操作,核心是基于非阻塞的 IO 多路复用机制,单线程反而避免了多线程的频繁上下文切换问题

【2】redis主从复制如何实现?redis的集群模式如何实现?redis的key是如何寻址的?

  • 主从复制实现:主节点将自己内存中的数据做一份快照,将快照发给从节点,从节点将数据恢复到内存中,之后每次增加新数据时,主节点以类似mysql的二进制日志方式将sql发送给从节点,从节点拿到主节点发送过来的语句进行执行,同步数据。
  • Redis-cluster集群分片原理:Cluster中有一个16384长度的虚拟哈希槽,编号分别为0-16383,每个master节点都会负责其中一部分的槽,当有某个key被映射到某个master,那么这个master负责为这个key提供服务,至于哪个master节点负责哪个槽,可以由用户执行,也可以在初始化的时候自动生成,只有master才有槽的所有权。master节点维护着一个16384/8字节的位序列,master节点用bit来标志对于某个槽自己是否拥有。这种结构很容易添加或者删除节点,比如如果我想新添加一个节点D,我们只需要从节点A、B、C中得到部分槽到D上。

【3】使用redis如何设计分布式锁?说一下思路?使用zk可以吗?如何实现?有什么区别?

  • 1.线程A setnx(上锁的对象,超时时的时间戳t1),如果返回true,获得锁。
  • 2.线程B 用get获取t1,与当前时间戳比较,判断是是否超时,没超时false,若超时执行第3步; 
  • 3.计算新的超时时间t2,使用getset命令返回t3(该值可能其他线程已经修改过),如果t1==t3,获得锁,如果t1!=t3说明锁被其他线程获取了。
  • 4.获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)

【4】请谈谈redis的持久化?底层是如何实现的, 有什么优缺点?

  •  RDB(Redis DataBase:在不同的时间点将redis的数据生成的快照同步到磁盘等介质上):内存到硬盘的快照,定期更新
  •     耗时,耗性能(fork+io操作),易丢失数据
  •     原理        【a】save:save时只管保存,其他不管,save是一个阻塞式命令,即当服务器接收了一条save命令之后就会开始拍摄快照,在此期间不会再去处理其他的请求,其他请求会被挂起直到备份结束,应该避免使用。
  •    【b】bgsave:redis会在后台异步进行快照操作,快照操作同时还可以响应客户端请求,与save命令区别就在于bgsave不会阻塞主进程处理的操作。Redis会单独创建(Fork)一个子进程来进行持久化,先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
  •  
  • AOF(Append Only File:将redis所执行过的所有指令都记录下来,在下次redis重启时,只需要执行指令就可以了):写日志。
  • 缺点:体积大,恢复速度慢。
  • aof采用文件追加方式,文件会越来越大,为避免出现此种情况,redis新增了重写机制,当aof文件的大小超过所设定的阈值时,Redis就会启动aof文件的内容压缩,只保留可以恢复数据的最小指令集。当然我们也可以使用命令bgrewriteaof手动告诉redis进行aof文件的重写,这样可以有效减小aof文件的体积。
  • 重写原理:
  •     redis会fork出一个子进程来对aof文件进行重写,原理跟快照持久化命令 bgsave 的工作原理类似,值得注意的是,进行aof文件重写时,如果原来的 aof文件体积已经非常大,那么重写aof并删除旧aof 文件的过程将会对 Redis 的性能造成较大的影响。

【5】请谈谈缓存穿透、缓存击穿、缓存雪崩的解决方案?

  • 缓存穿透:指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,可能导致DB挂掉
  • 解决方案:查询返回的数据为空,仍把这个空结果进行缓存,但过期时间会比较短
  •     
  • 缓存击穿:对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把DB压垮
  • 解决方案:
  •         【a】使用互斥锁:当缓存失效时,不立即去load db,先使用如Redis的setnx去设置一个互斥锁,当操作成功返回时再进行load db的操作并回设缓存,否则重试get缓存的方法
  •         【b】永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)
  •  
  • 缓存雪崩:设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩
  •     解决方案 :将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件

【6】什么时候选择redis,什么时候选择memcached?

选择redis的情况:

  • 复杂数据结构,value的数据是哈希,列表,集合,有序集合等这种情况下,会选择redis, 因为memcache无法满足这些数据结构,最典型的的使用场景是,用户订单列表,用户消息,帖子评论等
  • 需要进行数据的持久化功能,但是注意,不要把redis当成数据库使用,如果redis挂了,内存能够快速恢复热数据,不会将压力瞬间压在数据库上,没有cache预热的过程。对于只读和数据一致性要求不高的场景可以采用持久化存储
  • 高可用,redis支持集群,可以实现主动复制,读写分离,而对于memcache如果想要实现高可用,需要进行二次开发
  • 存储的内容比较大,memcache存储的value最大为1M

选择memcache的情况:

  • 纯KV,数据量非常大的业务,使用memcache更合适
  • 数据量大时,memcache更快
  • memcache使用多线程,主线程监听,worker子线程接受请求,执行读写,这个过程可能存在锁冲突。redis使用的单线程,虽然无锁冲突,但是难以利用多核

【7】请谈谈缓存和数据库不一致怎么处理?

  • 发生场景:写后立刻读,先一个写请求,淘汰缓存,写数据库,接着立刻一个读请求,读缓存,cache miss,读从库,写缓存放入数据,以便后续的读能够cache hit( 主从同步没有完成,缓存中放入了旧数据 ),最后,主从同步完成。
  • 导致结果:旧数据放入缓存, 即使主从同步完成,后续仍然会从缓存一直读取到旧数据。可以看到,加入缓存后,导致的 不一致影响时间会很长 ,并且最终也不会达到一致。
  • 解决方案:在从库同步完成之后, 如果有旧数据入缓存,应该及时把这个旧数据淘汰掉

【8】请谈谈主从数据库不一致怎么解决?

发生场景:写后立刻读

  •         主库一个写请求(主从没同步完成)
  •         从库接着一个读请求,读到了旧数据
  •         最后,主从同步完成
  • 导致结果

    主动同步完成之前,会读取到旧数据。可以看到,主从不一致的 影响时间很短 ,在主从同步完成后,就会读到新数据

  • 解决方案有下面三种:
  •     【a】忽略数据不一致,在数据不一致要求不高的业务下,未必需要时刻保持一致性
  •     【b】强制读主库,使用一个高可用的主库,数据库读写都在主库,添加一个缓存,提升数据读取的性能
  •     【c】选择性读取主库,添加一个缓存,用来记录必须读主库的数据,将哪个库,哪个表,哪个主键,作为缓存的key,设置缓存失效的时间为主从同步的时间,如果缓存中有这个数据,直接读取主库,如果缓存当中没有这个主键,就到对应的从库中读取。

【9】redis常见的性能问题和解决方案?

  • master最好不要持久化工作,如rdb和aof
  • 如果数据比较重要,某个slave开启aof备份,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,master和slave最好在一个局域网内
  • 尽量避免在压力大的主库增加从库
  • 主从复制不要采用网状接口,尽量是现行结构,Master<--Slave1<----Slave2

【10】redis的数据淘汰策略有哪些?

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用 的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数 据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据 淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

【11】请谈谈redis的数据结构有哪些,以及列举常用的命令?

  •     string:
  •         get、set、 setnx、setex、mset、mget、strlen
  •     list:
  •         lpush、push、lrange、lindex、llen、lpop、rpop、rpoplpush 源列表 目的列表
  •     hash:
  •         hset、hget、hmset、hmget、hgetall、hdel、hlen
  •     set:
  •         sadd、smembers、sismember、scard、srem、srandmember、spop
  •     zset:
  •         zadd、zrange、zrangebyscore、zrem、zcard、zcount、zrank

【12】假如redis里面有一亿个key,其中有10万个key是以固定的已知的前缀开头的,如何将他们全部找出来?

  • 使用keys指令可以扫出指定模式的key列表
  • 对应的问题 :因为redis 是单线程 所以一次性操作大量的数据 可能会导致业务出现卡顿
  • 解决办法:可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长

【13】使用redis实现异步队列,是如何实现的?

  • 使用List作为队列,RPUSH生产消息,LPOP消费消息, 当lpop没有消息时,可以sleep一段时间,然后再检查有没有消息,如果不想sleep的话,可以使用blpop,在没有消息的时候,会一直阻塞,知道消息的到来。redis可以通过pub/sub发布订阅模式实现生产者消费者模型。
  • 缺点:没有等待队列里有值就直接消费

【14】redis如何实现延迟队列?

使用sortedset,使用时间戳作为score,消息内容作为key,使用zadd来生产消息,消费者使用zrangebyscore获取n秒之前的数据做轮训处理。

【15】什么是redis?简述它的优缺点?

redis是一个完全开源免费的key-value内存数据库,通常被认为是一个数据结构服务器,主要是因为其有着丰富的数据结构 strings、map、 list、sets、 sortedsets。
优点:

  •     性能极高
  •     丰富的数据类型 
  •     原子操作
  •     持久化
  •     主从、集群

缺点:

  •     数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上

【16】一个字符串类型的值能存储最大容量是多少?

512M

【17】为什么redis需要把所有数据放到内存中?

redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘IO速度会严重影响redis的性能。如果设置了最大使用内存,则数据已有记录数达到内存限制后不能继续插入新值。

【18】redis集群方案什么情况下会导致整个集群不可用?

有A、B、C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少 5 501-11000整个范围的槽而不可用。

【19】mysql里面有2000万数据,redis中只存20万数据,如何保证redis中的数据都是热点数据?

redis内存数据集大小上升到一定大小的时候,就会实施数据淘汰策略

【20】redis都有哪些适合的场景?

  • 会话缓存
  • 消息队列
  • 排行榜、计数器 :ZRANGE user_scores 0 10 WITHSCORES
  • 发布、订阅

【21】请说说redis哈希槽的概念?

redis集群没有使用一致性hash,而是引入了哈希槽的概念,redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分哈希槽。

【22】redis的集群的主从复制是怎么样的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群中使用了主从复制模型,每个节点都会有N-1个复制品。

【23】redis集群会有写操作丢失吗?为什么?

redis并不能保证数据的强一致性,这意味着实际中集群在特定的条件下可能会丢失写操作。

【24】redis集群之间是如何复制的?

异步复制的方式:Redis 通过同步(sync)和指令传播(command propagate)两个操作完成同步。

  •     同步(sync):将从节点的数据库状态更新至与主节点的数据库状态一致
  •     指令传播(command propagate):主节点数据被修改,会主动向从节点发送执行的写指令,从节点执行之后,两个节点数据状态又保持一致

步骤如下:

  •     ①从数据库向主数据库发送sync(数据同步)命令。
  •     ②主数据库接收同步命令后,会保存快照,创建一个RDB文件。
  •     ③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。
  •     ④主数据库将缓冲区的所有写命令发给从服务器执行。
  •     ⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库

【25】redis集群最大节点个数是多少?

16384

【26】redis集群如何选择数据库?

redis集群目前无法做数据库旋转,默认在0号数据库,总共16个数据库,编号为0-15

【27】怎么测试redis的连通性?

ping命令

【28】redis的管道有什么用?

一次请求/响应服务器能实现处理新的请求即使旧的请求还没被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。

【29】怎么理解redis事务?

事务是一个单独的隔离操作,事务中的所有命令都会被序列化,按顺序地执行,事务在执行过程中,不会被其他客户端发送来的命令所打断。事务是一个原子操作,事务中的鄋命令要么全部都执行,要么全部都不执行。

【30】redis事务相关的命令是什么?

MULTI、EXEC、DISCARD、WATCH

【31】redis key的过期时间和永久有效都是怎么设置的?

  • EXPIRE
  • PERSIST

【32】redis如何做内存优化?

  • 设置内存上限,并指定内存回收策略:maxmemory配置参数可限制当前Redis实例可使用的最大内存
  • 缩减键、值对象的长度:简化键名,使用高效的序列化工具来序列化值对象,还可使用压缩工具(Google Snappy)压缩序列化后的数据
  • 尽可能的使用hash散列表数据结构,减少key的数量
  • 共享对象池:Redis内部维护[0-9999]的整数对象池,对于0-9999的内部整数类型的元素、整数值对象都会直接引用整数对象池中的对象,因此尽量使用整数对象可节省内存;

【33】redis回收进程是如何工作的?

一个客户端运行了新的命令,添加了新的数据。Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限 制就会被这个内存使用量超越。

三、总结

分享脑图总结:

发布了250 篇原创文章 · 获赞 112 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/105716775