Redis 缓存框架面试题全解析

概述

随着应用研发技术的不断成熟,Redis 缓存技术已经成为后台研发同学必备的能力之一。在很多公司面试的过程中,都必不可少地考察 Redis 知识点的掌握。

在这里我将所掌握的 Redis 的面试题目进行详细的梳理,结合平时工作遇到的问题以及面试题库收集的内容给大家进行分享。

本文将 Redis 知识方向分为六大块进行梳理:

  1. Redis 概念理解
  2. Redis 数据结构与指令
  3. Redis 高并发处理策略
  4. Redis 集群结构以及设计理念
  5. Redis 缓存管理与持久化机制
  6. Redis 应用场景设计

通过本文的学习,相信对大家未来的面试以及知识点的掌握都会有所助益。

一、Redis 概念理解

1. 什么是 Redis?

Redis 全称为:Remote Dictionary Server(远程数据服务),是一个基于内存且支持持久化的高性能 key-value 数据库。

具备一下几个基本特征:

  1. 多数据类型
  2. 持久化机制
  3. 主从同步

2. Redis 的特点有哪些?

  1. Redis 本质上是一个 key-value 类型的数据库
  2. 整个数据库都是在内存中进行操作,可定期刷新到磁盘进行持久化存储
  3. 由于是在内存操作,读写能力非常好,每秒可以处理 10 万次读写操作
  4. Redis 支持多种数据结构,提供了丰富的数据类型选择
  5. Redis 同时支持数据备份,主从配置
  6. Redis 的所有操作都是原子性的

3. Memcache 与 Redis 的区别都有哪些?

  1. 存储方式不同:Memcache 把数据全部存在内存之中,断电后会丢失。Redis 所有数据加载在内存,但也会持久化到磁盘,保证数据的持久性。
  2. 支持数据类型不同:Memcache 对数据类型支持相对简单,只支持 key-value 结构。Redis 有复杂的数据类型。
  3. 底层模型不同:底层实现方式以及客户端通信应用协议不一样。 Redis 直接自己构建了 VM 机制。
  4. 运行环境不同:Redis 目前官方只支持 Linux 上运行。

4. Redis 相比 Memcached 有哪些优势?

  1. Memcached 所有的值均是简单的字符串,Redis 作为其替代者,支持更为丰富的数据类型
  2. Redis 的速度比 Memcached 快很多
  3. Redis 可以持久化其数据

5. 如何实现本地缓存?请描述一下你知道的方式

  1. 程序中定义内存数据结构来实现, 比如说定义一个成员变量Map 或者 List 均可以实现
  2. 使用开源的缓存框架 Ehcache,Ehcache 封装了对于内存操作的功能
  3. Guava Cache 是 Google 开源的工具集, 提供了缓存的边界操作工具

6. Redis 通讯协议是什么?有什么特点?

Redis 的通信协议是 Redis Serialization Protocol,简称 RESP。

有如下特性:

  1. 是二进制安全的
  2. 在 TCP 层
  3. 基于请求—响应的模式

二、Redis 数据结构与指令

1. Redis 支持的数据类型

  1. String(字符串)
  2. list(列表):list 是字符串列表,按照插入顺序排序。元素可以在列表的头部(左边)或者尾部(右边)进行添加。
  3. hash(哈希):Redis hash 是一个键值对(key-value)集合。Redis hash 是一个 String 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
  4. set(集合):Redis 的 set 是 String 类型的无序集合。
  5. zset(sorted set:有序集合):Redis zset 和 set 一样也是 String 类型元素的集合,且不允许重复的成员。不同的 zset 是每个元素都会关联一个 double 类型的分数。zset 通过这个分数来为集合中所有元素进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。

2. Redis 常用的命令有哪些?

在这里插入图片描述

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

512M

4. Redis 各个数据类型最大存储量分别是多少?

  1. Strings 类型:一个 String 类型的 value 最大可以存储 512M
  2. Lists 类型:list 的元素个数最多为 2^32-1 个,也就是 4294967295 个。
  3. Sets 类型:元素个数最多为 2^32-1 个,也就是 4294967295 个。
  4. Hashes 类型:键值对个数最多为 2^32-1 个,也就是 4294967295 个。
  5. Sorted sets 类型:跟 Sets 类型相似。

5. 请介绍一下 Redis 的数据类型 SortedSet(zset)以及底层实现机制?

