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

相关推荐
李慕婉学姐7 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆9 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin9 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20059 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉9 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国10 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824810 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
han_10 小时前
从一道前端面试题,谈 JS 对象存储特点和运算符执行顺序
前端·javascript·面试
華勳全栈10 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9910 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc