Skip to content

Redis 已完结 ​

基础 ​

什么是 Redis? ​

  1. Redis 是一个基于内存的数据库,属于 NoSQL。
  2. 具有多种数据结构:字符串、哈希、列表、集合、有序集合、bitmap等

Redis 的优势? ​

  1. 读写速度快
  2. 支持多种数据结构
  3. 具有多种功能:集群、数据持久化、队列

为什么用 Redis 作为 MySQL 缓存? ​

Redis 存储在内存,不需要磁盘 IO。可以提高访问速度。

Redis 作为旁路缓存,怎么保证缓存一致的? ​

在更新数据库时主动更新或删除缓存

Redis 作为旁路缓存时,是先删缓存还是先更新数据库? ​

先操作数据库,然后再删除缓存

避免其他线程在删除完缓存后,更新数据库前重建旧的缓存。

Redis 相比 Memcached 有哪些优势? ​

  1. 数据结构的支持:Redis 有多种数据结构,Memcached 仅支持简单字符串。
  2. 持久化:Redis 有 AOF 和 RDB 机制进行持久化,Memcached 只保存在内存中。
  3. Lua 支持:Redis 支持 Lua 脚本。
  4. 多线程:Redis 是单线程的,Memcached 是多线程,CPU 利用率较高。

Redis支持事务回滚吗? ​

不支持

Redis 的数据类型有什么? ​

  1. String
  2. Hash:唯一键值对(不排序的 Zset)
  3. List 列表:双端链表
  4. Set 集合:唯一的值
  5. Zset 有序集合:通过 score 排序的集合

Redis 过期数据的返回值? ​

返回 nil

String ​

set 一个已有的值会发生什么? ​

覆盖原有值

浮点数在 String 中用什么表示? ​

根据浮点数长度不同,使用 Raw 或 EMBSTR 编码

String 最大多大? ​

官网注明最大 512 MB

Redis 的字符串如何实现? ​

字符串底层是 String 对象。有三种编码方式:INT、RAW、EMBSTR。

  1. 整数使用 INT 编码,ptr 中直接存数值
  2. 小于阈值(44字节)的字符串使用 EMBSTR 编码,为 SDS 和 head 申请连续空间,单次申请效率更高。
  3. 大于阈值使用 RAW 编码,最大可存 512 MB

SDS 要有什么用? ​

SDS 是简单动态字符串

  1. SDS 包含字符串长度,效率更高
  2. SDS 预留部分空间,节约性能
  3. 不以 \0 作为结束条件,二进制安全

SDS 编码阈值为什么是 44 字节? ​

Redis 使用 jemelloc 分配内存,jemelloc 以 64 字节为单位。Redis 对象头信息 16 字节、SDS 非内容部分 3 字节、字符串末尾 \0 1字节,剩余部分 44 字节。

List ​

List 是完全先入先出吗? ​

List 可以双端操作,不是完全的。

List 对象底层编码方式是什么? ​

  1. 3.2 版本之前:ZIPLIST 和 LINKEDLIST
  2. 3.2 版本以后:QUICKLIST 压缩列表组成的双向链表
  3. 7.0 版本以后:优化成 LISTPACK

ZIPLIST 是如何压缩数据的? ​

ZIPLIST 是连续内存空间,不产生内存碎片,不需要额外存放指针,节约内存。

ZIPLIST 下 List 可以从后往前遍历吗? ​

可以的,List 是双端队列。

ZIPLIST 头信息中存放最后一个节点的地址,节点存放前一个节点的长度,可以反向遍历。

在ZIPLIST数据结构下,查询节点个数的时间复杂度是多少? ​

ZIPLIST 头信息中有一个字段存放节点个数,2字节,最大为 65535。

所以长度在 65535 以内是 O(1),超过需要遍历 O(n)。

LINKEDLIST编码下,查询节点个数的时间复杂度是多少? ​

O(1)

LINKEDLIST 头信息中存有节点个数。

Set ​

Set 的编码方式 ​

Set 有两种编码方式

  1. 存放的数据均为整数,且不超 512 个,使用 intset 编码
  2. 否则使用 Dict 字典编码

intset:特殊的整数数组。保证唯一且有序,使用二分查找。

