《八股文》20 道 Redis 面试题

《八股文》20 道 Redis 面试题

以下文章来源于爱笑的架构师,作者雷架

爱笑的架构师
死磕技术,热爱生活!

强烈推荐👉项目消息推送平台Austin(10K+ stars),可以用作毕业设计,可以用作校招,可以看看生产环境是怎么推送消息的。

1、什么是 Redis,Redis 有哪些特点?

Redis 全称为:Remote Dictionary Server(远程字典服务),Redis 是一种支持 key-value 等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。
特点 1:丰富的数据类型
我们知道很多数据库只能处理一种数据结构:

2、Redis 有哪些数据结构?

Redis 是 key-value 数据库,key 的类型只能是 String,但是 value 的数据类型就比较丰富了,主要包括五种:

(1)String 字符串
语法

SET KEY_NAME VALUE

string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。string 类型是 Redis 最基本的数据类型,一个键最大能存储 512MB。
(2)Hash 哈希
语法

HSET KEY_NAME FIELD VALUE

Redis hash 是一个键值 (key=>value) 对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
(3)List 列表
语法

//在 key 对应 list 的头部添加字符串元素
LPUSH KEY_NAME VALUE1.. VALUEN
//在 key 对应 list 的尾部添加字符串元素
RPUSH KEY_NAME VALUE1..VALUEN
//对应 list 中删除 count 个和 value 相同的元素
LREM KEY_NAME COUNT VALUE
//返回 key 对应 list 的长度
LLEN KEY_NAME

Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)
(4)Set 集合
语法

SADD KEY_NAME VALUE1...VALUEn

Redis 的 Set 是 string 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
(5)Sorted Set 有序集合
语法

ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN

Redis zset 和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。
redis 正是通过分数来为集合中的成员进行从小到大的排序。
zset 的成员是唯一的,但分数 (score) 却可以重复。
ZADD 向有序集合中添加元素时,如果多个客户端同时向集合中添加元素,Redis 会确保每次操作的原子性。这意味着即使多个客户端并发执行 ZADD,每个命令都会独立执行,不会被中途打断。
例如:
ZADD myzset 1 "element1" ZADD myzset 2 "element2"
即使有多个并发请求,只要 Redis 进程处理这些命令时会确保每个操作是独立、顺序执行的
有时你可以通过设置合理的分数(score)来控制竞争,比如用时间戳或递增的计数器作为分数,这样即使多个客户端同时向集合中插入元素,也能通过分数确保元素在集合中的顺序。
例如,使用时间戳作为分数可以避免冲突:
ZADD myzset 1674000000 "element1" ZADD myzset 1674000001 "element2"

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