zset 有顺序,不能重复。在业务场景下,适合做排行榜之类的事情。

底层实现机制:

SortedSet 的实现方式可能有两种:ziplist 或者 skiplist

当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:

  1. 保存的元素数量小于 128;

  2. 保存的所有元素长度都小于 64 字节。    不能满足上面两个条件的使用 skiplist 编码。

  3. ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。

  4. skiplist 编码的有序集合对象使用 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表。字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。

6. Redis 事务相关命令有哪些?

  1. discard 命令:取消事务,丢弃事务中所有命令。
  2. exec 命令:执行所有事务内的命令。
  3. multi 命令:标记一个事务开始。
  4. unwatch 命令:取消 watch 命令对所有 key 的监视。
  5. watch 命令:监视一个(或多个)key,如果在执行事务之前这个(这些)key 被其他命令所改动,事务将被打断。

7. 什么是 Redis 事务?原理是什么?

Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位,一个事务要么都执行,要么都不执行。

Reids 事务保证一个事务内的命令依次执行,而不会被其他命令插入。

Redis 事务的原理是先将属于一个事务的命令发送给 Redis,然后依次执行这些命令。

8. Redis 事务的注意点有哪些?

  1. 不支持回滚,如果事务中有错误的操作,无法回滚到处理前的状态,需要开发者处理。
  2. 在执行完当前事务内所有指令前,不会同时执行其他客户端的请求。

9. Redis 为什么不支持回滚?

Redis 事务不支持回滚,如果遇到问题,会继续执行余下的命令。 这一点和关系型数据库不太一致。这样处理的原因有:

  1. 只有语法错误,Redis才会执行失败,例如错误类型的赋值, 这就是说从程序层面完全可以捕获以及解决这些问题
  2. 支持回滚需要增加很多工作,不支持的情况下,Redis 可以保持简单、速度快的特性

10. 请介绍一下 Redis 的 Pipeline(管道),以及使用场景

Redis 客户端与服务端通信模型使用的 TCP 协议进行连接, 那么在单个指令的执行过程中,都会存在“交互往返”的时间。

Redis 提供了批量操作命令,例如 mget、mset 等,能够一定程度上节省这类时间,但大部分命令还是不支持批量操作。

因此 Pipeline 功能,能够改善这一类问题。 Pipeline 将一组 Redis 命令进行组装,一次性传输给 Redis,再将这些命令执行结果,按照顺序返回给客户端。

适用场景:有批量的数据写入 Redis,并且这些数据允许一定比例的写入失败,那么这种场景就可以适用 Pipeline。失败的数据后期进行补偿即可。

11. 请说明一下 Redis 的批量命令与 Pipeline 有什么不同?

  1. 批量命令保证原子性的,Pipeline 非原子性
  2. 批量命令是一个命令对应多个 key,Pipeline 支持多个命令
  3. 批量命令是 Redis 服务端实现,而 Pipeline 是需要服务端和客户端共同实现

12. 请介绍一下 Redis 的发布订阅功能

Redis 提供了基于“发布/订阅”模式的消息机制,消息发布者和订阅者不能直接通信,客户端发布消息的时候指定发送的频道,然后订阅了该频道的用户可以接收到该消息。具体指令如下:

  1. 发布消息:publish channel message
  2. 订阅消息:subscribe channel [……]
  3. 退订消息:punsubscribe

13. Redis 的链表数据结构的特征有哪些?

  1. 双端引用:链表的最前和最后节点都有引用,获取前后节点的复杂度为 o(1)
  2. 无环链表:对于链表的访问都是以 null 结束
  3. 长度计数器:通过 len 属性来记录链表长度

14. 请介绍一下 Redis 的 String 类型底层实现?

Redis 底层实现了简单动态字符串的类型(SSD),来表示 String 类型。 没有直接使用 C 语言定义的字符串类型。

struct sdshdr{
    //记录 buf 数组中已使用字节的数量
    //等于 SDS 保存字符串的长度
    int len;
    //记录 buf 数组中未使用字节的数量
    int free;
    //字节数组,用于保存字符串
    char buf[];
}