Set 是有序的吗? ​

无序的。

对于 INTSET 编码的 Set 是有序的,使用 Dict 字典编码是无序的。

Hash ​

Hash 的编码是什么? ​

Hash 有两种编码方式。

  1. 对于元素较少且单个元素长度较小,使用 ZIPSET 编码。
  2. 否则使用 HashTable 编码

Hash 查找每个 key 的时间复杂度是多少? ​

  1. 对于 ZIPSET 编码,需要遍历,O(n)。
  2. 对于 HashTable 编码,O(1)。

HashTable 查找元素总数的复杂度? ​

O(1)

HashTable 头信息中有 used 字段。

一个数据在 HashTable 中的存储位置,是怎么计算的? ​

通过哈希函数算出哈希值,与掩码做与运算,获得索引值,也就是 HashTable 的存储位置。

HashTable 如何扩容? ​

通过渐进式 rehash 进行扩容。

  1. HashTable 有两个表指针,计算新的 size,并申请内存,将存放在 1 号表中。
  2. 每次 CRUD 时,都会把当前存储位置的旧表数据,重新计算哈希值,迁移到新表。
  3. 直至所有数据都迁移至一号表,释放 0 号表内存,并将 0 号表和 1 号表互换。

HashTable 扩容和缩容的条件? ​

扩容:无后台操作时,负载因子大于等于 1;后台有操作时,负载因子大于等于 5。

缩容:负载因子小于等于 0.1

Zset ​

Zset 底层有几种编码方式? ​

Zset 有两种编码:ZIPSET 和 跳表 + 字典。

  1. 当元素较少,且单个元素较小时,会使用 ZIPSET 编码。
  2. 否则使用跳表 SkipList 和字典 Dict 编码。

跳表查询节点总数的复杂度? ​

O(1)

跳表的头结点中存放了 length 字段。

跳表插入的时间复杂读 ​

O(logn)

插入前需要查找插入位置,查找的复杂度是 O(logn)

跳表的节点层高是如何决定的? ​

使用随机函数计算层高,每加一层的概率是 25%,最高为 32 层。

ZSet 为什么不使用平衡树? ​

  1. 跳表的实现比平衡树简单
  2. 平衡树需要保持平衡,导致树的旋转。
  3. 对于区间遍历,跳表比平衡树更方便

Redis 的执行 ​

Redis 是单线程还是多线程? ​

对于核心操作是单线程

对于其他模块有多线程参与,比如异步删除、解包回包等

解包:接收客户端请求并解析命令

回包:将命令执行结果发送回客户端

为什么 Redis 的核心功能使用单线程? ​

  1. Redis 是内存操作,多线程不会带来较大性能提升
  2. 多线程有上下文切换的成本和线程安全问题

为什么 Redis 单线程性能也很好? ​

  1. 内存操作,比磁盘 IO 速度快
  2. Redis 使用了高效的数据结构
  3. 采用单线程,减少上下文切换的成本,避免线程安全问题
  4. 使用了 IO 多路复用,可以同时监听多个 Socket

Redis 6.0 的多线程是默认开启的吗?主要负责哪里? ​

默认关闭的,可以修改配置文件开启。

多线程主要负责解包和回包,也就是 Socket 的的 IO 部分。

Redis 持久化 ​

RDB 和 AOF 的本质区别是什么? ​

RDB 是保存快照,AOF 是追加日志。

  1. RDB 保存的是二进制数据,AOF 是文本数据。
  2. 宕机时,RDB 容易丢失更多数据,AOF 默认配置最多丢失 1s 的数据。
  3. RDB 是二进制数据,恢复速度比 AOF 更快。
  4. RDB 需要全量保存,操作较重;AOF 是追加数据,操作比较轻。

RDB 和 AOF 只能选择一个,怎么选择? ​

主要看应用场景。

  1. 能接受分钟级别的数据丢失,使用 RDB,获得更好的性能。
  2. 如果对数据安全性要求较高,使用 AOF。

RDB 的触发时机 ​

  1. 执行 save、bgsave 命令
  2. 根据配置的周期保存
  3. 全量同步
  4. Redis 关闭之前

AOF 混合存储是什么? ​

