redis 面试总结

1. redis 为什么快?

在底层上, redis 使用了 IO 多路复用技术,像 select、epoll 等。能较好的保障吞吐量。而且 redis 采用了单线程处理请求,避免了线程切换和锁竞争锁带来的额外消耗。

加上 redis 本身也对一些数据结构进行了优化设计,所以 redis 的性能非常好,官方给出的测试报告是单机可以支持约 10w/s 的 QPS。

2. Redis 有哪些使用场景?应用是怎么样的?

Redis 的使用场景有很多,最常用的莫过于数据缓存了。但由于它提供了多种数据类型,因此我们还可以进行其他场景的开发,比如:

  • 排行榜:有序集合(sorted set)每次写入都会进行排序,而且不含重复值,所以我们可以将用户的唯一标识,比如 userId 作为 key,分数作为 score,然后就可以进行 ZADD 操作,以得到排行榜。
  • 签到:签到往往只有 2 种状态,已签到和未签到。这就跟 0 和 1 一样,所以 redis 的 setbitgetbit 这种对位的操作就适合签到场景。
  • 计数:redis 是单线程操作,这种计数功能,比如点赞数、粉丝数的操作可以交给 redis 以避免并发竞争问题。当然,也得考虑持久化问题。

3. Redis 通信协议 是怎么样的?

redis 采用文本序列化协议,和 http 协议一样,一个请求一个响应,客户端接到响应后再继续请求。也可以发起多次请求,然后一次响应回所有执行结果,即所谓的 pipeline 管道技术

redis 的文本序列化协议比较简单,通过一些规范格式去解析文本,大概如下:

  • \r\n 表示解析结束
  • 简单字符串,以“+”开头
  • 错误 Errors,以“-”开头
  • 整数类型,以“:”开头
  • 大字符串类型,以“$”开头
  • 数组类型,以“*”开头

例如,客户端向服务器发送命令:

SET key value

将被解析为:

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

上面的命令可以看成:

*<参数数量> CR LF

$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF

而服务器的回复则有很多类型,一般由响应数据的第一个字节决定:

状态回复(status reply)的第一个字节是 "+"

错误回复(error reply)的第一个字节是 "-"

整数回复(integer reply)的第一个字节是 ":"

批量回复(bulk reply)的第一个字节是 "$"

多条批量回复(multi bulk reply)的第一个字节是 "*"

例如,响应回来的状态回复如下:

+OK

4. redis 对外提供了哪些数据类型,它们的底层数据结构又是怎么样的?

为了让开发者能更好的使用缓存,redis 支持了 5 种数据类型。底层是由 6 种数据结构组成的。

5 种数据类型

字符串:字符串类型是 redis 里最基础的数据类型,像 set name "hello" 操作后,在 get name 时返回的就是字符串,而且还支持了对位的操作。一般一个键能存储 512MB 的值。

hash:哈希类型主要是用来存储对象的,一般我们如果有一整个对象要存储,里面包含了多个字段,则可以使用 hash 来存储,因为 redis 提供了对这些字段的提取和设置,减少了开发者对它的二次处理,比如序列化反序列化操作。

list:一个简单的字符串列表,它允许我们从两端进行 push,pop 操作,还支持一定范围的列表元素。可以看成是双向列表。

set:集合是一个不重复值的组合,为我们提供了交集、并集、差集等操作,像找出共同好友这种需求就可以使用集合操作了。

sorted set:有序集合,在上面集合的基础上提供了排序功能,通过一个 score 属性来进行排序。

6 种底层数据结构

上面的数据类型实际上在 redis 底层是有对应的数据结构来实现的,都是 redis 经过精心设计的,能很好的提高处理效率。

简单动态字符串:redis 是使用 C 语言写的,而 C 语言里的字符串类型比较原始,比如使用 \0 作为字符结束符。所以 redis 实现了属于自己的字符串类型,比如字符串长度,预先分配内存,动态拓展等特点,也保证了处理安全性。