查询官方文档(https://redis.io/topics/data-types)可以看到 String 类型的 value 值最多支持的长度为 512M,所以正确的答案是 512M。

4、能说一下 Redis 每种数据结构的使用场景吗?

(1)String 的使用场景
字符串类型的使用场景:信息缓存、计数器、分布式锁等等。
常用命令:get/set/del/incr/decr/incrby/decrby
实战场景 1:记录每一个用户的访问次数,或者记录每一个商品的浏览次数
方案:
常用键名:userid:pageview 或者 pageview:userid,如果一个用户的 id 为 123,那对应的 redis key 就为 pageview:123,value 就为用户的访问次数,增加次数可以使用命令:incr。
使用理由:每一个用户访问次数或者商品浏览次数的修改是很频繁的,如果使用 mysql 这种文件系统频繁修改会造成 mysql 压力,效率也低。而使用 redis 的好处有二:使用内存,很快;单线程,所以无竞争,数据不会被改乱。
实战场景 2:缓存频繁读取,但是不常修改的信息,如用户信息,视频信息
方案:
业务逻辑上:先从 redis 读取,有值就从 redis 读取,没有则从 mysql 读取,并写一份到 redis 中作为缓存,注意要设置过期时间。
键值设计上:
直接将用户一条 mysql 记录做序列化 (通常序列化为 json) 作为值,userInfo:userid 作为 key,键名如:userInfo:123,value 存储对应用户信息的 json 串。如 key 为:"user:id:name:1",  value 为 "{"name":"leijia","age":18}"。
实战场景 3:限定某个 ip 特定时间内的访问次数
方案:
用 key 记录 IP,value 记录访问次数,同时 key 的过期时间设置为 60 秒,如果 key 过期了则重新设置,否则进行判断,当一分钟内访问超过 100 次,则禁止访问。令牌桶的思路
实战场景 4: 分布式 session
我们知道 session 是以文件的形式保存在服务器中的;如果你的应用做了负载均衡,将网站的项目放在多个服务器上,当用户在服务器 A 上进行登陆,session 文件会写在 A 服务器;当用户跳转页面时,请求被分配到 B 服务器上的时候,就找不到这个 session 文件,用户就要重新登陆。
如果想要多个服务器共享一个 session,可以将 session 存放在 redis 中,redis 可以独立于所有负载均衡服务器,也可以放在其中一台负载均衡服务器上;但是所有应用所在的服务器连接的都是同一个 redis 服务器。
当我们用一台专门的服务器来保存集群中的 session 的时候,我们实际上就得到了一个无状态集群,集群可以无痛添加机器而不用担心状态问题。
要使用 Redis 保存用户的会话数据,可以使用 Redis 的 键值对 存储模型,通常会选择 哈希(hash)字符串(string) 来存储会话信息。使用字符串的时候,会将用户信息,比如用户名,角色,登陆时间,上次操作时间之类的信息放到一个 JSON 字符串中进行保存,不过我们也可以用一个 Hash 类型的值来保存,这样可以我们就可以单独更新用户信息中的某一个字段
如何设置 session 过期呢?我们可以为 Web 应用做一个拦截器,只要有用户访问了服务,就延长用户的 session 的过期时间。
SpringBoot 也提供了 @EnableRedisHttpSession 注解来将 Redis 作为会话管理器。
话说回来,使用 JWT 也可以实现集群的去中心化的身份认证。不过我感觉没有 Redis 好用,原因是 JWT 无法做到快速踢用户下线。具体请看 JWT基本知识
(2)Hash 的使用场景
以购物车为例子,用户 id 设置为 key,那么购物车里所有的商品就是用户 key 对应的值了,每个商品有 id 和购买数量,对应 hash 的结构就是商品 id 为 field,商品数量为 value。如图所示:
500
如果将商品 id 和商品数量序列化成 json 字符串,那么也可以用上面讲的 string 类型存储。下面对比一下这两种数据结构:

对比项 string(json) hash
效率 很高
容量
灵活性
序列化 简单 复杂
总结一下:
当对象的某个属性需要频繁修改时,不适合用 string+json,因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值;如果使用 hash 类型,则可以针对某个属性单独修改,没有序列化,也不需要修改整个对象。比如,商品的价格、销量、关注数、评价数等可能经常发生变化的属性,就适合存储在 hash 类型里。
(3)List 的使用场景
列表本质是一个有序的,元素可重复的队列。
实战场景:定时排行榜
list 类型的 lrange 命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在 list 类型中,如 QQ 音乐内地排行榜,每周计算一次存储在 list 类型中,访问接口时通过 page 和 size 分页转化成 lrange 命令获取排行榜数据。
500
但是,并不是所有的排行榜都能用 list 类型实现,只有定时计算的排行榜才适合使用 list 类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list 类型不能支持实时计算的排行榜,下面介绍有序集合 sorted set 的应用场景时会详细介绍实时计算的排行榜的实现。

限流器

滑动窗口日志算法 (Sliding Window Log)。
将 IP 的记录请求时间戳记录到 redis 中,一个 IP 一个 key,然后如果每次来请求,都根据当前请求时间戳往前固定时间作为起点,将队列中窗口起始时间早于这个起点的都删掉,并统计窗口内的请求个数,如果超过指定个数,就报错,
(4)Set 的使用场景
集合的特点是无序性和确定性(不重复)。
实战场景:收藏夹
例如 QQ 音乐中如果你喜欢一首歌,点个『喜欢』就会将歌曲放到个人收藏夹中,每一个用户做一个收藏的集合,每个收藏的集合存放用户收藏过的歌曲 id。
key 为用户 id,value 为歌曲 id 的集合。
500
(5)Sorted Set 的使用场景
有序集合的特点是有序,无重复值。与 set 不同的是 sorted set 每个元素都会关联一个 score 属性,redis 正是通过 score 来为集合中的成员进行从小到大的排序。
实战场景:实时排行榜
QQ 音乐中有多种实时榜单,比如飙升榜、热歌榜、新歌榜,可以用 redis key 存储榜单类型,score 为点击量,value 为歌曲 id,用户每点击一首歌曲会更新 redis 数据,sorted set 会依据 score 即点击量将歌曲 id 排序。
500

5、Redis 如何做持久化的?能说一下 RDB 和 AOF 的实现原理吗?

什么是持久化?
持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML 数据文件中等等。

还可以从如下两个层面简单的理解持久化 :

RDB 持久化

RDB(Redis Database) 持久化是把当前内存数据生成快照保存到硬盘的过程,触发 RDB 持久化过程分为手动触发自动触发
(1)手动触发
手动触发对应 save 命令,会阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。
(2)自动触发
自动触发对应 bgsave 命令,Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。
在 redis.conf 配置文件中可以配置:

save <seconds> <changes>

表示 xx 秒内数据修改 xx 次时自动触发 bgsave。如果想关闭自动触发,可以在 save 命令后面加一个空串,即:

save ""

还有其他常见可以触发 bgsave,如:

AOF 持久化

AOF(append only file)持久化:以独立日志的方式记录每次写命令, 重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式。
AOF 持久化工作机制
开启 AOF 功能需要配置:appendonly yes,默认不开启。
AOF 文件名 通过 appendfilename 配置设置,默认文件名是 appendonly.aof。保存路径同 RDB 持久化方式一致,通过 dir 配置指定。
AOF 的工作流程操作:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)。
300
(1)所有的写入命令会追加到 aof_buf(缓冲区)中。
(2)AOF 缓冲区根据对应的策略向硬盘做同步操作。
AOF 为什么把命令追加到 aof_buf 中?Redis 使用单线程响应命令,如果每次写 AOF 文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。先写入缓冲区 aof_buf 中,还有另一个好处,Redis 可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡。
(3)随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
(4)当 Redis 服务器重启时,可以加载 AOF 文件进行数据恢复。
AOF 重写(rewrite)机制
重写的目的:

RDB 和 AOF 的优缺点

RDB 优点

6、讲解一下 Redis 的线程模型?

redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。
如果面试官继续追问为啥 redis 单线程模型也能效率这么高?

7、缓存雪崩、缓存穿透、缓存预热、缓存击穿、缓存降级的区别是什么?

在实际生产环境中有时会遇到缓存穿透、缓存击穿、缓存雪崩等异常场景,为了避免异常带来巨大损失,我们需要了解每种异常发生的原因以及解决方案,帮助提升系统可靠性和高可用。

(1)缓存穿透

什么是缓存穿透?
缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍,然后返回空。
如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至击垮数据库系统。
缓存穿透常用的解决方案
(1)布隆过滤器(推荐)
布隆过滤器(Bloom Filter,简称 BF)由 Burton Howard Bloom 在 1970 年提出,是一种空间效率高的概率型数据结构。
布隆过滤器专门用来检测集合中是否存在特定的元素。
如果在平时我们要判断一个元素是否在一个集合中,通常会采用查找比较的方法,下面分析不同的数据结构查找效率:

(2)缓存击穿

什么是缓存击穿?
缓存击穿,是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
缓存击穿危害
数据库瞬时压力骤增,造成大量请求阻塞。
如何解决?
方案一:使用互斥锁(mutex key)
这种思路比较简单,就是让一个线程回写缓存,其他线程等待回写缓存线程执行完,重新读缓存即可。
600
同一时间只有一个线程读数据库然后回写缓存,其他线程都处于阻塞状态。如果是高并发场景,大量线程阻塞势必会降低吞吐量。这种情况如何解决?大家可以在留言区讨论。
如果是分布式应用就需要使用分布式锁。
方案二:热点数据永不过期
永不过期实际包含两层意思:

(3)缓存雪崩

什么是缓存雪崩?
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,请求直接落到数据库上,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
缓存雪崩解决方案
常用的解决方案有:

(4)缓存预热

什么是缓存预热?
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,这样就可以避免在用户请求的时候,先查询数据库,然后再将数据回写到缓存。
如果不进行预热, 那么 Redis 初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。
缓存预热的操作方法

(5)缓存降级

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。
在项目实战中通常会将部分热点数据缓存到服务的内存中,这样一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。
降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

8、Redis 的内存淘汰机制

Redis 内存淘汰策略是指当缓存内存不足时,通过淘汰旧数据处理新加入数据选择的策略。
如何配置最大内存?
(1)通过配置文件配置
修改 redis.conf 配置文件

maxmemory 1024mb //设置Redis最大占用内存大小为1024M

注意:maxmemory 默认配置为 0,在 64 位操作系统下 redis 最大内存为操作系统剩余内存,在 32 位操作系统下 redis 最大内存为 3GB。
(2)通过动态命令配置
Redis 支持运行时通过命令动态修改内存大小:

127.0.0.1:6379> config set maxmemory 200mb //设置Redis最大占用内存大小为200M
127.0.0.1:6379> config get maxmemory //获取设置的Redis能使用的最大内存大小
1) "maxmemory"
2) "209715200"

淘汰策略的分类
Redis 最大占用内存用完之后,如果继续添加数据,如何处理这种情况呢?实际上 Redis 官方已经定义了八种策略来处理这种情况:
noeviction
默认策略,对于写请求直接返回错误,不进行淘汰。
allkeys-lru
lru(less recently used), 最近最少使用。从所有的 key 中使用近似 LRU 算法进行淘汰。
volatile-lru
lru(less recently used), 最近最少使用。从设置了过期时间的 key 中使用近似 LRU 算法进行淘汰。
allkeys-random
从所有的 key 中随机淘汰。
volatile-random
从设置了过期时间的 key 中随机淘汰。
volatile-ttl
ttl(time to live),在设置了过期时间的 key 中根据 key 的过期时间进行淘汰,越早过期的越优先被淘汰。
allkeys-lfu
lfu(Least Frequently Used),最少使用频率。从所有的 key 中使用近似 LFU 算法进行淘汰。从 Redis4.0 开始支持。
volatile-lfu
lfu(Least Frequently Used),最少使用频率。从设置了过期时间的 key 中使用近似 LFU 算法进行淘汰。从 Redis4.0 开始支持。
注意:当使用 volatile-lru、volatile-random、volatile-ttl 这三种策略时,如果没有设置过期的 key 可以被淘汰,则和 noeviction 一样返回错误。
LRU 算法
LRU(Least Recently Used),即最近最少使用,是一种缓存置换算法。在使用内存作为缓存的时候,缓存的大小一般是固定的。当缓存被占满,这个时候继续往缓存里面添加数据,就需要淘汰一部分老的数据,释放内存空间用来存储新的数据。这个时候就可以使用 LRU 算法了。其核心思想是:如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉。
LRU 在 Redis 中的实现
Redis 使用的是近似 LRU 算法,它跟常规的 LRU 算法还不太一样。近似 LRU 算法通过随机采样法淘汰数据,每次随机出 5 个(默认)key,从里面淘汰掉最近最少使用的 key。
可以通过 maxmemory-samples 参数修改采样数量, 如:maxmemory-samples 10
maxmenory-samples 配置的越大,淘汰的结果越接近于严格的 LRU 算法,但因此耗费的 CPU 也很高。
Redis 为了实现近似 LRU 算法,给每个 key 增加了一个额外增加了一个 24bit 的字段,用来存储该 key 最后一次被访问的时间。
Redis3.0 对近似 LRU 的优化
Redis3.0 对近似 LRU 算法进行了一些优化。新算法会维护一个候选池(大小为 16),池中的数据根据访问时间进行排序,第一次随机选取的 key 都会放入池中,随后每次随机选取的 key 只有在访问时间小于池中最小的时间才会放入池中,直到候选池被放满。当放满后,如果有新的 key 需要放入,则将池中最后访问时间最大(最近被访问)的移除。
当需要淘汰的时候,则直接从池中选取最近访问时间最小(最久没被访问)的 key 淘汰掉就行。
LFU 算法
LFU(Least Frequently Used),是 Redis4.0 新加的一种淘汰策略,它的核心思想是根据 key 的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。
LFU 算法能更好的表示一个 key 被访问的热度。假如你使用的是 LRU 算法,一个 key 很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些 key 将来是很有可能被访问到的则被淘汰了。如果使用 LFU 算法则不会出现这种情况,因为使用一次并不会使一个 key 成为热点数据。

