Redis 面试题库

文章目录

      • [1. Redis 中的 Big Key 和热点 key 问题是什么?如何解决?](#1. Redis 中的 Big Key 和热点 key 问题是什么?如何解决?)
      • [2. Redis 的持久化机制有哪些?Redis 在生成 RDB 文件时如何处理请求?](#2. Redis 的持久化机制有哪些?Redis 在生成 RDB 文件时如何处理请求?)
      • [3. Redis 主从复制的实现原理是什么?主从复制的常见拓扑结构有哪些?Redis 复制延迟的常见原因有哪些?](#3. Redis 主从复制的实现原理是什么?主从复制的常见拓扑结构有哪些?Redis 复制延迟的常见原因有哪些?)
      • [4. Redis 的哨兵机制是什么?Redis Cluster 模式与 Sentinel 模式的区别是什么?](#4. Redis 的哨兵机制是什么?Redis Cluster 模式与 Sentinel 模式的区别是什么?)
      • [5. Redis 集群的实现原理是什么?Redis 集群会出现脑裂问题吗?在 Redis 集群中,如何根据键定位到对应的节点?](#5. Redis 集群的实现原理是什么?Redis 集群会出现脑裂问题吗?在 Redis 集群中,如何根据键定位到对应的节点?)
      • [6. Redis 中如何实现分布式锁?分布式锁可能遇到哪些问题?如何解决?Red Lock 是什么?说说 Redisson 分布式锁的原理?](#6. Redis 中如何实现分布式锁?分布式锁可能遇到哪些问题?如何解决?Red Lock 是什么?说说 Redisson 分布式锁的原理?)
      • [7. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?Redis 中如何保证缓存与数据库的数据一致性?](#7. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?Redis 中如何保证缓存与数据库的数据一致性?)
      • [8. Redis String 类型的底层实现是什么?(SDS) Redis 字符串类型的最大值大小是多少?Redis 中 EMBSTR 对象的阈值设置为何为 44?其调整历史是什么?](#8. Redis String 类型的底层实现是什么?(SDS) Redis 字符串类型的最大值大小是多少?Redis 中 EMBSTR 对象的阈值设置为何为 44?其调整历史是什么?)
      • [9. Redis 中常见的数据类型有哪些?Redis 中跳表的实现原理是什么?Redis Zset 的实现原理是什么?为什么使用跳表而不是红黑树或 B+ 树?Redis 的 hash 是什么?](#9. Redis 中常见的数据类型有哪些?Redis 中跳表的实现原理是什么?Redis Zset 的实现原理是什么?为什么使用跳表而不是红黑树或 B+ 树?Redis 的 hash 是什么?)
      • [10. Redis List 类型的常见操作命令有哪些?如何在 Redis 中实现队列和栈数据结构?Redis 中的 Ziplist、Quicklist 和 ListPack 数据结构的特点是什么?](#10. Redis List 类型的常见操作命令有哪些?如何在 Redis 中实现队列和栈数据结构?Redis 中的 Ziplist、Quicklist 和 ListPack 数据结构的特点是什么?)
      • [11. Redis 支持事务吗?如何实现?Redis 事务与关系型数据库事务的主要区别是什么?](#11. Redis 支持事务吗?如何实现?Redis 事务与关系型数据库事务的主要区别是什么?)
      • [12. Redis 数据过期后的删除策略是什么?Redis 中有哪些内存淘汰策略?](#12. Redis 数据过期后的删除策略是什么?Redis 中有哪些内存淘汰策略?)
      • [13. Redis 的 Lua 脚本功能是什么?如何使用?Redis 的 Pipeline 功能是什么?Redis 中原生批处理命令(MSET、MGET)与 Pipeline 的区别是什么?](#13. Redis 的 Lua 脚本功能是什么?如何使用?Redis 的 Pipeline 功能是什么?Redis 中原生批处理命令(MSET、MGET)与 Pipeline 的区别是什么?)
      • [14. Redis 的订阅发布功能是什么?你了解吗?](#14. Redis 的订阅发布功能是什么?你了解吗?)
      • [15. 如何使用 Redis 快速实现排行榜?如何使用 Redis 快速实现布隆过滤器?如何使用 Redis 统计大量用户唯一访问量(UV)?Redis 中的 Geo 数据结构是什么?](#15. 如何使用 Redis 快速实现排行榜?如何使用 Redis 快速实现布隆过滤器?如何使用 Redis 统计大量用户唯一访问量(UV)?Redis 中的 Geo 数据结构是什么?)
      • [16. Redis 为什么这么快?为什么 Redis 设计为单线程?6.0 版本为何引入多线程?Redis 通常应用于哪些场景?](#16. Redis 为什么这么快?为什么 Redis 设计为单线程?6.0 版本为何引入多线程?Redis 通常应用于哪些场景?)
      • [17. Redis 和 Memcached 有哪些区别?](#17. Redis 和 Memcached 有哪些区别?)
      • [18. Redis 中的内存碎片化是什么?如何进行优化?Redis 的虚拟内存(VM)机制是什么?](#18. Redis 中的内存碎片化是什么?如何进行优化?Redis 的虚拟内存(VM)机制是什么?)
      • [19. Redis 性能瓶颈时如何处理?你在项目中使用的 Redis 客户端是什么?](#19. Redis 性能瓶颈时如何处理?你在项目中使用的 Redis 客户端是什么?)
      • [20. Redis 源码中有哪些巧妙的设计,举几个典型的例子?](#20. Redis 源码中有哪些巧妙的设计,举几个典型的例子?)
    • [Redis 和 ZooKeeper 对比](#Redis 和 ZooKeeper 对比)

1. Redis 中的 Big Key 和热点 key 问题是什么?如何解决?

总结:Redis Big Key 是指占用内存较大的 key,通常指单个 key 的 value 大小超过 10KB,或者包含大量元素的集合类型。Big Key 会导致内存占用高、阻塞操作、网络传输慢、主从同步延迟等问题。解决方案包括:1. 拆分大 key,将大 key 拆分为多个小 key;2. 压缩 value,使用压缩算法减少内存占用;3. 设置过期时间,定期清理;4. 使用合适的数据结构,避免使用不必要的大集合;5. 监控和预防,建立监控机制,及时发现和处理 Big Key。

总结:Redis 热点 key 是指访问频率极高的 key,可能导致单个 Redis 节点压力过大,影响性能。解决方案包括:1. 本地缓存,在应用层缓存热点数据;2. 多级缓存,使用多级缓存架构;3. 读写分离,将读请求分散到多个从节点;4. 分片,将热点 key 分散到多个 Redis 节点;5. 限流,对热点 key 的访问进行限流;6. 监控和预警,及时发现热点 key。

2. Redis 的持久化机制有哪些?Redis 在生成 RDB 文件时如何处理请求?

总结:Redis 提供了两种持久化机制:1. RDB(Redis Database),快照方式,定期将内存数据保存到磁盘;2. AOF(Append Only File),追加方式,记录每个写操作。RDB 适合备份和恢复,文件小,恢复快,但可能丢失数据。AOF 适合数据安全,可以保证数据不丢失,但文件大,恢复慢。生产环境通常同时使用两种方式,RDB 用于备份,AOF 用于数据安全。

总结 :Redis 在生成 RDB 文件时使用 BGSAVE 命令,通过 fork 子进程来执行,主进程继续处理请求。fork 使用写时复制(Copy-On-Write)机制,只有在数据被修改时才复制内存页,减少内存占用。生成 RDB 期间,主进程继续处理请求,写操作会触发内存页复制,但不会阻塞读操作。如果数据修改频繁,可能导致内存占用增加和性能下降。

3. Redis 主从复制的实现原理是什么?主从复制的常见拓扑结构有哪些?Redis 复制延迟的常见原因有哪些?

总结 :Redis 主从复制是主节点(Master)将数据复制到从节点(Slave)的过程。复制过程包括:1. 从节点发送 PSYNC 命令请求同步;2. 主节点执行 BGSAVE 生成 RDB 文件;3. 主节点将 RDB 文件发送给从节点;4. 从节点加载 RDB 文件;5. 主节点将后续的写命令发送给从节点(命令传播)。主从复制支持全量复制和增量复制,增量复制通过复制偏移量和复制积压缓冲区实现。

主从复制的常见拓扑结构

  1. 一主一从

    • 一个主节点,一个从节点
    • 适合数据备份和读写分离
    • 缺点:从节点故障时无法提供读服务
  2. 一主多从

    • 一个主节点,多个从节点
    • 适合读多写少的场景,提高读性能
    • 缺点:从节点过多会增加主节点压力
  3. 树状结构(主-从-从)

    • 主节点下有从节点,从节点下还有从节点
    • 减少主节点压力,适合大规模部署
    • 缺点:数据同步延迟可能增加

Redis 复制延迟的常见原因

  1. 网络延迟:主从节点之间的网络延迟
  2. 主节点写压力大:主节点写操作频繁,从节点同步跟不上
  3. 从节点性能不足:从节点处理能力弱,同步速度慢
  4. 复制积压缓冲区不足:缓冲区大小不够,导致全量复制
  5. 从节点数量过多:多个从节点同时同步,主节点压力大

4. Redis 的哨兵机制是什么?Redis Cluster 模式与 Sentinel 模式的区别是什么?

总结:Redis 哨兵(Sentinel)是 Redis 的高可用解决方案,用于监控主从节点的健康状态,并在主节点故障时自动进行故障转移。哨兵可以监控多个主从集群,通过投票机制选举新的主节点,并通知客户端新的主节点地址。哨兵支持自动故障转移、配置提供、通知等功能,保证 Redis 服务的高可用性。

Redis Cluster 模式与 Sentinel 模式的区别

特性 Sentinel 模式 Cluster 模式
架构 主从 + 哨兵 无中心化集群
数据分片 不支持,所有数据在主节点 支持,16384个哈希槽分片
水平扩展 不支持,只能垂直扩展 支持,可以动态添加节点
高可用 通过哨兵实现故障转移 通过集群实现故障转移
数据一致性 主从复制,可能短暂不一致 每个槽位保证一致性
适用场景 中小规模,数据量不大 大规模,需要水平扩展
复杂度 相对简单 相对复杂

5. Redis 集群的实现原理是什么?Redis 集群会出现脑裂问题吗?在 Redis 集群中,如何根据键定位到对应的节点?

总结 :Redis 集群采用无中心化的分布式架构,使用哈希槽(Hash Slot)分片,将 16384 个槽位分配到多个节点。客户端通过 CRC16(key) % 16384 计算 key 所属的槽位,然后路由到对应的节点。集群支持节点故障检测和自动故障转移,通过 Gossip 协议进行节点间通信。集群模式支持水平扩展,可以动态添加或删除节点。

总结:Redis 集群可能出现脑裂问题,但通过机制可以避免。脑裂是指集群被分割成多个部分,每个部分都认为自己是主节点,导致数据不一致。Redis 集群通过以下机制避免脑裂:1. 大多数原则,只有获得大多数节点支持的节点才能成为主节点;2. 故障检测,节点定期发送心跳,检测故障;3. 自动故障转移,主节点故障时,从节点自动提升为主节点。如果网络分区导致集群被分割,只有包含大多数节点的部分可以继续服务,其他部分会停止服务,避免数据不一致。

在 Redis 集群中,如何根据键定位到对应的节点

  1. 计算槽位

    • 使用公式:slot = CRC16(key) % 16384
    • CRC16 是循环冗余校验算法,将 key 转换为 0-16383 之间的槽位号
  2. 查找节点

    • 根据槽位号查找对应的节点
    • 每个节点维护一个槽位映射表
  3. 重定向机制

    • 如果 key 不在当前节点,返回 MOVED <slot> <ip>:<port> 错误
    • 客户端根据错误信息重定向到正确的节点
  4. ASK 重定向

    • 在集群迁移过程中,可能返回 ASK <slot> <ip>:<port> 错误
    • 客户端需要先发送 ASKING 命令,再执行操作

6. Redis 中如何实现分布式锁?分布式锁可能遇到哪些问题?如何解决?Red Lock 是什么?说说 Redisson 分布式锁的原理?

总结 :Redis 分布式锁可以通过 SETNX 命令或 Redisson 实现。常见问题包括:1. 死锁(锁过期时间设置不当);2. 误删(删除其他线程的锁);3. 锁续期(业务执行时间超过锁过期时间)。解决方案包括:使用 UUID 标识锁、使用看门狗机制自动续期、使用 Red Lock 算法保证高可用。Redisson 分布式锁通过 Lua 脚本保证原子性,使用看门狗机制自动续期,支持可重入锁。

回答

Redis 分布式锁的实现方式

  1. 使用 SETNX 命令

    • SETNX key value:如果 key 不存在则设置,返回 1;否则返回 0
    • 设置过期时间:EXPIRE key seconds
    • 释放锁:DEL key
  2. 使用 SET 命令(推荐)

    • SET key value NX EX seconds:原子性设置 key 和过期时间
    • 避免 SETNX + EXPIRE 的非原子性问题
  3. 使用 Redisson

    • 封装了分布式锁的实现,支持自动续期、可重入等特性

分布式锁可能遇到的问题

  1. 死锁

    • 问题:锁过期时间设置不当,业务未执行完锁已过期
    • 解决:使用看门狗机制自动续期,或设置合理的过期时间
  2. 误删锁

    • 问题:线程 A 的锁过期,线程 B 获取锁,线程 A 执行完后删除线程 B 的锁
    • 解决:使用 UUID 标识锁,删除时验证 value 是否匹配
  3. 锁续期

    • 问题:业务执行时间不确定,可能超过锁过期时间
    • 解决:使用看门狗机制自动续期

Red Lock 算法

Red Lock 是 Redis 官方提出的分布式锁算法,用于在多个 Redis 节点上实现分布式锁,保证高可用。

实现原理

  1. 获取锁

    • 客户端向 N 个 Redis 节点(N 为奇数,通常为 5)发送获取锁的请求
    • 使用相同的 key 和 value,设置相同的过期时间
    • 如果大多数节点(至少 N/2+1 个)成功获取锁,则认为获取成功
  2. 释放锁

    • 客户端向所有节点发送释放锁的请求
    • 即使部分节点失败,也要尝试释放所有节点的锁

Red Lock 的优势

  1. 高可用:即使部分节点故障,只要大多数节点正常,锁仍然可用
  2. 防止单点故障:不依赖单个 Redis 节点
  3. 保证安全性:大多数节点获取锁成功,保证锁的唯一性

Redisson 分布式锁的原理

  1. Lua 脚本保证原子性

    • 使用 Lua 脚本执行 SET key value NX EX seconds,保证原子性
    • 使用 UUID + 线程 ID 作为 value,标识锁的持有者
  2. 看门狗机制自动续期
    总结:Redis 的看门狗机制是 Redisson 分布式锁中的自动续期机制,用于防止业务执行时间超过锁的过期时间导致锁被提前释放。看门狗会启动一个后台线程,每隔锁有效期的 1/3 时间执行一次续期,默认锁有效期是 30 秒,每 10 秒续期一次,直到锁被释放或线程中断。

  3. 可重入锁

    • 支持同一线程多次获取锁,使用计数器记录重入次数
    • 释放锁时递减计数器,计数器为 0 时真正释放锁
  4. 公平锁

    • 使用队列维护等待获取锁的线程
    • 按照请求顺序分配锁,保证公平性

7. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?Redis 中如何保证缓存与数据库的数据一致性?

缓存击穿、缓存穿透和缓存雪崩

  1. 缓存击穿

    • 定义:热点数据过期,大量请求同时穿透到数据库
    • 原因:热点数据过期,大量并发请求同时查询数据库
    • 解决方案
      • 使用分布式锁,只允许一个线程查询数据库
      • 热点数据永不过期,或异步更新
      • 使用本地缓存(如 Caffeine)作为二级缓存
  2. 缓存穿透

    • 定义:查询不存在的数据,缓存未命中,大量请求打到数据库
    • 原因:恶意请求或业务逻辑问题,查询不存在的数据
    • 解决方案
      • 缓存空值,设置较短的过期时间(如 5 分钟)
      • 使用布隆过滤器,过滤不存在的数据
      • 参数校验,在业务层过滤无效请求
  3. 缓存雪崩

    • 定义:大量缓存同时过期,大量请求同时打到数据库
    • 原因:缓存过期时间设置相同,或 Redis 故障
    • 解决方案
      • 设置随机过期时间,避免同时过期
      • 使用多级缓存,本地缓存 + Redis + 数据库
      • Redis 高可用,使用主从、哨兵或集群模式
      • 限流降级,保护数据库

总结:缓存和数据库一致性是分布式系统中的经典问题。主要方案包括:1. Cache-Aside(旁路缓存)模式,先更新数据库再删除缓存;2. Write-Through(写透)模式,同时更新缓存和数据库;3. Write-Back(写回)模式,先更新缓存,异步更新数据库;4. 延迟双删策略,更新数据库后延迟删除缓存;5. 消息队列异步更新,通过消息队列保证最终一致性。最佳实践是使用 Cache-Aside 模式,先更新数据库再删除缓存,避免缓存穿透和缓存雪崩。

8. Redis String 类型的底层实现是什么?(SDS) Redis 字符串类型的最大值大小是多少?Redis 中 EMBSTR 对象的阈值设置为何为 44?其调整历史是什么?

总结:Redis String 类型使用 SDS(Simple Dynamic String)作为底层实现。SDS 相比 C 字符串具有长度信息、二进制安全、预分配空间等优势。Redis 字符串最大值为 512MB。EMBSTR 对象阈值设置为 44 字节是为了优化小字符串的内存布局,减少内存分配次数,提高性能。

回答

SDS(Simple Dynamic String)的结构

SDS 是 Redis 自定义的字符串结构,包含以下字段:

  • len:字符串长度
  • free:剩余空间长度
  • buf[]:字符数组,存储实际数据

SDS 的优势

  1. O(1) 获取长度:通过 len 字段直接获取,不需要遍历
  2. 二进制安全 :可以存储任意二进制数据,不依赖 \0 结束符
  3. 预分配空间:减少内存重新分配次数,提高性能
  4. 空间预分配策略
    • 如果修改后长度 < 1MB,分配 2 倍空间
    • 如果修改后长度 >= 1MB,分配 len + 1MB 空间

Redis 字符串类型的最大值

  • Redis 字符串最大值为 512MB
  • 这是 Redis 的限制,防止单个 key 占用过多内存

EMBSTR 对象的阈值设置

  1. EMBSTR 对象

    • 当字符串长度 <= 44 字节时,使用 EMBSTR 编码
    • EMBSTR 将 RedisObject 和 SDS 连续存储在同一块内存中
    • 只需要一次内存分配,提高性能
  2. 为什么是 44 字节

    • RedisObject 占用 16 字节(64 位系统)
    • SDS 结构占用:len(1) + free(1) + buf(1) = 3 字节(最小)
    • 实际可用空间:64 - 16 - 3 = 45 字节(考虑内存对齐)
    • 为了安全,设置为 44 字节
  3. 调整历史

    • Redis 3.2 之前:EMBSTR 阈值是 39 字节
    • Redis 3.2 之后:优化为 44 字节,提高小字符串的性能
    • 原因:优化内存布局,减少内存分配次数

9. Redis 中常见的数据类型有哪些?Redis 中跳表的实现原理是什么?Redis Zset 的实现原理是什么?为什么使用跳表而不是红黑树或 B+ 树?Redis 的 hash 是什么?

总结:Redis 支持 5 种基本数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Sorted Set(有序集合)。还有 Bitmap、HyperLogLog、Geospatial 等高级数据类型。每种数据类型都有特定的应用场景和操作命令。

总结:Redis 跳表(Skip List)是一种有序数据结构,用于实现 Sorted Set。跳表通过多层索引加速查找,每层都是有序链表,上层索引是下层索引的抽样。查找时从顶层开始,逐层向下查找,时间复杂度 O(log n)。跳表比红黑树实现简单,性能接近,适合 Redis 的场景。

为什么 Redis Zset 使用跳表而不是红黑树或 B+ 树

  1. 实现简单

    • 跳表实现比红黑树简单,代码量少,易于维护
    • 红黑树需要处理复杂的旋转操作,跳表只需要简单的指针操作
  2. 范围查询高效

    • 跳表支持高效的范围查询,时间复杂度 O(log n + m),m 是结果数量
    • 红黑树范围查询需要中序遍历,效率较低
    • B+ 树虽然支持范围查询,但实现复杂,不适合内存数据结构
  3. 性能接近

    • 跳表的查找、插入、删除都是 O(log n),与红黑树性能接近
    • 在 Redis 的场景下,跳表的性能已经足够好
  4. 内存友好

    • 跳表的索引层是稀疏的,内存占用相对较少
    • B+ 树节点需要存储多个键值对,内存占用较大
  5. 适合 Redis 场景

    • Redis 需要支持范围查询(如 ZRANGE),跳表更适合
    • Redis 的数据量通常不会特别大,跳表的性能足够

总结:Redis 的 Hash 是一种键值对数据结构,类似于 Java 的 Map,可以存储多个字段-值对。Hash 适合存储对象,可以单独操作字段,节省内存。Hash 的底层实现包括 ziplist(压缩列表)和 hashtable(哈希表),根据元素数量和大小自动选择。

10. Redis List 类型的常见操作命令有哪些?如何在 Redis 中实现队列和栈数据结构?Redis 中的 Ziplist、Quicklist 和 ListPack 数据结构的特点是什么?

总结 :Redis List 类型支持 LPUSHRPUSHLPOPRPOPLRANGE 等命令。可以使用 LPUSH + RPOP 实现队列,使用 LPUSH + LPOP 实现栈。List 的底层实现包括 ziplist(压缩列表)、quicklist(快速列表)和 listpack(列表包),根据元素数量和大小自动选择。

回答

Redis List 类型的常见操作命令

  1. 插入操作

    • LPUSH key value [value ...]:从左边插入元素
    • RPUSH key value [value ...]:从右边插入元素
    • LINSERT key BEFORE|AFTER pivot value:在指定元素前后插入
  2. 删除操作

    • LPOP key:从左边弹出元素
    • RPOP key:从右边弹出元素
    • LREM key count value:删除指定数量的元素
    • LTRIM key start stop:保留指定范围的元素
  3. 查询操作

    • LRANGE key start stop:获取指定范围的元素
    • LINDEX key index:获取指定索引的元素
    • LLEN key:获取列表长度

如何在 Redis 中实现队列和栈

  1. 队列(FIFO)

    • 入队:LPUSH key value(或 RPUSH
    • 出队:RPOP key(或 LPOP
    • 阻塞出队:BRPOP key timeout(队列为空时阻塞等待)
  2. 栈(LIFO)

    • 入栈:LPUSH key value
    • 出栈:LPOP key
    • 两端操作,实现后进先出

Ziplist、Quicklist 和 ListPack 数据结构的特点

  1. Ziplist(压缩列表)

    • 特点:连续内存存储,节省内存,适合小列表
    • 优点:内存紧凑,减少内存碎片
    • 缺点:插入删除需要移动元素,效率较低
    • 使用场景:元素数量少且值小的列表
  2. Quicklist(快速列表)

    • 特点:双向链表 + Ziplist,结合链表和压缩列表的优点
    • 优点
      • 插入删除效率高(链表特性)
      • 内存占用小(节点使用 ziplist)
      • 支持快速访问(双向链表)
    • 使用场景:Redis 3.2+ 默认使用,适合大多数场景
  3. ListPack(列表包)

    • 特点:Redis 7.0 引入,优化版的压缩列表
    • 优点
      • 相比 ziplist,删除操作更安全,不会出现级联更新
      • 内存布局更优化,性能更好
    • 使用场景:Redis 7.0+ 用于替代 ziplist

11. Redis 支持事务吗?如何实现?Redis 事务与关系型数据库事务的主要区别是什么?

总结 :Redis 支持事务,通过 MULTIEXECDISCARDWATCH 命令实现。Redis 事务不是 ACID 的原子性,而是保证命令按顺序执行,不会被其他客户端打断。Redis 事务不支持回滚,如果命令执行失败,不会回滚已执行的命令。WATCH 命令可以实现乐观锁,监控 key 的变化。

Redis 事务与关系型数据库事务的主要区别

特性 Redis 事务 关系型数据库事务
原子性 命令要么全部执行,要么全部不执行,但不支持回滚 支持 ACID 原子性,支持回滚
隔离性 单线程执行,天然隔离 通过锁机制实现隔离
持久性 依赖持久化机制(RDB/AOF) 通过日志保证持久性
一致性 不保证强一致性 保证强一致性
回滚 不支持回滚,命令执行失败不会回滚 支持回滚,可以撤销已执行的操作
错误处理 命令语法错误会导致整个事务失败,运行时错误不会回滚 运行时错误会回滚整个事务
WATCH 支持乐观锁,监控 key 变化 支持悲观锁和乐观锁

12. Redis 数据过期后的删除策略是什么?Redis 中有哪些内存淘汰策略?

总结:Redis 数据过期后的删除策略有两种:1. 惰性删除(Lazy Deletion),访问 key 时检查是否过期,过期则删除;2. 定期删除(Periodic Deletion),定期扫描过期 key 并删除。Redis 同时使用两种策略,惰性删除保证及时清理,定期删除保证不会有过期 key 堆积。

总结:Redis 提供了 8 种内存淘汰策略,当内存不足时触发。包括:noeviction(不淘汰)、allkeys-lru(所有 key 的 LRU)、allkeys-lfu(所有 key 的 LFU)、volatile-lru(过期 key 的 LRU)、volatile-lfu(过期 key 的 LFU)、volatile-ttl(过期 key 的 TTL)、volatile-random(过期 key 的随机)、allkeys-random(所有 key 的随机)。常用的是 allkeys-lru 和 volatile-lru。

13. Redis 的 Lua 脚本功能是什么?如何使用?Redis 的 Pipeline 功能是什么?Redis 中原生批处理命令(MSET、MGET)与 Pipeline 的区别是什么?

总结 :Redis 的 Lua 脚本功能允许在 Redis 服务器端执行 Lua 脚本,实现复杂的业务逻辑。Lua 脚本是原子性的,执行过程中不会被其他命令打断。使用 EVALEVALSHA 命令执行 Lua 脚本,可以访问 Redis 的数据和命令。

总结:Redis Pipeline 是一种批量执行命令的机制,客户端将多个命令打包发送给服务器,服务器批量执行后返回结果。Pipeline 可以减少网络往返次数,提高性能。Pipeline 不是事务,命令之间没有原子性保证,但可以提高吞吐量。

MSET/MGET 与 Pipeline 的区别

特性 MSET/MGET Pipeline
原子性 所有命令原子性执行 命令之间没有原子性保证
命令类型 只能执行相同类型的命令 可以执行不同类型的命令
返回值 统一返回结果 按顺序返回每个命令的结果
使用场景 批量设置/获取相同类型的 key 批量执行不同类型的命令
性能 性能好,但受限于命令类型 性能好,灵活性高

选择建议

  1. 使用 MSET/MGET

    • 批量设置/获取相同类型的 key
    • 需要原子性保证
  2. 使用 Pipeline

    • 批量执行不同类型的命令
    • 不需要原子性保证
    • 需要更高的灵活性

14. Redis 的订阅发布功能是什么?你了解吗?

总结:Redis 的订阅发布(Pub/Sub)是一种消息通信模式,发布者发送消息到频道,订阅者订阅频道接收消息。Pub/Sub 是异步的、非阻塞的,支持一对多的消息广播。Redis Pub/Sub 不持久化消息,订阅者离线后无法接收历史消息。

回答

Pub/Sub 的核心概念

  1. 发布者(Publisher)

    • 使用 PUBLISH channel message 命令发布消息
    • 将消息发送到指定的频道
  2. 订阅者(Subscriber)

    • 使用 SUBSCRIBE channel [channel ...] 订阅频道
    • 使用 PSUBSCRIBE pattern [pattern ...] 订阅模式匹配的频道
    • 订阅后进入订阅模式,只能接收订阅相关的命令
  3. 频道(Channel)

    • 消息的传输通道
    • 支持模式匹配,如 news.* 匹配所有以 news. 开头的频道

Pub/Sub 的特点

  1. 异步非阻塞

    • 发布者和订阅者之间解耦
    • 发布者不需要等待订阅者接收消息
  2. 一对多广播

    • 一个消息可以发送给多个订阅者
    • 支持频道订阅和模式订阅
  3. 不持久化

    • 消息不存储在 Redis 中
    • 订阅者离线后无法接收历史消息
  4. 实时性

    • 消息实时传递,延迟低
    • 适合实时消息推送场景

Pub/Sub 的应用场景

  1. 实时消息推送:系统通知、聊天消息等
  2. 事件通知:订单状态变更、库存变化等
  3. 系统解耦:微服务之间的消息通信

Pub/Sub 的局限性

  1. 消息不持久化:订阅者离线后无法接收消息
  2. 无消息确认:无法保证消息一定被接收
  3. 无消息队列:不支持消息堆积和重试
  4. 不适合可靠消息场景:需要可靠消息时,应使用消息队列(如 RabbitMQ、Kafka)

15. 如何使用 Redis 快速实现排行榜?如何使用 Redis 快速实现布隆过滤器?如何使用 Redis 统计大量用户唯一访问量(UV)?Redis 中的 Geo 数据结构是什么?

总结:可以使用 Sorted Set 实现排行榜,使用 Bitmap 或第三方库实现布隆过滤器,使用 HyperLogLog 统计 UV,使用 Geospatial 数据结构存储地理位置信息。

回答

如何使用 Redis 快速实现排行榜

  1. 使用 Sorted Set

    • ZADD key score member:添加成员和分数
    • ZREVRANGE key start stop WITHSCORES:获取排行榜(降序)
    • ZRANK key member:获取成员排名
    • ZINCRBY key increment member:增加成员分数
  2. 实现步骤

    • 用户完成操作后,使用 ZINCRBY 增加分数
    • 查询排行榜时,使用 ZREVRANGE 获取 Top N
    • 查询个人排名时,使用 ZREVRANK 获取排名

如何使用 Redis 快速实现布隆过滤器

  1. 使用 Bitmap

    • 使用多个哈希函数计算 key 的多个哈希值
    • 将对应的 bit 位置为 1
    • 查询时检查所有 bit 位是否都为 1
  2. 使用第三方库

    • Redisson 提供了布隆过滤器的实现
    • 使用 RBloomFilter 接口,自动处理哈希函数和位操作
  3. 应用场景

    • 防止缓存穿透:查询前先检查布隆过滤器
    • 去重:判断元素是否已存在
    • 爬虫去重:判断 URL 是否已爬取

如何使用 Redis 统计大量用户唯一访问量(UV)

  1. 使用 HyperLogLog

    • PFADD key element [element ...]:添加元素
    • PFCOUNT key [key ...]:统计基数(去重后的数量)
    • PFMERGE destkey sourcekey [sourcekey ...]:合并多个 HyperLogLog
  2. 优势

    • 内存占用小:12KB 可以统计 2^64 个元素
    • 误差率低:标准误差率为 0.81%
    • 性能高:O(1) 时间复杂度
  3. 实现步骤

    • 用户访问时,使用 PFADD 添加用户 ID
    • 查询 UV 时,使用 PFCOUNT 统计
    • 合并多天的数据时,使用 PFMERGE

Redis 中的 Geo 数据结构是什么

  1. Geo 数据结构

    • Redis 3.2+ 支持的地理位置数据结构
    • 底层使用 Sorted Set 实现,score 存储经纬度的编码值
  2. 常用命令

    • GEOADD key longitude latitude member:添加地理位置
    • GEODIST key member1 member2 [unit]:计算两点距离
    • GEORADIUS key longitude latitude radius unit:查找附近的位置
    • GEORADIUSBYMEMBER key member radius unit:查找成员附近的位置
  3. 应用场景

    • 附近的人:查找指定范围内的用户
    • 距离计算:计算两点之间的距离
    • 位置服务:LBS(Location Based Service)应用

16. Redis 为什么这么快?为什么 Redis 设计为单线程?6.0 版本为何引入多线程?Redis 通常应用于哪些场景?

总结:Redis 快的原因包括:1. 纯内存操作,避免磁盘 IO;2. 单线程模型,避免上下文切换和锁竞争;3. 高效的数据结构,如跳表、压缩列表等;4. I/O 多路复用,使用 epoll 等机制;5. 优化的网络模型,减少系统调用。这些因素共同作用,使 Redis 能够达到极高的性能。

总结:Redis 设计为单线程是为了避免上下文切换和锁竞争,简化实现,提高性能。Redis 6.0 引入多线程是为了提高网络 IO 的处理能力,但命令执行仍然是单线程的。多线程只用于处理网络 IO(接收请求和发送响应),命令解析和执行仍然是单线程,保证线程安全。

总结:Redis 通常应用于缓存、会话存储、消息队列、计数器、分布式锁、排行榜、限流、发布订阅等场景。Redis 的高性能和丰富的数据结构使其适合各种高并发场景。

17. Redis 和 Memcached 有哪些区别?

总结:Redis 和 Memcached 都是内存数据库,但 Redis 功能更丰富。主要区别包括:1. 数据类型(Redis 支持多种数据类型,Memcached 只支持字符串);2. 持久化(Redis 支持持久化,Memcached 不支持);3. 性能(Redis 单线程,Memcached 多线程);4. 功能(Redis 支持事务、Lua 脚本、发布订阅等,Memcached 不支持)。

18. Redis 中的内存碎片化是什么?如何进行优化?Redis 的虚拟内存(VM)机制是什么?

总结:内存碎片化是指已分配的内存块之间出现无法使用的空闲内存。Redis 通过内存分配器、定期整理等方式优化内存碎片。Redis 的虚拟内存(VM)机制在早期版本中用于将不常用的数据交换到磁盘,但在 Redis 2.4+ 版本中已被移除,因为性能影响较大。

回答

内存碎片化的原因

  1. 内存分配器

    • Redis 使用 jemalloc 或 glibc 的 malloc 分配内存
    • 频繁的内存分配和释放会产生碎片
  2. 数据修改

    • 修改字符串、列表等数据时,可能触发内存重新分配
    • 旧内存释放后,新内存分配可能产生碎片
  3. 过期删除

    • 大量 key 过期删除后,内存空间不连续
    • 形成内存碎片

内存碎片化的影响

  1. 内存浪费:碎片内存无法使用,造成浪费
  2. 内存不足:即使总内存足够,也可能因为碎片导致 OOM
  3. 性能下降:内存碎片化会影响内存分配效率

如何优化内存碎片化

  1. 使用合适的内存分配器

    • 推荐使用 jemalloc,碎片率较低
    • 配置:--with-malloc=jemalloc
  2. 控制 key 过期时间

    • 避免大量 key 同时过期
    • 设置随机过期时间,分散过期时间
  3. 定期重启

    • 在业务低峰期重启 Redis,释放碎片内存
    • 使用主从切换,减少影响
  4. 监控内存碎片率

    • 使用 INFO memory 查看内存碎片率
    • 碎片率 = (used_memory_rss - used_memory) / used_memory
    • 碎片率 > 1.5 时,考虑优化

Redis 的虚拟内存(VM)机制

  1. VM 机制的作用

    • 将不常用的数据交换到磁盘
    • 释放内存空间,存储更多数据
  2. VM 机制的问题

    • 性能影响大:磁盘 IO 比内存慢几个数量级
    • 实现复杂:需要管理内存和磁盘的数据交换
    • 不适合 Redis 的场景:Redis 追求高性能,不适合磁盘交换
  3. VM 机制的移除

    • Redis 2.4+ 版本移除了 VM 机制
    • 原因:性能影响太大,不符合 Redis 的设计理念
    • 替代方案:使用内存淘汰策略、数据分片等方式

19. Redis 性能瓶颈时如何处理?你在项目中使用的 Redis 客户端是什么?

总结:Redis 性能瓶颈的处理包括:1. 分析瓶颈原因(CPU、内存、网络、命令);2. 优化数据结构和使用方式;3. 使用 Pipeline、批量操作等优化;4. 调整配置参数;5. 使用集群、读写分离等架构优化。常见的 Redis 客户端包括 Jedis、Lettuce、Redisson 等。

回答

Redis 性能瓶颈的排查方法

  1. 使用慢查询日志

    • 配置 slowlog-log-slower-than 记录慢查询
    • 使用 SLOWLOG GET 查看慢查询记录
    • 分析慢查询命令,优化业务逻辑
  2. 监控性能指标

    • 使用 INFO stats 查看命令统计
    • 使用 INFO memory 查看内存使用
    • 使用 INFO clients 查看客户端连接数
  3. 分析瓶颈原因

    • CPU 瓶颈:检查是否有复杂命令或大量计算
    • 内存瓶颈:检查内存使用率、碎片率
    • 网络瓶颈:检查网络延迟、带宽使用
    • 命令瓶颈:检查是否有慢查询、大 key

Redis 性能瓶颈的处理方法

  1. 优化数据结构

    • 使用合适的数据结构,避免大 key
    • 使用 Hash 代替多个 String,节省内存
    • 使用 HyperLogLog 代替 Set 进行基数统计
  2. 使用批量操作

    • 使用 Pipeline 批量执行命令
    • 使用 MSET/MGET 批量设置/获取
    • 减少网络往返次数
  3. 优化命令使用

    • 避免使用 KEYS 命令,使用 SCAN 代替
    • 避免使用 SMEMBERS,使用 SSCAN 代替
    • 使用 EXISTS 代替 GET 检查 key 是否存在
  4. 架构优化

    • 使用主从复制,实现读写分离
    • 使用集群模式,实现水平扩展
    • 使用多级缓存,减少 Redis 压力
  5. 配置优化

    • 调整内存淘汰策略
    • 优化持久化配置(RDB/AOF)
    • 调整网络相关参数

常见的 Redis 客户端

  1. Jedis

    • 同步阻塞式客户端
    • 使用简单,性能好
    • 适合大多数场景
  2. Lettuce

    • 异步非阻塞式客户端
    • 基于 Netty,支持响应式编程
    • Spring Data Redis 默认使用
  3. Redisson

    • 功能丰富的客户端
    • 提供分布式锁、布隆过滤器等高级功能
    • 适合复杂场景
  4. Spring Data Redis

    • Spring 封装的 Redis 客户端
    • 支持 Jedis 和 Lettuce
    • 提供模板类,简化操作

20. Redis 源码中有哪些巧妙的设计,举几个典型的例子?

总结:Redis 源码中有很多巧妙的设计,包括:1. 渐进式 rehash,避免一次性 rehash 造成阻塞;2. 跳表实现,简单高效的有序数据结构;3. 压缩列表,节省内存的小数据结构;4. 写时复制(COW),RDB 生成时不影响主进程;5. 事件驱动模型,单线程处理高并发。

回答

Redis 源码中的巧妙设计

  1. 渐进式 rehash

    • 问题:HashMap 扩容时需要一次性 rehash,可能阻塞 Redis
    • 设计:分多次完成 rehash,每次处理一部分数据
    • 实现:维护两个哈希表,逐步迁移数据
    • 优势:避免长时间阻塞,保证 Redis 响应性
  2. 跳表实现

    • 问题:需要高效的有序数据结构,支持范围查询
    • 设计:使用跳表代替红黑树
    • 优势:实现简单,性能接近,支持高效范围查询
  3. 压缩列表(Ziplist)

    • 问题:小列表使用链表浪费内存
    • 设计:使用连续内存存储,节省内存
    • 优势:内存紧凑,减少内存碎片
  4. 写时复制(COW)

    • 问题:RDB 生成时需要复制内存,影响性能
    • 设计:fork 子进程,使用写时复制机制
    • 优势:只有修改时才复制内存页,减少内存占用
  5. 事件驱动模型

    • 问题:单线程如何实现高并发
    • 设计:使用 I/O 多路复用(epoll/kqueue)
    • 优势:单线程处理多个连接,避免线程切换开销
  6. 内存预分配

    • 问题:频繁的内存分配影响性能
    • 设计:SDS 预分配空间,减少重新分配次数
    • 优势:提高字符串操作性能
  7. 对象共享

    • 问题:相同的小整数、字符串重复存储浪费内存
    • 设计:使用对象池,共享相同的小对象
    • 优势:节省内存,提高性能
  8. 延迟删除

    • 问题:删除大 key 会阻塞 Redis
    • 设计 :使用 UNLINK 异步删除,后台线程处理
    • 优势:避免阻塞主线程,提高响应性

Redis 和 ZooKeeper 对比

redis 多种数据结构,数据存储是纯内存,单节点数据量无限制(受内存限制),读写速度 qps 快于 zk,并发能力高,网络开销低

redis 支持多种过期策略,支持原子操作,可以限制内存和自动淘汰。 Redis 主从可能短暂不一致,ZooKeeper 保证强一致。 Redis 写操作快,ZooKeeper 写操作慢但一致性强。Redis 读操作灵活,ZooKeeper 读操作有限制。 Redis 可以关闭持久化,ZooKeeper 必须持久化。redis 支持消息队列,热点数据缓存,支持事务更完善(MULTI/EXEC

相关推荐
顾林海17 分钟前
从0到1搭建Android网络框架:别再让你的请求在"路上迷路"了
android·面试·架构
懒惰蜗牛17 分钟前
Day63 | Java IO之NIO三件套--选择器(下)
java·nio·选择器·selector·半包粘包·tcp缓冲区
JavaGuide24 分钟前
美团2026届后端一二面(附详细参考答案)
java·后端
小马爱打代码25 分钟前
SpringBoot + Quartz + Redis:分布式任务调度系统 - 从架构设计到企业级落地
spring boot·redis·分布式
打工人你好26 分钟前
如何设计更安全的 VIP 权限体系
java·jvm·安全
拉不动的猪27 分钟前
前端三大权限场景全解析:设计、实现、存储与企业级实践
前端·javascript·面试
L.EscaRC33 分钟前
Spring IOC核心原理与运用
java·spring·ioc
摇滚侠1 小时前
2025最新 SpringCloud 教程,Nacos-总结,笔记19
java·笔记·spring cloud
在逃热干面1 小时前
(笔记)获取终端输出保存到文件
java·笔记·spring