使用 RDB 将内存数据持久化到 AOF 文件,对于运行期间的操作,使用 AOF 追加写入。得到 AOF 和 RDB 组成的日志。

简单描述 AOF 重写流程 ​

  1. 触发 AOF 重写时,子进程会读取 Redis 数据,写入到 AOF 文件。
  2. AOF 重写期间的写入操作会存放到 AOF 重写缓冲区。
  3. 主进程会将缓冲区数据发送给子进程,子进程追加写入 AOF 文件。

AOF 重写你觉得有什么不足之处? ​

AOF 重写期间的写入操作会入保存两次,AOF缓冲和AOF重写缓冲。

针对 AOF 重写的不足,你有什么优化思路呢? ​

Redis 7.0 以后使用了 MP-AOF 方案。

将 AOF 文件分为 Base AOF 和 Incr AOF。Base AOF 是历史部分,Incr 是新增部分。两者共同组成了重写后的 AOF 文件。

manifest 可以理解成 AOF 的配置清单,记录 AOF 是通过哪几部分组成的。

Redis 过期删除和内存淘汰策略 ​

Redis 是如何删除过期 key 的? ​

使用 惰性删除 + 定期删除 策略

  1. 惰性删除:取 key 时查询是否过期
  2. 定期删除:每隔一段时间删除所有的过期 key

Redis 有几种内存回收策略? ​

一种是不开启内存回收,内存满了就写入失败。

另一种是开启内存回收,分为两种。其一是基于有过期时间的数据淘汰,有 LRU、LFU、Random、TTL 算法;其二是基于所有数据淘汰,有 LRU、LFU、Random 算法。

  • LRU:最久没使用
  • LFU:最少使用
  • TTL:更早过期的、TTL 最小的

内存回收是什么时候进行的? ​

每次读写时会检查是否需要回收。

说一下 Redis 的 LRU 算法 ​

LRU 是最近最久未使用,淘汰最久未访问的数据。

Redis 对于 LRU 算法进行了优化。标准 LRU 算法需要维护双向链表,消耗较大。Redis 采用了 LRU 采样,随机取若干个数据,淘汰最久没访问的数据。

什么是 LFU 算法?为什么需要使用 LFU 算法? ​

LRU 只考虑了访问时间,忽略了访问频率。

LFU 将访问频率加入影响因素中,访问计数同时受上一次访问时间和访问频率的综合影响。

Redis 使用场景 ​

Redis 主要的应用场景是什么? ​

  1. 用于存储 Token 或 Session 实现分布式登录
  2. 进行数据的缓存
  3. 具有生存时间,适用于验证码或优惠券
  4. 生成唯一 ID
  5. 限流
  6. 排行榜
  7. 分布式锁
  8. 消息队列
  9. 共同关注
  10. 签到
  11. UV 计数
  12. 地理位置相关操作

Redis 缓存是如何使用的? ​

作为旁路缓存。查询时先查询 Redis,如果 Redis 没有数据则查询 MySQL,并缓存到 Redis。

Redis 做旁路缓存,DB 更新了应该如何操作? ​

我的项目中在更新数据库后会删除缓存,来提升缓存一致性,并为缓存加入过期时间作为备用。

我还了解过通过订阅 MySQL 的 binlog,异步更新缓存。这种方法和业务完全解耦,但是需要消息队列和消费服务,成本较高。

Redis 做秒杀场景的应用和思路 ​

  1. 记录库存,通过 Redis 高性能进行库存的记录。
  2. 作为消息队列,进行流量消峰。

Redis 可以做消息队列吗? ​

Redis 有一个 List 数据结构,可以作为轻量级消息队列。

如果业务比较轻量级,并且没有引入其他的消息队列,那么 Redis 就可以满足需求。

什么是分布式锁? ​

用于在分布式系统中控制对共享资源的访问的机制。类比单机环境下多线程的 synchronized。

Redis 分布式锁怎么实现的 ​

使用 setnx 命令,保证只有一个线程能够成功设置值,相当于这个线程获取到了锁。

分布式锁的要点是什么? ​

  1. 加锁:使用 setnx 命令,key 是锁名,value 是持有者 id,添加过期时间。
  2. 解锁:检查是否是自己的锁,然后释放锁。操作需要保证原子性,需要使用 Lua 脚本。