9、Redis 有事务机制吗?

有事务机制。Redis 事务生命周期:

10、Redis 事务到底是不是原子性的?

先看关系型数据库 ACID 中关于原子性的定义:
原子性: 一个事务 (transaction) 中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复 (Rollback) 到事务开始前的状态,就像这个事务从来没有执行过一样。
官方文档对事务的定义:

11、Redis 为什么不支持回滚(roll back)?

在事务运行期间虽然 Redis 命令可能会执行失败,但是 Redis 依然会执行事务内剩余的命令而不会执行回滚操作。如果你熟悉 mysql 关系型数据库事务,你会对此非常疑惑,Redis 官方的理由如下:

只有当被调用的 Redis 命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis 能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致 Redis 命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。支持事务回滚能力会导致设计复杂,这与 Redis 的初衷相违背,Redis 的设计目标是功能简化及确保更快的运行速度。

对于官方的这种理由有一个普遍的反对观点:程序有 bug 怎么办?但其实回归不能解决程序的 bug,比如某位粗心的程序员计划更新键 A,实际上最后更新了键 B,回滚机制是没法解决这种人为错误的。正因为这种人为的错误不太可能进入生产系统,所以官方在设计 Redis 时选用更加简单和快速的方法,没有实现回滚的机制。

12、Redis 事务相关的命令有哪几个?

(1)WATCH
可以为 Redis 事务提供 check-and-set (CAS)行为。被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回 nil-reply 来表示事务已经失败。
(2)MULTI
用于开启一个事务,它总是返回 OK。MULTI 执行之后,客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行,而是被放到一个队列中,当 EXEC 命令被调用时, 所有队列中的命令才会被执行。
(3)UNWATCH
取消 WATCH 命令对所有 key 的监视,一般用于 DISCARD 和 EXEC 命令之前。如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。因为 EXEC 命令会执行事务,因此 WATCH 命令的效果已经产生了;而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视,因此这两个命令执行之后,就没有必要执行 UNWATCH 了。
(4)DISCARD
当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空,并且客户端会从事务状态中退出。
(5)EXEC
负责触发并执行事务中的所有命令:
如果客户端成功开启事务后执行 EXEC,那么事务中的所有命令都会被执行。
如果客户端在使用 MULTI 开启了事务后,却因为断线而没有成功执行 EXEC,那么事务中的所有命令都不会被执行。需要特别注意的是:即使事务中有某条/某些命令执行失败了,事务队列中的其他命令仍然会继续执行,Redis 不会停止执行事务中的命令,而不会像我们通常使用的关系型数据库一样进行回滚。

13、什么是 Redis 主从复制?

主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点 (master),后者称为从节点 (slave);数据的复制是单向的,只能由主节点到从节点。
主从复制的作用

14、Sentinel(哨兵模式)的原理你能讲一下吗?