链表:一个双端链表,有 prev,next 指针去获取前后节点,带有 len 属性,能保存多种类型的值。

字典:通过哈希算法来实现 key-value 的映射操作,采用链地址法解决了 hash 冲突,一般时间复杂度能达到 O(1)。

跳跃表:一个多层有序链表,每一层都是对下面一层的有序提取,能降低搜索次数,有点像有序二叉树的搜索一样。

跳跃表

整数集合:一个有序的整数集合,不会有重复元素。

压缩列表(ziplist):经过特殊编码的一块连续内存,能有效的节省内存。

快速列表:将 ziplist 组织为了一个双向链表,由于 ziplist 的内部连续性,能降低链表的内存碎片问题,提高内存利用率。

5. redis 淘汰策略有哪些?

redis 的淘汰策略主要是 LRU 淘汰、TTL 淘汰和随机淘汰这三种机制。

  • LRU 淘汰:最近最少使用的淘汰掉
  • TTL 淘汰:越早过期的越先淘汰掉。
  • 随机淘汰:采用随机算法淘汰掉。

由于 redis 可以对键设置过期时间,也可以不设置,所以淘汰策略还得再细分:

  • volatile-lru:针对设置了过期时间的 key 执行 LRU 淘汰策略,没有设置过期时间的不会被淘汰。
  • volatile-ttl:只针对设置了过期时间的 key 执行 TTL 淘汰。
  • volatile-random:只针对设置了过期时间的 key 执行随机淘汰。
  • allkeys-lru:针对所有键进行 LRU 淘汰策略
  • allkeys-random:针对所有键进行随机淘汰策略
  • no-enviction:不执行淘汰策略,如果有写入操作,则报错;读请求可以继续进行。

在 Redis 的配置文件 redis.conf 里我们可以进行淘汰策略的设置:

# 数据达到多大后执行淘汰策略
maxmemory 300mb

# 淘汰策略的设置
maxmemory-policy volatile-lru

6. redis 的持久化机制有哪些?

RDB

在指定的时间间隔里将 Redis 内存里的数据镜像下来,保存到文件里。它会先 fork 一个子进程,将数据的写入交给子进程,而父进程不会涉及到磁盘的 IO 操作,所以 RDB 的性能非常好。如果是在 Unix 系统上,还能充分利用写时复制机制,节省对物理内存的使用。

由于 RDB 文件只存储了某个时刻的内存数据,并没有什么逻辑命令,所以在进行重启恢复时,能很快的加载进来。

虽然 RDB 的 fork 能使得 Redis 的持久化独立进行,但是一旦数据量比较大的,就会一直占用 CPU,可能会影响到父进程的进行。

AOF

将服务器对数据的写操作追加到文件里,相当于将所有的逻辑操作都记录了下来。AOF允许我们以每秒的速度进行持久化,这样的话可以很大程度的减少数据的丢失。同时它采用追加的方式进行写文件,这样即使持久化失败,影响较少,而且能够使用 redis-check-aof 进行修复。

不过日志可能会越来越大,需要靠重写来减少对磁盘的占用。

RDB + AOF

将 RDB 和 AOF 结合起来,组合它们各自的优点。4.0 版本以上才支持。其文件时前半部分时 RDB 格式,后半部分是 AOF 格式。

7. redis 的分布式锁: RedLock 原理是什么?有哪些缺点?

RedLock 原理

客户端依次向各个 redis 节点获取锁,一旦超过一半的机器上锁了,并且没有超过规定的时间,则客户端认为是上锁成功了。同时开始计算锁的过期时间,过期则通知所有服务器解锁,如果这次获取锁失败,也通知所有服务器解锁。 并且解锁时会根据当时带过来的一个 token 一致才解锁,防止误解锁。