15. Redis 的 String 类型使用 SSD 方式实现的好处?

  1. 避免缓冲区溢出:进行字符修改时候,可以根据 len 属性来检查空间是否满足要求
  2. 减少内存分配次数:len 和 free 两个属性,可以协助进行空间预分配以及惰性空间释放
  3. 二进制安全:SSD 不是以空字符串来判断是否结束,而是以 len 属性来判断字符串是否结束
  4. 常数级别获取字符串长度:获取字符串的长度只需要读取 len 属性就可以获取
  5. 兼容 C 字符串函数:可以重用 C 语言库的 <string.h style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; outline: 0px !important;">的一部分函数</string.h>

16. 设置键的生存时间和过期时间有哪些命令?

  • EXPIRE 以秒为单位,设置键的生存时间
  • PEXPIRE 以毫秒为单位,设置键的生存时间
  • EXPIREAT 以秒为单位,设置键的过期 UNIX 时间戳
  • PEXPIREAT 以毫秒为单位,设置键的过期 UNIX 时间戳

三、Redis 高并发处理策略

1. 为什么 Redis 需要把所有数据放到内存中?

追求更快的读写速度,并异步方式持久化存储到磁盘。 如果不将数据放到内存中,磁盘的 I/O 速度会严重影响 Redis 的性能。

2. Redis 是单线程的吗?

是。

这里的单线程指的是 Redis 网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。

3. Redis 为什么设计成单线程的?

  1. 绝大部分请求是纯粹的内存操作(非常快速)
  2. 采用单线程,避免了不必要的上下文切换和竞争条件
  3. 非阻塞 IO,内部采用 epoll,epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,避免 IO 代价。

4. 什么是缓存穿透?怎么解决?

缓存穿透是指缓存中查询一个不存在的数据,需要去数据库中获取。

如果数据库也查不到结果,将不会同步到缓存,导致这个不存在数据每次请求都要到数据库查询,失去了缓存的意义。

解决方法有两个:

1. 布隆过滤(Bloom filter)

将所有查询的参数都存储到一个 bitmap 中,在查询缓存之前,先再找个 bitmap 里面进行验证。

如果 bitmap 中存在,则进行底层缓存的数据查询; 如果 bitmap 中不存在查询参数,则进行拦截,不再进行缓存的数据查询。

适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集

2. 缓存空对象

如果查询返回的数据为空,仍然把这个空结果进行缓存。那么再次用相同 key 获取数据的时候,即使不存在的数据,缓存也可以直接返回空值,避免重复访问 DB。

缓存空对象有两个不足之处:

  1. 缓存层将存储更多的键值对,如果是恶意的随机访问,将造成很多内存空间的浪费。这个不足之处可以通过将这类数据设置很短的过期时间来控制。
  2. DB 与缓存数据不一致。这种可以考虑通过异步消息来进行数据更新的通知,在一定程度上减少这类不一致的时间。

5. 什么是缓存雪崩? 怎么解决?

在集中的一段时间内,有大量的缓存失效,导致大量的访问没有命中缓存,从而将所有查询进行数据库访问,导致数据库的压力增大,从而造成了缓存雪崩。

比如,如果要做一个促销活动,我们将商品信息都刷新到缓存, 过期时间统一为 30 分钟。那么在 30 分钟之后,这批商品将全部过期。这时候这批商品的访问查询,都落到了数据库,对于数据库而言,这一刻的压力会非常大。从而造成系统整体性风险。

解决方案:

方法 1:分散失效时间

分析缓存数据的特点,尽量将热点缓存的失效时间均匀分布。 比如说将相同类型的缓存的失效时间设置成一个在一定区间内的随机值。从而有效的分散失效时间。

方法 2:DB 访问限制

对数据的访问进行限流性质的操作。比如说对数据库访问进行加锁的处理或者限流相关的处理。

方法 3:多级缓存设计

一级缓存为基础缓存,缓存失效时间设置一个较长时间, 二级缓存为应用缓存,失效时间正常设置,一般会比较短。 当二级缓存失效的时候,再从一级缓存里面获取。

6. 缓存的更新策略有几种?分别有什么注意事项?

缓存的更新策略包含:

  1. 先更新数据库,再更新缓存
  2. 先删除缓存,再更新数据库
  3. 先更新数据库,再删除缓存

策略一:先更新数据库,再更新缓存

1. 这种策略会导致线程安全问题

例如:线程 1 更新了数据库,线程 2 也更新数据库, 这时候由于某种原因,线程 2 首先更新了缓存,线程 1 后续更新。 这样就导致了脏数据的问题。 因为目前数据库中存储的线程 2 更新后的数据,而缓存存储的是线程1更新的老数据。

