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. 执行 savebgsave 命令
  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.