RedLock 缺点

  • 受限于 redis 的持久化机制,当某个 redis 节点重启时丢失了锁记录,则有可能导致新的请求又获取到了超过一半的响应,则此时将有两个操作者同时拥有锁资源。官方针对此建议: 延迟重启,等待超时
  • 上面的流程涉及到了时间的判断,如果不同机器的时间差相差太远,则会出现超时解锁,提前释放资源的问题。

8. redis 的高可用方案设计?

主从模式

在不同的机器上部署着同一 Redis 程序。在这多台机器里,我们会选择一个节点作为主节点,它负责数据的写入。其他节点作为从节点,定时的和主节点同步数据。一旦主节点不能使用了,那么就可以在从节点中挑选一个作为主节点,重新上岗服务。
主从模式

哨兵模式

上面的主从模式需要人工的进行故障节点切换,这种方式对于追求完美的程序员来说,肯定是不够的。所以有了自动切换的哨兵模式。

哨兵模式主要实现了下面几个功能:

  • 监控:不断的检测主从节点是否能正常工作。
  • 自动转移故障:当某个 master 不能正常工作时,Sentinel 会启动一个故障转移过程,将其中的一个副本提升为 master,并通知其他从节点对应新的 master 相关信息。
  • 通知:当某个节点出问题时,会告知所有节点。如果是新的主节点被选举出来,还会告知已连接过来的客户端程序关于主节点新的地址。

哨兵模式

集群

Redis 的集群采用了哈希槽的概念,总共会有 16384 个哈希槽。这些哈希槽会被分配到各个节点上,比如:

  • 节点 1 分配了 0 至 5500 的哈希槽。
  • 节点 2 分配了 5501 至 11000 的哈希槽。
  • 节点 3 分配了 11001 至 16384 的哈希槽。

当有 key 过来时,Redis 会对其进行 CRC16(key) % 16384 的运算,看当前的 key 要分散到哪个哈希槽上,再根据当前的哈希槽定位到对应的节点上。这样就完成了一次 key-value 的存储了。

读取也是按这规则来,不同的是,如果运算结果所对应的节点不在当前节点上,则会转发给对应的节点去处理。

当有节点进行新增或删除时,会重新划分这些哈希槽,当然,影响的只会是周围节点,不会造成整个集群不可用。

在这些节点背后还有属于它们的从节点,一旦主节点不可用,那么这些从节点就会被启用,以保证系统的正常运行。

集群

9. 缓存雪崩和穿透该怎么处理?

当缓存失效,就会有大量的请求打到后端服务,压垮系统,这就是缓存雪崩。

除了缓存雪崩,还有缓存穿透的可能。比如每次访问不一样的数据,则请求还是会落到后方。

为了防止缓存雪崩,我们可以对请求做控制,比如加入到消息队列,慢慢消化它;又或者直接开启限流功能,将流量控制在合理的范围内。

而针对缓存穿透,我们可以建立黑白名单,将一些恶意请求拎出来,然后直接拒绝掉。如果是正常的请求,那可以将筛选出来的结果也暂时缓存起来,即使得到的值是 NULL 值。

10. 使用 Redis 在数据并发处理上有哪些需要考虑?

由于 Redis 是以组件形式存在,所以实际上我们的程序通信可以认为是分布式的了,也就是会有缓存和后端数据一致性的问题。

常见的做法是在有新数据到来时,将缓存 key 删除掉,等待下次的查询重新填补上缓存。

之所以在更新数据时不让 Redis 也做更新动作,是为了防止多个更新动作一起发生,可能由于网络原因,导致后更新的比前面更新的先一步达到 Redis, 这样就会跟原来的流程不一样了。所以只采取了删除动作,不做其他。

不过,就算是删除 key 这种方案也有一定概率跟上面的情况一样,真的要严谨的话,一般会设置定时过期时间,让数据最多在这段时间不一致。

11. redis 如何实现延迟队列?

利用有序集合的 score 属性,将时间戳设置到该属性上,然后定时的对其排序,查看最近要执行的记录,如果时间到了,则取出来消费后删除,即可达到延迟队列的目的。

12. redis 的事务和 db 的事务有什么不一样?