Redis 的主从复制模式下,一旦主节点由于故障不能提供服务,需要手动将从节点晋升为主节点,同时还要通知客户端更新主节点地址,这种故障处理方式从一定程度上是无法接受的。
Redis 2.8 以后提供了 Redis Sentinel 哨兵机制来解决这个问题。
Redis Sentinel 是 Redis 高可用的实现方案。Sentinel 是一个管理多个 Redis 实例的工具,它可以实现对 Redis 的监控、通知、自动故障转移。
Redis Sentinel 架构图如下:
800
哨兵模式的原理
哨兵模式的主要作用在于它能够自动完成故障发现和故障转移,并通知客户端,从而实现高可用。哨兵模式通常由一组 Sentinel 节点和一组(或多组)主从复制节点组成。
心跳机制
(1)Sentinel 与 Redis Node
Redis Sentinel 是一个特殊的 Redis 节点。在哨兵模式创建时,需要通过配置指定 Sentinel 与 Redis Master Node 之间的关系,然后 Sentinel 会从主节点上获取所有从节点的信息,之后 Sentinel 会定时向主节点和从节点发送 info 命令获取其拓扑结构和状态信息。
(2)Sentinel 与 Sentinel
基于 Redis 的订阅发布功能, 每个 Sentinel 节点会向主节点的 sentinel:hello 频道上发送该 Sentinel 节点对于主节点的判断以及当前 Sentinel 节点的信息 ,同时每个 Sentinel 节点也会订阅该频道, 来获取其他 Sentinel 节点的信息以及它们对主节点的判断。
通过以上两步所有的 Sentinel 节点以及它们与所有的 Redis 节点之间都已经彼此感知到,之后每个 Sentinel 节点会向主节点、从节点、以及其余 Sentinel 节点定时发送 ping 命令作为心跳检测, 来确认这些节点是否可达。
故障转移
每个 Sentinel 都会定时进行心跳检查,当发现主节点出现心跳检测超时的情况时,此时认为该主节点已经不可用,这种判定称为主观下线
之后该 Sentinel 节点会通过 sentinel ismaster-down-by-addr 命令向其他 Sentinel 节点询问对主节点的判断, 当 quorum(法定人数) 个 Sentinel 节点都认为该节点故障时,则执行客观下线,即认为该节点已经不可用。这也同时解释了为什么必须需要一组 Sentinel 节点,因为单个 Sentinel 节点很容易对故障状态做出误判。

这里 quorum 的值是我们在哨兵模式搭建时指定的,后文会有说明,通常为 Sentinel 节点总数/2+1,即半数以上节点做出主观下线判断就可以执行客观下线。

因为故障转移的工作只需要一个 Sentinel 节点来完成,所以 Sentinel 节点之间会再做一次选举工作, 基于 Raft 算法选出一个 Sentinel 领导者来进行故障转移的工作。
被选举出的 Sentinel 领导者进行故障转移的具体步骤如下:
(1)在从节点列表中选出一个节点作为新的主节点

15、Cluster(集群)的原理你能讲一下吗?

引入 Cluster 模式的原因:
不管是主从模式还是哨兵模式都只能由一个 master 在写数据,在海量数据高并发场景,一个节点写数据容易出现瓶颈,引入 Cluster 模式可以实现多个节点同时写数据。
Redis-Cluster 采用无中心结构,每个节点都保存数据,节点之间互相连接从而知道整个集群状态。
600

如图所示 Cluster 模式其实就是多个主从复制的结构组合起来的,每一个主从复制结构可以看成一个节点,那么上面的 Cluster 集群中就有三个节点。

16、Memcache 与 Redis 的区别都有哪些?

存储方式
Memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。
Redis 有部份存在硬盘上,这样能保证数据的持久性。
数据支持类型
Memcache 对数据类型支持相对简单。
Redis 有丰富的数据类型。
使用底层模型不同
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

17、假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表:keys pre*,这个时候面试官会追问该命令对线上业务有什么影响,直接看下一个问题。

18、如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?

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

19、如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,Redis 可能会出现短暂的卡顿现象 (因为 redis 是单线程的)。严重的话可能会导致服务器雪崩,所以我们一般在过期时间上加一个随机值,让过期时间尽量分散。

20、Redis 常用的客户端有哪些?

Jedis:是老牌的 Redis 的 Java 实现客户端,提供了比较全面的 Redis 命令的支持。
Redisson:实现了分布式和可扩展的 Java 数据结构。
Lettuce:高级 Redis 客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
优点:
Jedis:比较全面的提供了 Redis 的操作特性。
Redisson:促使使用者对 Redis 的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过 Redis 支持延迟队列。
Lettuce:基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。Lettuce 的 API 是线程安全的,所以可以操   作单个 Lettuce 连接来完成各种操作。
END