文章目录
-
-
- [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. 主节点将后续的写命令发送给从节点(命令传播)。主从复制支持全量复制和增量复制,增量复制通过复制偏移量和复制积压缓冲区实现。
主从复制的常见拓扑结构:
-
一主一从:
- 一个主节点,一个从节点
- 适合数据备份和读写分离
- 缺点:从节点故障时无法提供读服务
-
一主多从:
- 一个主节点,多个从节点
- 适合读多写少的场景,提高读性能
- 缺点:从节点过多会增加主节点压力
-
树状结构(主-从-从):
- 主节点下有从节点,从节点下还有从节点
- 减少主节点压力,适合大规模部署
- 缺点:数据同步延迟可能增加
Redis 复制延迟的常见原因:
- 网络延迟:主从节点之间的网络延迟
- 主节点写压力大:主节点写操作频繁,从节点同步跟不上
- 从节点性能不足:从节点处理能力弱,同步速度慢
- 复制积压缓冲区不足:缓冲区大小不够,导致全量复制
- 从节点数量过多:多个从节点同时同步,主节点压力大
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 集群中,如何根据键定位到对应的节点:
-
计算槽位:
- 使用公式:
slot = CRC16(key) % 16384 - CRC16 是循环冗余校验算法,将 key 转换为 0-16383 之间的槽位号
- 使用公式:
-
查找节点:
- 根据槽位号查找对应的节点
- 每个节点维护一个槽位映射表
-
重定向机制:
- 如果 key 不在当前节点,返回
MOVED <slot> <ip>:<port>错误 - 客户端根据错误信息重定向到正确的节点
- 如果 key 不在当前节点,返回
-
ASK 重定向:
- 在集群迁移过程中,可能返回
ASK <slot> <ip>:<port>错误 - 客户端需要先发送
ASKING命令,再执行操作
- 在集群迁移过程中,可能返回
6. Redis 中如何实现分布式锁?分布式锁可能遇到哪些问题?如何解决?Red Lock 是什么?说说 Redisson 分布式锁的原理?
总结 :Redis 分布式锁可以通过 SETNX 命令或 Redisson 实现。常见问题包括:1. 死锁(锁过期时间设置不当);2. 误删(删除其他线程的锁);3. 锁续期(业务执行时间超过锁过期时间)。解决方案包括:使用 UUID 标识锁、使用看门狗机制自动续期、使用 Red Lock 算法保证高可用。Redisson 分布式锁通过 Lua 脚本保证原子性,使用看门狗机制自动续期,支持可重入锁。
回答
Redis 分布式锁的实现方式:
-
使用 SETNX 命令:
SETNX key value:如果 key 不存在则设置,返回 1;否则返回 0- 设置过期时间:
EXPIRE key seconds - 释放锁:
DEL key
-
使用 SET 命令(推荐):
SET key value NX EX seconds:原子性设置 key 和过期时间- 避免 SETNX + EXPIRE 的非原子性问题
-
使用 Redisson:
- 封装了分布式锁的实现,支持自动续期、可重入等特性
分布式锁可能遇到的问题:
-
死锁:
- 问题:锁过期时间设置不当,业务未执行完锁已过期
- 解决:使用看门狗机制自动续期,或设置合理的过期时间
-
误删锁:
- 问题:线程 A 的锁过期,线程 B 获取锁,线程 A 执行完后删除线程 B 的锁
- 解决:使用 UUID 标识锁,删除时验证 value 是否匹配
-
锁续期:
- 问题:业务执行时间不确定,可能超过锁过期时间
- 解决:使用看门狗机制自动续期
Red Lock 算法:
Red Lock 是 Redis 官方提出的分布式锁算法,用于在多个 Redis 节点上实现分布式锁,保证高可用。
实现原理:
-
获取锁:
- 客户端向 N 个 Redis 节点(N 为奇数,通常为 5)发送获取锁的请求
- 使用相同的 key 和 value,设置相同的过期时间
- 如果大多数节点(至少 N/2+1 个)成功获取锁,则认为获取成功
-
释放锁:
- 客户端向所有节点发送释放锁的请求
- 即使部分节点失败,也要尝试释放所有节点的锁
Red Lock 的优势:
- 高可用:即使部分节点故障,只要大多数节点正常,锁仍然可用
- 防止单点故障:不依赖单个 Redis 节点
- 保证安全性:大多数节点获取锁成功,保证锁的唯一性
Redisson 分布式锁的原理:
-
Lua 脚本保证原子性:
- 使用 Lua 脚本执行
SET key value NX EX seconds,保证原子性 - 使用 UUID + 线程 ID 作为 value,标识锁的持有者
- 使用 Lua 脚本执行
-
看门狗机制自动续期 :
总结:Redis 的看门狗机制是 Redisson 分布式锁中的自动续期机制,用于防止业务执行时间超过锁的过期时间导致锁被提前释放。看门狗会启动一个后台线程,每隔锁有效期的 1/3 时间执行一次续期,默认锁有效期是 30 秒,每 10 秒续期一次,直到锁被释放或线程中断。 -
可重入锁:
- 支持同一线程多次获取锁,使用计数器记录重入次数
- 释放锁时递减计数器,计数器为 0 时真正释放锁
-
公平锁:
- 使用队列维护等待获取锁的线程
- 按照请求顺序分配锁,保证公平性
7. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?Redis 中如何保证缓存与数据库的数据一致性?
缓存击穿、缓存穿透和缓存雪崩:
-
缓存击穿:
- 定义:热点数据过期,大量请求同时穿透到数据库
- 原因:热点数据过期,大量并发请求同时查询数据库
- 解决方案 :
- 使用分布式锁,只允许一个线程查询数据库
- 热点数据永不过期,或异步更新
- 使用本地缓存(如 Caffeine)作为二级缓存
-
缓存穿透:
- 定义:查询不存在的数据,缓存未命中,大量请求打到数据库
- 原因:恶意请求或业务逻辑问题,查询不存在的数据
- 解决方案 :
- 缓存空值,设置较短的过期时间(如 5 分钟)
- 使用布隆过滤器,过滤不存在的数据
- 参数校验,在业务层过滤无效请求
-
缓存雪崩:
- 定义:大量缓存同时过期,大量请求同时打到数据库
- 原因:缓存过期时间设置相同,或 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 的优势:
- O(1) 获取长度:通过 len 字段直接获取,不需要遍历
- 二进制安全 :可以存储任意二进制数据,不依赖
\0结束符 - 预分配空间:减少内存重新分配次数,提高性能
- 空间预分配策略 :
- 如果修改后长度 < 1MB,分配 2 倍空间
- 如果修改后长度 >= 1MB,分配 len + 1MB 空间
Redis 字符串类型的最大值:
- Redis 字符串最大值为 512MB
- 这是 Redis 的限制,防止单个 key 占用过多内存
EMBSTR 对象的阈值设置:
-
EMBSTR 对象:
- 当字符串长度 <= 44 字节时,使用 EMBSTR 编码
- EMBSTR 将 RedisObject 和 SDS 连续存储在同一块内存中
- 只需要一次内存分配,提高性能
-
为什么是 44 字节:
- RedisObject 占用 16 字节(64 位系统)
- SDS 结构占用:len(1) + free(1) + buf(1) = 3 字节(最小)
- 实际可用空间:64 - 16 - 3 = 45 字节(考虑内存对齐)
- 为了安全,设置为 44 字节
-
调整历史:
- 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+ 树:
-
实现简单:
- 跳表实现比红黑树简单,代码量少,易于维护
- 红黑树需要处理复杂的旋转操作,跳表只需要简单的指针操作
-
范围查询高效:
- 跳表支持高效的范围查询,时间复杂度 O(log n + m),m 是结果数量
- 红黑树范围查询需要中序遍历,效率较低
- B+ 树虽然支持范围查询,但实现复杂,不适合内存数据结构
-
性能接近:
- 跳表的查找、插入、删除都是 O(log n),与红黑树性能接近
- 在 Redis 的场景下,跳表的性能已经足够好
-
内存友好:
- 跳表的索引层是稀疏的,内存占用相对较少
- B+ 树节点需要存储多个键值对,内存占用较大
-
适合 Redis 场景:
- Redis 需要支持范围查询(如 ZRANGE),跳表更适合
- Redis 的数据量通常不会特别大,跳表的性能足够
总结:Redis 的 Hash 是一种键值对数据结构,类似于 Java 的 Map,可以存储多个字段-值对。Hash 适合存储对象,可以单独操作字段,节省内存。Hash 的底层实现包括 ziplist(压缩列表)和 hashtable(哈希表),根据元素数量和大小自动选择。
10. Redis List 类型的常见操作命令有哪些?如何在 Redis 中实现队列和栈数据结构?Redis 中的 Ziplist、Quicklist 和 ListPack 数据结构的特点是什么?
总结 :Redis List 类型支持 LPUSH、RPUSH、LPOP、RPOP、LRANGE 等命令。可以使用 LPUSH + RPOP 实现队列,使用 LPUSH + LPOP 实现栈。List 的底层实现包括 ziplist(压缩列表)、quicklist(快速列表)和 listpack(列表包),根据元素数量和大小自动选择。
回答
Redis List 类型的常见操作命令:
-
插入操作:
LPUSH key value [value ...]:从左边插入元素RPUSH key value [value ...]:从右边插入元素LINSERT key BEFORE|AFTER pivot value:在指定元素前后插入
-
删除操作:
LPOP key:从左边弹出元素RPOP key:从右边弹出元素LREM key count value:删除指定数量的元素LTRIM key start stop:保留指定范围的元素
-
查询操作:
LRANGE key start stop:获取指定范围的元素LINDEX key index:获取指定索引的元素LLEN key:获取列表长度
如何在 Redis 中实现队列和栈:
-
队列(FIFO):
- 入队:
LPUSH key value(或RPUSH) - 出队:
RPOP key(或LPOP) - 阻塞出队:
BRPOP key timeout(队列为空时阻塞等待)
- 入队:
-
栈(LIFO):
- 入栈:
LPUSH key value - 出栈:
LPOP key - 两端操作,实现后进先出
- 入栈:
Ziplist、Quicklist 和 ListPack 数据结构的特点:
-
Ziplist(压缩列表):
- 特点:连续内存存储,节省内存,适合小列表
- 优点:内存紧凑,减少内存碎片
- 缺点:插入删除需要移动元素,效率较低
- 使用场景:元素数量少且值小的列表
-
Quicklist(快速列表):
- 特点:双向链表 + Ziplist,结合链表和压缩列表的优点
- 优点 :
- 插入删除效率高(链表特性)
- 内存占用小(节点使用 ziplist)
- 支持快速访问(双向链表)
- 使用场景:Redis 3.2+ 默认使用,适合大多数场景
-
ListPack(列表包):
- 特点:Redis 7.0 引入,优化版的压缩列表
- 优点 :
- 相比 ziplist,删除操作更安全,不会出现级联更新
- 内存布局更优化,性能更好
- 使用场景:Redis 7.0+ 用于替代 ziplist
11. Redis 支持事务吗?如何实现?Redis 事务与关系型数据库事务的主要区别是什么?
总结 :Redis 支持事务,通过 MULTI、EXEC、DISCARD、WATCH 命令实现。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 脚本是原子性的,执行过程中不会被其他命令打断。使用 EVAL 或 EVALSHA 命令执行 Lua 脚本,可以访问 Redis 的数据和命令。
总结:Redis Pipeline 是一种批量执行命令的机制,客户端将多个命令打包发送给服务器,服务器批量执行后返回结果。Pipeline 可以减少网络往返次数,提高性能。Pipeline 不是事务,命令之间没有原子性保证,但可以提高吞吐量。
MSET/MGET 与 Pipeline 的区别:
| 特性 | MSET/MGET | Pipeline |
|---|---|---|
| 原子性 | 所有命令原子性执行 | 命令之间没有原子性保证 |
| 命令类型 | 只能执行相同类型的命令 | 可以执行不同类型的命令 |
| 返回值 | 统一返回结果 | 按顺序返回每个命令的结果 |
| 使用场景 | 批量设置/获取相同类型的 key | 批量执行不同类型的命令 |
| 性能 | 性能好,但受限于命令类型 | 性能好,灵活性高 |
选择建议:
-
使用 MSET/MGET:
- 批量设置/获取相同类型的 key
- 需要原子性保证
-
使用 Pipeline:
- 批量执行不同类型的命令
- 不需要原子性保证
- 需要更高的灵活性
14. Redis 的订阅发布功能是什么?你了解吗?
总结:Redis 的订阅发布(Pub/Sub)是一种消息通信模式,发布者发送消息到频道,订阅者订阅频道接收消息。Pub/Sub 是异步的、非阻塞的,支持一对多的消息广播。Redis Pub/Sub 不持久化消息,订阅者离线后无法接收历史消息。
回答
Pub/Sub 的核心概念:
-
发布者(Publisher):
- 使用
PUBLISH channel message命令发布消息 - 将消息发送到指定的频道
- 使用
-
订阅者(Subscriber):
- 使用
SUBSCRIBE channel [channel ...]订阅频道 - 使用
PSUBSCRIBE pattern [pattern ...]订阅模式匹配的频道 - 订阅后进入订阅模式,只能接收订阅相关的命令
- 使用
-
频道(Channel):
- 消息的传输通道
- 支持模式匹配,如
news.*匹配所有以news.开头的频道
Pub/Sub 的特点:
-
异步非阻塞:
- 发布者和订阅者之间解耦
- 发布者不需要等待订阅者接收消息
-
一对多广播:
- 一个消息可以发送给多个订阅者
- 支持频道订阅和模式订阅
-
不持久化:
- 消息不存储在 Redis 中
- 订阅者离线后无法接收历史消息
-
实时性:
- 消息实时传递,延迟低
- 适合实时消息推送场景
Pub/Sub 的应用场景:
- 实时消息推送:系统通知、聊天消息等
- 事件通知:订单状态变更、库存变化等
- 系统解耦:微服务之间的消息通信
Pub/Sub 的局限性:
- 消息不持久化:订阅者离线后无法接收消息
- 无消息确认:无法保证消息一定被接收
- 无消息队列:不支持消息堆积和重试
- 不适合可靠消息场景:需要可靠消息时,应使用消息队列(如 RabbitMQ、Kafka)
15. 如何使用 Redis 快速实现排行榜?如何使用 Redis 快速实现布隆过滤器?如何使用 Redis 统计大量用户唯一访问量(UV)?Redis 中的 Geo 数据结构是什么?
总结:可以使用 Sorted Set 实现排行榜,使用 Bitmap 或第三方库实现布隆过滤器,使用 HyperLogLog 统计 UV,使用 Geospatial 数据结构存储地理位置信息。
回答
如何使用 Redis 快速实现排行榜:
-
使用 Sorted Set:
ZADD key score member:添加成员和分数ZREVRANGE key start stop WITHSCORES:获取排行榜(降序)ZRANK key member:获取成员排名ZINCRBY key increment member:增加成员分数
-
实现步骤:
- 用户完成操作后,使用
ZINCRBY增加分数 - 查询排行榜时,使用
ZREVRANGE获取 Top N - 查询个人排名时,使用
ZREVRANK获取排名
- 用户完成操作后,使用
如何使用 Redis 快速实现布隆过滤器:
-
使用 Bitmap:
- 使用多个哈希函数计算 key 的多个哈希值
- 将对应的 bit 位置为 1
- 查询时检查所有 bit 位是否都为 1
-
使用第三方库:
- Redisson 提供了布隆过滤器的实现
- 使用
RBloomFilter接口,自动处理哈希函数和位操作
-
应用场景:
- 防止缓存穿透:查询前先检查布隆过滤器
- 去重:判断元素是否已存在
- 爬虫去重:判断 URL 是否已爬取
如何使用 Redis 统计大量用户唯一访问量(UV):
-
使用 HyperLogLog:
PFADD key element [element ...]:添加元素PFCOUNT key [key ...]:统计基数(去重后的数量)PFMERGE destkey sourcekey [sourcekey ...]:合并多个 HyperLogLog
-
优势:
- 内存占用小:12KB 可以统计 2^64 个元素
- 误差率低:标准误差率为 0.81%
- 性能高:O(1) 时间复杂度
-
实现步骤:
- 用户访问时,使用
PFADD添加用户 ID - 查询 UV 时,使用
PFCOUNT统计 - 合并多天的数据时,使用
PFMERGE
- 用户访问时,使用
Redis 中的 Geo 数据结构是什么:
-
Geo 数据结构:
- Redis 3.2+ 支持的地理位置数据结构
- 底层使用 Sorted Set 实现,score 存储经纬度的编码值
-
常用命令:
GEOADD key longitude latitude member:添加地理位置GEODIST key member1 member2 [unit]:计算两点距离GEORADIUS key longitude latitude radius unit:查找附近的位置GEORADIUSBYMEMBER key member radius unit:查找成员附近的位置
-
应用场景:
- 附近的人:查找指定范围内的用户
- 距离计算:计算两点之间的距离
- 位置服务: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+ 版本中已被移除,因为性能影响较大。
回答
内存碎片化的原因:
-
内存分配器:
- Redis 使用 jemalloc 或 glibc 的 malloc 分配内存
- 频繁的内存分配和释放会产生碎片
-
数据修改:
- 修改字符串、列表等数据时,可能触发内存重新分配
- 旧内存释放后,新内存分配可能产生碎片
-
过期删除:
- 大量 key 过期删除后,内存空间不连续
- 形成内存碎片
内存碎片化的影响:
- 内存浪费:碎片内存无法使用,造成浪费
- 内存不足:即使总内存足够,也可能因为碎片导致 OOM
- 性能下降:内存碎片化会影响内存分配效率
如何优化内存碎片化:
-
使用合适的内存分配器:
- 推荐使用 jemalloc,碎片率较低
- 配置:
--with-malloc=jemalloc
-
控制 key 过期时间:
- 避免大量 key 同时过期
- 设置随机过期时间,分散过期时间
-
定期重启:
- 在业务低峰期重启 Redis,释放碎片内存
- 使用主从切换,减少影响
-
监控内存碎片率:
- 使用
INFO memory查看内存碎片率 - 碎片率 = (used_memory_rss - used_memory) / used_memory
- 碎片率 > 1.5 时,考虑优化
- 使用
Redis 的虚拟内存(VM)机制:
-
VM 机制的作用:
- 将不常用的数据交换到磁盘
- 释放内存空间,存储更多数据
-
VM 机制的问题:
- 性能影响大:磁盘 IO 比内存慢几个数量级
- 实现复杂:需要管理内存和磁盘的数据交换
- 不适合 Redis 的场景:Redis 追求高性能,不适合磁盘交换
-
VM 机制的移除:
- Redis 2.4+ 版本移除了 VM 机制
- 原因:性能影响太大,不符合 Redis 的设计理念
- 替代方案:使用内存淘汰策略、数据分片等方式
19. Redis 性能瓶颈时如何处理?你在项目中使用的 Redis 客户端是什么?
总结:Redis 性能瓶颈的处理包括:1. 分析瓶颈原因(CPU、内存、网络、命令);2. 优化数据结构和使用方式;3. 使用 Pipeline、批量操作等优化;4. 调整配置参数;5. 使用集群、读写分离等架构优化。常见的 Redis 客户端包括 Jedis、Lettuce、Redisson 等。
回答
Redis 性能瓶颈的排查方法:
-
使用慢查询日志:
- 配置
slowlog-log-slower-than记录慢查询 - 使用
SLOWLOG GET查看慢查询记录 - 分析慢查询命令,优化业务逻辑
- 配置
-
监控性能指标:
- 使用
INFO stats查看命令统计 - 使用
INFO memory查看内存使用 - 使用
INFO clients查看客户端连接数
- 使用
-
分析瓶颈原因:
- CPU 瓶颈:检查是否有复杂命令或大量计算
- 内存瓶颈:检查内存使用率、碎片率
- 网络瓶颈:检查网络延迟、带宽使用
- 命令瓶颈:检查是否有慢查询、大 key
Redis 性能瓶颈的处理方法:
-
优化数据结构:
- 使用合适的数据结构,避免大 key
- 使用 Hash 代替多个 String,节省内存
- 使用 HyperLogLog 代替 Set 进行基数统计
-
使用批量操作:
- 使用 Pipeline 批量执行命令
- 使用 MSET/MGET 批量设置/获取
- 减少网络往返次数
-
优化命令使用:
- 避免使用
KEYS命令,使用SCAN代替 - 避免使用
SMEMBERS,使用SSCAN代替 - 使用
EXISTS代替GET检查 key 是否存在
- 避免使用
-
架构优化:
- 使用主从复制,实现读写分离
- 使用集群模式,实现水平扩展
- 使用多级缓存,减少 Redis 压力
-
配置优化:
- 调整内存淘汰策略
- 优化持久化配置(RDB/AOF)
- 调整网络相关参数
常见的 Redis 客户端:
-
Jedis:
- 同步阻塞式客户端
- 使用简单,性能好
- 适合大多数场景
-
Lettuce:
- 异步非阻塞式客户端
- 基于 Netty,支持响应式编程
- Spring Data Redis 默认使用
-
Redisson:
- 功能丰富的客户端
- 提供分布式锁、布隆过滤器等高级功能
- 适合复杂场景
-
Spring Data Redis:
- Spring 封装的 Redis 客户端
- 支持 Jedis 和 Lettuce
- 提供模板类,简化操作
20. Redis 源码中有哪些巧妙的设计,举几个典型的例子?
总结:Redis 源码中有很多巧妙的设计,包括:1. 渐进式 rehash,避免一次性 rehash 造成阻塞;2. 跳表实现,简单高效的有序数据结构;3. 压缩列表,节省内存的小数据结构;4. 写时复制(COW),RDB 生成时不影响主进程;5. 事件驱动模型,单线程处理高并发。
回答
Redis 源码中的巧妙设计:
-
渐进式 rehash:
- 问题:HashMap 扩容时需要一次性 rehash,可能阻塞 Redis
- 设计:分多次完成 rehash,每次处理一部分数据
- 实现:维护两个哈希表,逐步迁移数据
- 优势:避免长时间阻塞,保证 Redis 响应性
-
跳表实现:
- 问题:需要高效的有序数据结构,支持范围查询
- 设计:使用跳表代替红黑树
- 优势:实现简单,性能接近,支持高效范围查询
-
压缩列表(Ziplist):
- 问题:小列表使用链表浪费内存
- 设计:使用连续内存存储,节省内存
- 优势:内存紧凑,减少内存碎片
-
写时复制(COW):
- 问题:RDB 生成时需要复制内存,影响性能
- 设计:fork 子进程,使用写时复制机制
- 优势:只有修改时才复制内存页,减少内存占用
-
事件驱动模型:
- 问题:单线程如何实现高并发
- 设计:使用 I/O 多路复用(epoll/kqueue)
- 优势:单线程处理多个连接,避免线程切换开销
-
内存预分配:
- 问题:频繁的内存分配影响性能
- 设计:SDS 预分配空间,减少重新分配次数
- 优势:提高字符串操作性能
-
对象共享:
- 问题:相同的小整数、字符串重复存储浪费内存
- 设计:使用对象池,共享相同的小对象
- 优势:节省内存,提高性能
-
延迟删除:
- 问题:删除大 key 会阻塞 Redis
- 设计 :使用
UNLINK异步删除,后台线程处理 - 优势:避免阻塞主线程,提高响应性
Redis 和 ZooKeeper 对比
redis 多种数据结构,数据存储是纯内存,单节点数据量无限制(受内存限制),读写速度 qps 快于 zk,并发能力高,网络开销低
redis 支持多种过期策略,支持原子操作,可以限制内存和自动淘汰。 Redis 主从可能短暂不一致,ZooKeeper 保证强一致。 Redis 写操作快,ZooKeeper 写操作慢但一致性强。Redis 读操作灵活,ZooKeeper 读操作有限制。 Redis 可以关闭持久化,ZooKeeper 必须持久化。redis 支持消息队列,热点数据缓存,支持事务更完善(MULTI/EXEC