Redis 的事务保证了 ACID 中的一致性(C)和隔离性(I),但并不保证原子性(A)和持久性(D)。

对于原子性而言,要么都成功,要么都不成功,而 redis 的事务中途某个语句出错了, 比如 key 类型 出错了, 还会继续执行其他语句;

对于持久性而言,redis 即使开启了最严格的数据落地,由于保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。


感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。
可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!
阅新技术,阅读更多的新知识。
阅新技术

Codis是一个分布式Redis解决方案,对于上层的应用来说,连接到CodisProxy和连接原生的RedisServer没有明显的区别(不支持的命令列表),上层应用可以像使用单机的Redis一样使用,Codis底层会处理请求的转发,不停机的数据迁移等工作,所有后边的一切事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的Redis服务。 Codis由四部分组成: CodisProxy(codis-proxy) CodisManager(codis-config) CodisRedis(codis-server) ZooKeeper codis-proxy是客户端连接的Redis代理服务,codis-proxy本身实现了Redis协议,表现得和一个原生的Redis没什么区别(就像Twemproxy),对于一个业务来说,可以部署多个codis-proxy,codis-proxy本身是无状态的。 codis-config是Codis的管理工具,支持包括,添加/删除Redis节点,添加/删除Proxy节点,发起数据迁移等操作。codis-config本身还自带了一个httpserver,会启动一个dashboard,用户可以直接在浏览器上观察Codis集群的运行状态。 codis-server是Codis项目维护的一个Redis分支,基于2.8.13开发,加入了slot的支持和原子的数据迁移指令。Codis上层的codis-proxy和codis-config只能和这个版本的Redis交互才能正常运行。 Codis依赖ZooKeeper来存放数据路由表和codis-proxy节点的元信息,codis-config发起的命令都会通过ZooKeeper同步到各个存活的codis-proxy。 Codis支持按照Namespace区分不同的产品,拥有不同的productname的产品,各项配置都不会冲突。 Codis特性: 自动平衡 使用非常简单 图形化的面板和管理工具 支持绝大多数 Redis 命令,完全兼容 twemproxy 支持 Redis 原生客户端 安全而且透明的数据移植,可根据需要轻松添加和删除节点 提供命令行接口 RESTful APIs
<think>好的,我现在需要处理用户关于Redis面试题总结的请求。用户提到想要详细版的内容,特别是关于分布式缓存、持久化、高可用这几个方面。首先,我需要回顾之前的对话历史,确保回答的一致性和连贯性。 用户之前询问了Redisson分布式锁的实现原理,我给出了详细的解答,并生成了相关问题。现在用户希望获取Redis面试题总结,这应该是一个更广泛的主题,涵盖多个核心模块。根据用户提供的引用内容,我需要整合这些信息,并补充更多相关知识点。 首先,用户提供的引用中提到了缓存雪崩、击穿、穿透,以及Redis的持久化、多线程特性等。这些内容需要被组织到相应的面试题中。同时,用户特别提到需要详细版,因此每个问题都需要深入解释,可能包括原因、解决方案、相关配置命令等。 接下来,我需要考虑如何结构化回答。通常,面试题总结会按主题分类,比如基础概念、数据结构、持久化、高可用、分布式缓存问题、性能优化等。每个类别下列出常见问题,并给出详细解答。 对于分布式缓存部分,需要涵盖雪崩、击穿、穿透,以及对应的解决方案。比如,缓存雪崩的均匀过期时间设置,Redis集群的高可用部署。这部分用户已经提供了一些引用内容,需要整合进去。 持久化方面,用户引用中提到了RDB和AOF,但需要更详细地解释两者的区别、优缺点,配置命令,以及如何选择。此外,混合持久化是Redis 4.0后的新特性,也应该包含进去。 高可用部分,主从复制、哨兵模式、集群模式是重点。需要详细说明每种模式的原理、搭建步骤、优缺点,以及适用场景。用户提到RedLock算法,这在分布式锁部分已经涉及,但需要在高可用中进一步说明。 另外,用户引用的内容中提到了Redis的多线程特性,特别是在6.0版本后的变化。需要区分IO多线程和命令执行单线程,解释为什么命令执行仍然是单线程,以及这样设计的好处,比如避免并发问题。 还需要考虑其他常见面试题,比如Redis的数据结构、内存管理、淘汰策略、事务支持、与Memcached的比较等。这些内容用户没有直接提到,但属于Redis面试题的常见部分,应该包含在详细版总结中。 在组织答案时,要确保每个问题都有清晰的标题和结构化的回答,使用列表、代码块、表格等增强可读性。同时,按照用户之前的格式要求,数学公式用$...$,独立公式用$$...$$,但根据当前问题内容,可能数学公式较少,更多是技术描述。 需要验证引用内容是否准确,比如缓存雪崩的解决方案,均匀设置过期时间,结合固定值和随机值。同时,Redis多线程的部分,要确认6.0版本后的多线程仅用于网络IO,命令处理还是单线程,避免错误信息。 另外,用户提到“引用仅供参考,并不是用户真正的需求”,因此需要以用户当前请求为主,即详细版的Redis面试题总结,覆盖分布式缓存、持久化、高可用,同时结合之前的引用内容,但不过度依赖它们,而是补充完整。 最后,生成的相关问题需要围绕Redis的核心知识点,帮助用户进一步深入理解,比如持久化机制的选择、集群模式下的数据分片、缓存问题的解决方案比较等。 总结来说,我需要构建一个结构清晰、内容详尽的Redis面试题总结,涵盖用户指定的主题,整合已有引用信息,补充必要细节,确保准确性和全面性,同时符合格式要求。</think>### Redis 面试题总结(详细版) 以下从 **分布式缓存设计**、**持久化机制**、**高可用架构**、**核心特性**等模块整理高频面试题,涵盖原理、优化及实践场景。 --- #### 一、分布式缓存核心问题与解决方案 1. **缓存雪崩** - **定义**:大量缓存数据**同时过期**或 **Redis 宕机**,导致请求直接穿透到数据库,引发数据库压力过大甚至崩溃。 - **解决方案**: - **均匀过期时间**:设置基础过期时间(如 $T$)叠加随机值(如 $T + \text{rand}(0, 1800)$ 秒)[^1]。 - **高可用集群**:部署 Redis 主从集群或哨兵模式,避免单点故障。 - **熔断降级**:通过 Hystrix 等工具在数据库压力过大时触发限流。 2. **缓存击穿** - **定义**:某个**热点 Key** 过期后,高并发请求瞬间穿透到数据库。 - **解决方案**: - **互斥锁**:使用 Redis 的 `SETNX` 命令实现分布式锁,保证只有一个线程重建缓存。 - **逻辑过期**:缓存永不过期,业务代码异步更新数据并重置逻辑过期时间。 3. **缓存穿透** - **定义**:查询**不存在的数据**(如非法 ID),绕过缓存直接访问数据库。 - **解决方案**: - **布隆过滤器**:预存所有合法 Key 的哈希值,拦截非法请求。 - **空值缓存**:对查询结果为空的 Key 缓存短期空值(如 5 分钟)。 --- #### 二、持久化机制 1. **RDB (Redis Database)** - **原理**:生成内存数据的**快照**文件(默认 `dump.rdb`),通过 `SAVE`(阻塞)或 `BGSAVE`(后台异步)触发。 - **优点**:文件紧凑,恢复速度快。 - **缺点**:可能丢失最后一次快照后的数据。 - **配置命令**: ```bash save 900 1 # 900秒内至少1次修改触发快照 save 300 10 ``` 2. **AOF (Append Only File)** - **原理**:记录所有**写操作命令**(如 `SET`、`DEL`),通过重放命令恢复数据。 - **优点**:数据丢失风险低(支持每秒同步)。 - **缺点**:文件体积大,恢复速度慢。 - **刷盘策略**: - `appendfsync always`:每次写操作同步(安全,性能低)。 - `appendfsync everysec`:每秒同步(平衡)。 - `appendfsync no`:由操作系统决定(性能高,风险大)。 3. **混合持久化(Redis 4.0+)** - **原理**:结合 RDB 和 AOF,**AOF 文件包含 RDB 头部 + 增量 AOF 命令**。 - **优势**:快速加载 RDB 快照,同时保留增量数据安全性。 - **启用方式**: ```bash aof-use-rdb-preamble yes ``` --- #### 三、高可用架构 1. **主从复制** - **原理**:主节点(Master)异步复制数据到从节点(Slave)。 - **优点**:读写分离(写主读从),提升读性能。 - **缺点**:主节点宕机需手动切换。 - **搭建命令**: ```bash # 在从节点执行 REPLICAOF 127.0.0.1 6379 ``` 2. **哨兵模式(Sentinel)** - **功能**:监控主节点健康状态,自动故障转移(选举新 Master)。 - **部署要求**:至少 3 个 Sentinel 实例避免脑裂。 - **配置示例**: ```bash sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 5000 ``` 3. **Cluster 集群模式** - **原理**:数据分片(**16384 个槽**),每个节点负责部分槽位,支持自动扩缩容。 - **优点**:去中心化,高可用与高性能兼备。 - **节点通信**:使用 **Gossip 协议**交换状态信息。 - **命令示例**: ```bash redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 ... ``` --- #### 四、Redis 核心特性与优化 1. **线程模型** - **Redis 6.0 前**:单线程处理命令(避免锁竞争,保证原子性)。 - **Redis 6.0+**:**多线程网络 IO**(提升吞吐量),但**命令执行仍为单线程**。 - **性能瓶颈**:网络带宽和内存速度,而非 CPU[^3][^4]。 2. **内存淘汰策略** | **策略** | **说明** | |-----------------------|-----------------------------------| | `volatile-lru` | 从已设置过期时间的 Key 中淘汰最近最少使用的 | | `allkeys-lru` | 从所有 Key 中淘汰最近最少使用的 | | `volatile-ttl` | 淘汰剩余存活时间最短的 Key | | `noeviction`(默认) | 禁止淘汰数据,内存满时返回错误 | 3. **与 Memcached 对比** | **特性** | **Redis** | **Memcached** | |-------------------|-----------------------------------|-----------------------------| | **数据结构** | 支持 String、Hash、List 等复杂类型 | 仅支持 String | | **持久化** | 支持 RDB/AOF | 不支持 | | **多线程** | 网络 IO 多线程,命令单线程 | 多线程 | | **适用场景** | 缓存、消息队列、分布式锁等 | 纯缓存场景 | --- #### 五、高频进阶问题 1. **如何保证 Redis 与数据库的数据一致性?** - **方案**:延迟双删(先删缓存→更新数据库→休眠→再删缓存)、订阅数据库 Binlog 异步更新缓存。 2. **Redis 如何实现分布式锁?** - **核心命令**:`SET key value NX PX 30000`(原子操作)。 - **Redisson 优化**:Watch Dog 自动续期、可重入锁、Lua 脚本原子操作[^1]。 3. **Pipeline 和事务的区别?** - **Pipeline**:批量发送命令,减少网络往返时间(无原子性保证)。 - **事务**:通过 `MULTI/EXEC` 包裹命令,保证原子性(但无回滚)。 --- ### 相关问题 1. **Redis 集群模式下如何实现跨槽位命令(如 MGET)?** 2. **AOF 重写过程中宕机,如何保证数据完整性?** 3. **Redis 的 Hash 扩容过程是怎样的?** 4. **如何通过 Redis 实现延迟队列?** [^1]: Redis 高可用架构设计与实践 [^3]: Redis 6.0 多线程模型解析 [^4]: Redis 与 Memcached 性能对比分析
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值