redis与mysql差缺补漏-面试版持续更新
2026/8/14 13:59:28 网站建设 项目流程

目录

  • 1. 压缩列表介绍
  • 2. 跳表介绍
    • 2.1 核心原因:范围查询的实现难度(决定性因素)
    • 2.2 实现复杂度:Lock-Free(无锁)与易维护性
  • 3. Redis 数据同步解释一下
  • 4. 布隆过滤器原理

1. 压缩列表介绍
答:本质是使用一段连续的内存区域对数据进行编码减少开销:
zlbyte(整个压缩列表的占用字节数)- zltail(最尾部的数据距离开头的偏移量)- zllen(数据实际占用长度)- entryX(具体数据节点)- zlend(尾部结束标识)
使用:如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为底层数据结构。hash 的元素个数则为 512 个。

追问:现在压缩列表被 listpack 替代原因是?
答:原来的压缩列表存储的格式为:previous_entry_len(前一个元素的长度)- encoding(元素类型)- content(实际内容),当增加、删除、修改都会导致 previous_entry_len 的改动,导致形成多米诺骨牌效应。listpack 目前的存储格式为:total-byte(整个 listpack 占用字节)- num-element(元素总数)- 实际节点 - end-byte(实际结束节点),节点设计为 encoding(类型)- data(数据)- len(长度),避免元素修改对别的元素产生影响。

2. 跳表介绍
答:跳表如图:

当要查找节点 5 时候,会直接来到 level 2 定位,从 1 找到 5 只查询了两次,整体速度为 log n。
实现过程:当一个节点,Redis 会随机生成 0-1 之间的小数,若小于 0.25 则会升阶,并且重复直至大于 0.25 为止。如节点 1, 2, 3, 4, 5, 6,这时候对 1,第一次生成为 0.4 则 1 就在 level 0 中,对 2 第一次为 0.1,第二次为 0.2,第三次为 0.9 则他为 level 2 中。
本质有序链表但是随机层高。

追问:为什么不使用红黑树呢,要使用跳表?
答:

1. 核心原因:范围查询的实现难度(决定性因素)

这是 Redis 选择跳表最重要的原因。对于有序集合(ZSet)而言,范围查询(如ZRANGEZRANKZREVRANGE)是最高频的操作之一。

  • 在跳表中:一旦你通过索引找到了范围起始的节点,你只需要顺着最底层的链表(Level 0)向后遍历即可。因为底层链表本身就是一个有序的、包含所有元素的序列。时间复杂度为O(log N + M)(M 为返回的元素数量)。

  • 在红黑树中:虽然红黑树也是有序的,但它的结构是树形的。想要输出一个范围内的所有元素,你必须进行中序遍历(In-order Traversal)。这需要维护一个栈或父节点指针来记录回溯路径,代码实现复杂得多,且缓存局部性(Cache Locality)远不如链表连续遍历好。

2. 实现复杂度:Lock-Free(无锁)与易维护性

  • 跳表:跳表的插入和删除逻辑非常直观,只需要修改相邻节点的指针。即使需要调整层高(随机生成),也是局部修改。整个数据结构仅需约 400 行 C 语言代码即可实现所有功能。

  • 红黑树:红黑树的插入和删除涉及复杂的旋转(Rotate)颜色翻转(Color Flip)操作,需要处理多种边界情况(插入时的 3 种情况,删除时的 6 种情况)。代码量通常是跳表的数倍(约 1500-2000 行)。

  • 重要引申:Redis 是单线程(指主事件循环)模型,但跳表简单的结构使得更容易在将来进行无锁(Lock-Free)并发改造,且当前代码便于维护和 Debug。antirez 曾直言,维护红黑树在长时间运行中极易出现难以复现的指针问题。

追问:为什么不使用 B+ 树呢?
答:

  • B+ 树适合磁盘 IO场景,因为它将数据聚合在页(Page)中,降低磁盘寻道次数。

  • Redis 是纯内存数据库,数据都在内存中,不存在磁盘寻道开销。使用 B+ 树在内存中反而会因为复杂的页管理而浪费内存,且增加代码复杂度。跳表在内存中的表现更优。

追问:跳表为什么要随机层高而不是连续有规律的层高呢?
答:

  • 使用连续层高(如严格 1/2 比例)
    假设你维护一个完美的跳表,第 1 层有 N/2 个节点,第 2 层有 N/4 个节点……当你插入一个新节点时,为了维持这个严格的“1/2”比例,你可能需要把大量后续节点从底层提升到上一层,或者调整已有的索引关系。这会导致连锁反应,最坏情况下单次插入的时间复杂度会退化为O(N)
    这就像维护一个完全平衡的二叉搜索树(如 AVL 树)一样,插入后必须进行复杂的旋转操作。

  • 使用随机层高
    跳表通过抛硬币(随机数)决定新节点的层高,完全不依赖现有节点的数量和分布。插入新节点时,只需要:

    <ol> <li> <p>在底层链表中插入节点。</p> </li> <li> <p>根据随机值,决定这个节点出现在哪几层索引中。</p> </li> <li> <p>将新节点“链入”这几层索引的对应位置(只修改前后指针)。<br /> 整个过程是<strong>纯局部操作</strong>,时间复杂度稳定在 <strong>O(log N)</strong> 的期望值,且<strong>无需移动或调整任何其他节点的层高</strong>。</p> </li> </ol> </li>

3. Redis 数据同步解释一下

答:前置了解,AOF 以及 RDB 快照。AOF 日志是增量,每次记录 Redis 的执行操作,一般有 everysec、always、no 三种模式,常用的是 everysec 每秒记录一次,若宕机只会损失一秒的数据。
RDB 快照,顾名思义对当时情况进行拍照,可以通过 save() 或者 bgsave() 调用。
自服务会在:1. 初始化;2. 缺少较多数据即偏移量过旧。触发全量增长。
Redis 有一个环形缓冲区,环形缓冲区存放 Redis 执行命令,当偏移量不存在于环形缓冲区则会触发全量复制。一般流程为:数据执行 - AOF 缓存 - replication backlog - 根据策略 AOF 刷盘。

4. 布隆过滤器原理
答:

1. 添加元素(存指纹)
假设我们有一个 16 位的空数组,以及 3 个哈希函数(hash1hash2hash3)。当要添加字符串apple时:

  • 分别计算三个哈希值,得到三个位置,比如:2813

  • 将数组中这些位置都设为 1

2. 查询元素(查指纹)
当要检查banana是否存在时,同样计算它的三个哈希值,得到位置:2713

  • 检查这些位置:位置 2 和 13 都是 1,但位置 7 是 0

  • 结论:只要有任意一个位置为 0,就说明banana绝对不存在

3. 误判(指纹冲突)
当要检查grape时,计算位置为:2813

  • 检查这些位置:全部都是 1

  • 结论:布隆过滤器会告诉你grape可能存在

  • 但真相可能是:这些 1 是apple和其他元素留下的,grape根本没存过。这就是误判

5.redis使用set nx实现分布式锁
答:set nx本质就是如果key 存在则无法创建达到锁的效果,但是只用这一句话来实现分布式锁依旧有问题:
1.会意外释放到别的人创建的锁---创建特定id
2.会导致多个线程同时认为自己拿到锁---加锁原子性
3.执行到一半锁过期---看门狗
所以目前常用直接使用redission 或者是lua脚本

Mysql相关内容:
1.

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询