2. 更新缓存的复杂度相对较高

数据写入数据库之后,一般存入缓存的数据都要经过一系列的加工计算,然后写入缓存。 这时候更新缓存相比较于直接删除缓存要比较复杂。

策略二:先删除缓存,再更新数据库

这种策略可能导致数据不一致的问题。线程 1 写数据删除缓存;这时候有线程 2 查询该缓存,发现不存在,则去访问数据库,得到旧值放入缓存;线程 1 更新数据库。这时候就出现了数据不一致的问题。 如果缓存没有过期时间,这个脏数据一直存在。

解决方案:在写数据库成功之后, 再次淘汰缓存一次。

策略三:先更新数据库,再删除缓存

可能会造成比较短暂的数据不一致。在更新完成数据库, 还没有删除缓存的时刻,如果有缓存数据访问, 就会造成数据不一致的情形。 但这种如果数据同步机制比较科学,一般都会比较快, 不一致的影响比较小。

7. 请介绍几个可能导致 Redis 阻塞的原因

内部原因:

  1. Redis 的 API 或者指令数据结构使用不合理
  2. Redis 主机 CPU 负载过高,导致系统崩溃
  3. 持久化工作资源占用过多

外部原因:

  1. CPU 竞争
  2. 内存交换
  3. 网络问题

8. 怎么去发现 Redis 阻塞异常情况?

1. 通过应用服务监控发现

当 Redis 阻塞的时候,线上应用服务应该会感知发现。比如说发现 Redis 链接超时等。这种就需要应用服务增加对于异常的统计,并针对 Redis 相关的异常,进行报警。

2. 通过 Redis 自身监控系统

借助 Redis 监控系统发现阻塞问题,当监控系统发现各个监控指标存在异常的时候,发送报警。 指标有:CPU/内存/磁盘等, 慢查询,命令耗时增加等。

四、Redis 集群结构以及设计理念

1. Redis 集群架构模式有哪几种?

  1. Redis 单节点单机器部署
  2. Redis 主从节点部署
  3. Redis Sentinel(哨兵)模式部署
  4. Redis 集群模式

2. Redis 集群最大节点个数是多少?

16384 个

3. Redis 集群的主从复制模型是怎样的?

Redis 中的主从复制,也就是 Master-Slave 模型,多个 Redis 实例间的数据同步以及 Redis 集群中数据同步会使用主从复制。

主从复制主要是数据同步, 数据同步分为全量同步增量同步

全量同步:

全量同步一般发生在 Slave 机器初始化阶段, 这时候 Slave 需要将 Master 上的所有数据都进行同步复制。

大概步骤如下所示:

https://images.gitbook.cn/ea87acc0-fbc5-11e9-a1fa-3962553c2766
  1. 从服务器发送 SYNC 命令,链接主服务器
  2. 主服务器收到 SYNC 命令后,进行存盘的操作,并继续收集后续的写命令,存储缓冲区
  3. 存盘结束后,将对应的数据文件发送到 Slave 中,完成一次全量同步
  4. 主服务数据发送完毕后,将进行增量的缓冲区数据同步
  5. Slave 加载数据文件和缓冲区数据,开始接受命令请求,提供操作

增量更新:

Slave 节点初始化完成之后,开始正常工作,Master 节点进行的写操作都会同步到 Slave 节点上面。Master 节点每执行一个写命令都会向从服务器发送相同的写命令,从服务器接收到命令,并执行。

4. 请介绍一下 Redis 集群实现方案

1. Redis Cluster 集群方案(服务端 Sharding 技术)

Redis Cluster 是 3.0 版本开始正式提供的服务器 Sharding 技术。引入 slot(槽)的概念,整个集群共有 16384 个槽。根据 key 进行散列(CRC16 后 16384 取模),分配到其中一个槽当中。

2. Redis Sharding 集群(客户端 Sharding 技术)

Redis Sharding 出现先与 Redis Cluster 方案,他是多 Redis 实例集群方案。

客户端 Sharding 方式,该向哪个 Redis 节点操作数据,由客户端来进行控制。服务端 Redis 还是一个个相对独立的 Redis 实例节点,没有做任何变动。节点选择可采用的方法:一致性 hash 算法或者虚拟节点算法。

还有 31% 的精彩内容
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
支付 ¥29.99 继续阅读
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容