Lua 脚本一定能保证原子性吗? ​

Lua 本身没有原子性。但是 Redis 是单线程操作,所以 Lua 脚本的执行不会被打断,保证了操作的原子性。

Redis 作为分布式锁的优缺点? ​

优点:

  1. 性能高
  2. Redis 有 setnx 命令,实现简单
  3. Redis 有集群,避免单点故障

缺点:

  1. 超时时间不好设置,需要守护线程续约。
  2. 主从集群是异步复制的,导致不可靠

Redis 集群下如何保证分布式锁的可靠性 ​

使用红锁算法。

客户端和多个独立的Redis节点依次请求申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。

缓存穿透是什么?怎么解决? ​

缓存穿透:用户在请求一个 Redis 和数据库都不存在的数据,请求会直接到达数据库,且不会重建缓存。大量请求会导致大量的数据库压力。

解决方法:

  1. 缓存空对象:方法简单,但是会造成内存浪费。
  2. 布隆过滤器:用于查询数据是否存在,对于不存在的数据直接返回,不再查询数据库。
  3. 限流策略:限制对数据库的请求频率。

布隆过滤器是如何工作的? ​

布隆过滤器由位图数组和多个哈希函数组成。

写入数据库中,将数据通过多个哈希函数计算,将每个哈希值都在数组中标记。

在读取数据时,将数据进行哈希计算,如果数组中每个位置对应的都是 1,因为有哈希冲突的可能,所以数据可能存在。但如果不全为 1,那么数据一定不存在。

缓存击穿是什么?怎么解决? ​

缓存击穿:缓存中某个热点 key 过期了,造成大量请求直接访问数据库。

解决方法:

  1. 使用互斥锁或分布式锁:只让一个线程去查询数据库并重建缓存。
  2. 添加多级缓存,比如使用 Caffeine、OpenResty。
  3. 热点 Key 设置永不过期。

缓存雪崩是什么?怎么解决? ​

缓存雪崩:大量 Key 同时过期,或 Redis 宕机,导致大量请求直接访问数据库。

解决方法:

  1. 给不同的 Key 设置不同的 TTL 值:避免大量 Key 同时过期重建。
  2. 使用互斥锁或分布式锁:只让一个线程去查询数据库并重建缓存。
  3. 缓存预热:在非高峰期提前更新缓存
  4. 添加多级缓存:使用 Caffeine、OpenResty。
  5. 使用 Redis 集群:提高 Redis 的可用性

Redis 集群 ​

Redis 集群架构有几种? ​

主从集群、哨兵集群、切片集群。

  1. 主从集群:一台作为主节点,其余为从节点。主节点处理读写,从节点只读,主节点将数据异步同步给从节点。
  2. 哨兵集群:实现故障恢复的功能,哨兵监控主节点。
  3. 切片集群:解决数据量较大的问题,将数据分布在多个节点上,可以降低对单个节点的依赖,提高读写性能。

主从节点是如何同步的? ​

分为两种:全量同步、增量同步

  1. 全量同步:首次同步。需要将主节点所有数据同步到从节点。
  2. 增量同步:全量同步后,当主节点数据发生修改,会同步给从节点。

主从模式是同步复制还是异步复制? ​

异步

主节点收到写命令之后,先写到内部的缓冲区,然后再异步发送给从节点。

Redis 主从同步的优缺点? ​

优点:读写分离,提高性能

缺点:占用较多内存,存在单点故障

哨兵模式是什么? ​

哨兵模式主要解决主从集群存在主节点单点故障的问题。

哨兵会监控主节点状态,当主节点宕机时,会选举一个哨兵节点作为主节点。

哨兵模式的优缺点是什么? ​

优点:保证 Redis 服务的高可用

缺点:主从模式单点风险高、主从切换可能会造成数据丢失、数据量不能太大

Redis 的哈希槽是什么? ​

用于分片集群中。

Redis 集群有 2^14 个哈希槽,对每个 key 通过哈希运算决定放到哪个槽。每个 Redis 节点负责一部分的哈希槽。

Released under the MIT License.