Redis|学习笔记

文章目录

  • [1. Redis 是什么](#1. Redis 是什么)
    • [1.1 Redis 的定义](#1.1 Redis 的定义)
    • [1.2 面试回答模板](#1.2 面试回答模板)
  • [2. Redis 和 MySQL 的关系](#2. Redis 和 MySQL 的关系)
  • [3. Redis 常见应用场景](#3. Redis 常见应用场景)
    • [3.1 分布式缓存](#3.1 分布式缓存)
    • [3.2 分布式 Session](#3.2 分布式 Session)
    • [3.3 分布式锁](#3.3 分布式锁)
    • [3.4 计数器](#3.4 计数器)
    • [3.5 排行榜](#3.5 排行榜)
    • [3.6 点赞、关注、共同好友](#3.6 点赞、关注、共同好友)
    • [3.7 签到打卡](#3.7 签到打卡)
    • [3.8 UV 统计](#3.8 UV 统计)
    • [3.9 附近的人 / 附近酒店](#3.9 附近的人 / 附近酒店)
    • [3.10 消息队列](#3.10 消息队列)
  • [4. Redis 为什么快](#4. Redis 为什么快)
    • [4.1 主要原因](#4.1 主要原因)
    • [4.2 面试回答模板](#4.2 面试回答模板)
  • [5. Redis 到底是单线程还是多线程](#5. Redis 到底是单线程还是多线程)
    • [5.1 Redis 3.x](#5.1 Redis 3.x)
    • [5.2 Redis 4.x](#5.2 Redis 4.x)
    • [5.3 Redis 6/7](#5.3 Redis 6/7)
    • [5.4 面试回答模板](#5.4 面试回答模板)
  • [6. Redis 为什么以前选择单线程](#6. Redis 为什么以前选择单线程)
    • [6.1 原因](#6.1 原因)
    • [6.2 面试回答模板](#6.2 面试回答模板)
  • [7. IO 多路复用](#7. IO 多路复用)
    • [7.1 为什么需要 IO 多路复用](#7.1 为什么需要 IO 多路复用)
    • [7.2 BIO、NIO、IO 多路复用对比](#7.2 BIO、NIO、IO 多路复用对比)
    • [7.3 BIO](#7.3 BIO)
    • [7.4 NIO](#7.4 NIO)
    • [7.5 IO 多路复用](#7.5 IO 多路复用)
  • [8. select、poll、epoll 区别](#8. select、poll、epoll 区别)
    • [8.1 select](#8.1 select)
    • [8.2 poll](#8.2 poll)
    • [8.3 epoll](#8.3 epoll)
    • [8.4 面试回答模板](#8.4 面试回答模板)
  • [9. Redis 的文件事件处理器](#9. Redis 的文件事件处理器)
  • [10. Redis 数据类型总览](#10. Redis 数据类型总览)
    • [10.1 传统 5 大类型](#10.1 传统 5 大类型)
    • [10.2 扩展 5 大类型](#10.2 扩展 5 大类型)
  • [11. String 类型](#11. String 类型)
    • [11.1 String 基本特点](#11.1 String 基本特点)
    • [11.2 String 的使用场景](#11.2 String 的使用场景)
      • [11.2.1 缓存对象](#11.2.1 缓存对象)
      • [11.2.2 计数器](#11.2.2 计数器)
      • [11.2.3 分布式锁基础版](#11.2.3 分布式锁基础版)
    • [11.3 String 底层编码](#11.3 String 底层编码)
    • [11.4 Redis 为什么不用 C 字符串,而要自己设计 SDS](#11.4 Redis 为什么不用 C 字符串,而要自己设计 SDS)
  • [12. Hash 类型](#12. Hash 类型)
    • [12.1 Hash 基本特点](#12.1 Hash 基本特点)
    • [12.2 Hash 使用场景](#12.2 Hash 使用场景)
      • [12.2.1 用户对象缓存](#12.2.1 用户对象缓存)
      • [12.2.2 购物车](#12.2.2 购物车)
      • [12.2.3 可重入分布式锁](#12.2.3 可重入分布式锁)
    • [12.3 Hash 底层结构](#12.3 Hash 底层结构)
  • [13. List 类型](#13. List 类型)
    • [13.1 List 基本特点](#13.1 List 基本特点)
    • [13.2 List 使用场景](#13.2 List 使用场景)
      • [13.2.1 栈](#13.2.1 栈)
      • [13.2.2 队列](#13.2.2 队列)
      • [13.2.3 阻塞队列](#13.2.3 阻塞队列)
      • [13.2.4 简单消息队列](#13.2.4 简单消息队列)
    • [13.3 List 底层结构](#13.3 List 底层结构)
  • [14. Set 类型](#14. Set 类型)
    • [14.1 Set 基本特点](#14.1 Set 基本特点)
    • [14.2 Set 使用场景](#14.2 Set 使用场景)
      • [14.2.1 点赞去重](#14.2.1 点赞去重)
      • [14.2.2 关注关系](#14.2.2 关注关系)
      • [14.2.3 共同关注](#14.2.3 共同关注)
      • [14.2.4 可能认识的人](#14.2.4 可能认识的人)
    • [14.3 Set 底层结构](#14.3 Set 底层结构)
  • [15. ZSet 类型](#15. ZSet 类型)
    • [15.1 ZSet 基本特点](#15.1 ZSet 基本特点)
    • [15.2 ZSet 使用场景](#15.2 ZSet 使用场景)
      • [15.2.1 排行榜](#15.2.1 排行榜)
      • [15.2.2 延迟队列](#15.2.2 延迟队列)
      • [15.2.3 最新评论分页](#15.2.3 最新评论分页)
    • [15.3 ZSet 底层结构](#15.3 ZSet 底层结构)
    • [15.4 跳表是什么](#15.4 跳表是什么)
  • [16. Bitmap](#16. Bitmap)
    • [16.1 Bitmap 是什么](#16.1 Bitmap 是什么)
    • [16.2 Bitmap 使用场景](#16.2 Bitmap 使用场景)
      • [16.2.1 用户签到](#16.2.1 用户签到)
      • [16.2.2 日活统计](#16.2.2 日活统计)
      • [16.2.3 连续两天活跃用户](#16.2.3 连续两天活跃用户)
    • [16.3 Bitmap 优缺点](#16.3 Bitmap 优缺点)
  • [17. HyperLogLog](#17. HyperLogLog)
    • [17.1 HyperLogLog 是什么](#17.1 HyperLogLog 是什么)
    • [17.2 HyperLogLog 使用场景](#17.2 HyperLogLog 使用场景)
      • [17.2.1 UV 统计](#17.2.1 UV 统计)
      • [17.2.2 多页面 UV 合并](#17.2.2 多页面 UV 合并)
    • [17.3 HyperLogLog 和 Set 的区别](#17.3 HyperLogLog 和 Set 的区别)
  • [18. GEO](#18. GEO)
    • [18.1 GEO 是什么](#18.1 GEO 是什么)
    • [18.2 GEO 使用场景](#18.2 GEO 使用场景)
  • [19. Stream](#19. Stream)
    • [19.1 Stream 是什么](#19.1 Stream 是什么)
    • [19.2 Stream 和 Pub/Sub 对比](#19.2 Stream 和 Pub/Sub 对比)
  • [20. Redis 底层数据结构总览](#20. Redis 底层数据结构总览)
    • [20.1 Redis 常见底层结构](#20.1 Redis 常见底层结构)
    • [20.2 Redis 6 和 Redis 7 数据结构变化](#20.2 Redis 6 和 Redis 7 数据结构变化)
      • [Redis 6](#Redis 6)
      • [Redis 7](#Redis 7)
  • [21. Redis 过期删除策略](#21. Redis 过期删除策略)
    • [21.1 Redis key 过期后会立刻删除吗](#21.1 Redis key 过期后会立刻删除吗)
    • [21.2 三种过期删除策略](#21.2 三种过期删除策略)
      • [21.2.1 定时删除 / 立即删除](#21.2.1 定时删除 / 立即删除)
      • [21.2.2 惰性删除](#21.2.2 惰性删除)
      • [21.2.3 定期删除](#21.2.3 定期删除)
    • [21.3 Redis 实际采用什么策略](#21.3 Redis 实际采用什么策略)
    • [21.4 面试回答模板](#21.4 面试回答模板)
  • [22. Redis 内存淘汰策略](#22. Redis 内存淘汰策略)
    • [22.1 什么时候触发内存淘汰](#22.1 什么时候触发内存淘汰)
    • [22.2 Redis 8 种淘汰策略](#22.2 Redis 8 种淘汰策略)
    • [22.3 淘汰策略可以从两个维度理解](#22.3 淘汰策略可以从两个维度理解)
    • [22.4 LRU 和 LFU 的区别](#22.4 LRU 和 LFU 的区别)
    • [22.5 淘汰策略怎么选](#22.5 淘汰策略怎么选)
    • [22.6 面试回答模板](#22.6 面试回答模板)
  • [23. RDB 持久化](#23. RDB 持久化)
    • [23.1 RDB 是什么](#23.1 RDB 是什么)
    • [23.2 RDB 触发方式](#23.2 RDB 触发方式)
    • [23.3 save 配置规则](#23.3 save 配置规则)
    • [23.4 SAVE 和 BGSAVE 的区别](#23.4 SAVE 和 BGSAVE 的区别)
    • [23.5 fork 和写时复制](#23.5 fork 和写时复制)
    • [23.6 RDB 优点](#23.6 RDB 优点)
    • [23.7 RDB 缺点](#23.7 RDB 缺点)
    • [23.8 RDB 适合什么场景](#23.8 RDB 适合什么场景)
    • [23.9 面试回答模板](#23.9 面试回答模板)
  • [24. AOF 持久化](#24. AOF 持久化)
    • [24.1 AOF 是什么](#24.1 AOF 是什么)
    • [24.2 AOF 工作流程](#24.2 AOF 工作流程)
    • [24.3 AOF 三种写回策略](#24.3 AOF 三种写回策略)
    • [24.4 appendfsync 配置](#24.4 appendfsync 配置)
    • [24.5 Redis 7 Multi Part AOF](#24.5 Redis 7 Multi Part AOF)
    • [24.6 AOF 优点](#24.6 AOF 优点)
    • [24.7 AOF 缺点](#24.7 AOF 缺点)
    • [24.8 AOF 异常修复](#24.8 AOF 异常修复)
    • [24.9 面试回答模板](#24.9 面试回答模板)
  • [25. AOF 重写机制](#25. AOF 重写机制)
    • [25.1 为什么需要 AOF 重写](#25.1 为什么需要 AOF 重写)
    • [25.2 AOF 重写触发方式](#25.2 AOF 重写触发方式)
    • [25.3 AOF 重写原理](#25.3 AOF 重写原理)
    • [25.4 AOF 重写期间为什么还写旧 AOF](#25.4 AOF 重写期间为什么还写旧 AOF)
    • [25.5 面试回答模板](#25.5 面试回答模板)
  • [26. RDB 和 AOF 对比](#26. RDB 和 AOF 对比)
    • [26.1 核心对比表](#26.1 核心对比表)
    • [26.2 RDB 和 AOF 可以同时开启吗](#26.2 RDB 和 AOF 可以同时开启吗)
    • [26.3 生产中怎么选](#26.3 生产中怎么选)
    • [26.4 面试回答模板](#26.4 面试回答模板)
  • [27. RDB-AOF 混合持久化](#27. RDB-AOF 混合持久化)
    • [27.1 混合持久化是什么](#27.1 混合持久化是什么)
    • [27.2 混合持久化优点](#27.2 混合持久化优点)
    • [27.3 混合持久化缺点](#27.3 混合持久化缺点)
    • [27.4 面试回答模板](#27.4 面试回答模板)
  • [28. 纯缓存模式](#28. 纯缓存模式)
    • [28.1 什么是纯缓存模式](#28.1 什么是纯缓存模式)
    • [28.2 什么时候可以关闭持久化](#28.2 什么时候可以关闭持久化)
    • [28.3 面试回答模板](#28.3 面试回答模板)
  • [29. Redis 事务](#29. Redis 事务)
    • [29.1 Redis 事务是什么](#29.1 Redis 事务是什么)
    • [29.2 Redis 事务特点](#29.2 Redis 事务特点)
    • [29.3 Redis 事务错误情况](#29.3 Redis 事务错误情况)
    • [29.4 WATCH 乐观锁](#29.4 WATCH 乐观锁)
    • [29.5 Redis 事务和 MySQL 事务区别](#29.5 Redis 事务和 MySQL 事务区别)
    • [29.6 面试回答模板](#29.6 面试回答模板)
  • [30. Pipeline 管道](#30. Pipeline 管道)
    • [30.1 Pipeline 解决什么问题](#30.1 Pipeline 解决什么问题)
    • [30.2 Pipeline 示例](#30.2 Pipeline 示例)
    • [30.3 Pipeline 和事务区别](#30.3 Pipeline 和事务区别)
    • [30.4 Pipeline 和 MGET/MSET 区别](#30.4 Pipeline 和 MGET/MSET 区别)
    • [30.5 Pipeline 注意点](#30.5 Pipeline 注意点)
    • [30.6 面试回答模板](#30.6 面试回答模板)
  • [31. 发布订阅 Pub/Sub](#31. 发布订阅 Pub/Sub)
    • [31.1 Pub/Sub 是什么](#31.1 Pub/Sub 是什么)
    • [31.2 Pub/Sub 使用流程](#31.2 Pub/Sub 使用流程)
    • [31.3 Pub/Sub 缺点](#31.3 Pub/Sub 缺点)
    • [31.4 Pub/Sub 和 Stream 对比](#31.4 Pub/Sub 和 Stream 对比)
    • [31.5 面试回答模板](#31.5 面试回答模板)
  • [32. Lua 脚本](#32. Lua 脚本)
    • [32.1 Redis 为什么需要 Lua](#32.1 Redis 为什么需要 Lua)
    • [32.2 Lua 基本命令](#32.2 Lua 基本命令)
    • [32.3 Lua 解锁脚本](#32.3 Lua 解锁脚本)
    • [32.4 Lua 的优点](#32.4 Lua 的优点)
    • [32.5 Lua 注意点](#32.5 Lua 注意点)
    • [32.6 面试回答模板](#32.6 面试回答模板)
  • [33. 缓存三大问题总览](#33. 缓存三大问题总览)
  • [34. 缓存穿透](#34. 缓存穿透)
    • [34.1 缓存穿透是什么](#34.1 缓存穿透是什么)
    • [34.2 缓存穿透的典型场景](#34.2 缓存穿透的典型场景)
    • [34.3 解决方案一:缓存空对象](#34.3 解决方案一:缓存空对象)
    • [34.4 解决方案二:布隆过滤器](#34.4 解决方案二:布隆过滤器)
    • [34.5 解决方案三:参数校验和限流](#34.5 解决方案三:参数校验和限流)
    • [34.6 面试回答模板](#34.6 面试回答模板)
  • [35. 布隆过滤器](#35. 布隆过滤器)
    • [35.1 布隆过滤器是什么](#35.1 布隆过滤器是什么)
    • [35.2 为什么布隆过滤器会误判](#35.2 为什么布隆过滤器会误判)
    • [35.3 为什么布隆过滤器不能删除](#35.3 为什么布隆过滤器不能删除)
    • [35.4 布隆过滤器优缺点](#35.4 布隆过滤器优缺点)
    • [35.5 面试回答模板](#35.5 面试回答模板)
  • [36. 缓存击穿](#36. 缓存击穿)
    • [36.1 缓存击穿是什么](#36.1 缓存击穿是什么)
    • [36.2 缓存击穿和缓存穿透的区别](#36.2 缓存击穿和缓存穿透的区别)
    • [36.3 解决方案一:热点 key 不设置过期时间](#36.3 解决方案一:热点 key 不设置过期时间)
    • [36.4 解决方案二:互斥锁重建缓存](#36.4 解决方案二:互斥锁重建缓存)
    • [36.5 解决方案三:逻辑过期](#36.5 解决方案三:逻辑过期)
    • [36.6 解决方案四:双缓存](#36.6 解决方案四:双缓存)
    • [36.7 面试回答模板](#36.7 面试回答模板)
  • [37. 缓存雪崩](#37. 缓存雪崩)
    • [37.1 缓存雪崩是什么](#37.1 缓存雪崩是什么)
    • [37.2 缓存雪崩危害](#37.2 缓存雪崩危害)
    • [37.3 解决方案一:过期时间加随机值](#37.3 解决方案一:过期时间加随机值)
    • [37.4 解决方案二:热点数据预热](#37.4 解决方案二:热点数据预热)
    • [37.5 解决方案三:Redis 高可用](#37.5 解决方案三:Redis 高可用)
    • [37.6 解决方案四:多级缓存](#37.6 解决方案四:多级缓存)
    • [37.7 解决方案五:限流、降级、熔断](#37.7 解决方案五:限流、降级、熔断)
    • [37.8 解决方案六:开启持久化](#37.8 解决方案六:开启持久化)
    • [37.9 面试回答模板](#37.9 面试回答模板)
  • [38. 缓存预热](#38. 缓存预热)
    • [38.1 缓存预热是什么](#38.1 缓存预热是什么)
    • [38.2 适合预热的数据](#38.2 适合预热的数据)
    • [38.3 实现方式](#38.3 实现方式)
    • [38.4 面试回答模板](#38.4 面试回答模板)
  • [39. 缓存和数据库双写一致性](#39. 缓存和数据库双写一致性)
    • [39.1 为什么会有一致性问题](#39.1 为什么会有一致性问题)
    • [39.2 四种更新策略](#39.2 四种更新策略)
      • [39.2.1 先更新数据库,再更新缓存](#39.2.1 先更新数据库,再更新缓存)
      • [39.2.2 先更新缓存,再更新数据库](#39.2.2 先更新缓存,再更新数据库)
      • [39.2.3 先删除缓存,再更新数据库](#39.2.3 先删除缓存,再更新数据库)
      • [39.2.4 先更新数据库,再删除缓存](#39.2.4 先更新数据库,再删除缓存)
    • [39.3 为什么不是更新缓存,而是删除缓存](#39.3 为什么不是更新缓存,而是删除缓存)
    • [39.4 延时双删](#39.4 延时双删)
    • [39.5 MQ 重试方案](#39.5 MQ 重试方案)
    • [39.6 canal 监听 binlog 方案](#39.6 canal 监听 binlog 方案)
    • [39.7 强一致能做到吗](#39.7 强一致能做到吗)
    • [39.8 面试回答模板](#39.8 面试回答模板)
  • [40. 四种缓存更新策略面试总结](#40. 四种缓存更新策略面试总结)
  • [41. 本段面试高频题速查](#41. 本段面试高频题速查)
    • [41.1 Redis key 过期后立即删除吗](#41.1 Redis key 过期后立即删除吗)
    • [41.2 Redis 内存淘汰策略有哪些](#41.2 Redis 内存淘汰策略有哪些)
    • [41.3 LRU 和 LFU 区别](#41.3 LRU 和 LFU 区别)
    • [41.4 RDB 和 AOF 区别](#41.4 RDB 和 AOF 区别)
    • [41.5 SAVE 和 BGSAVE 区别](#41.5 SAVE 和 BGSAVE 区别)
    • [41.6 AOF 三种刷盘策略](#41.6 AOF 三种刷盘策略)
    • [41.7 AOF 重写原理](#41.7 AOF 重写原理)
    • [41.8 Redis 事务支持回滚吗](#41.8 Redis 事务支持回滚吗)
    • [41.9 Pipeline 是事务吗](#41.9 Pipeline 是事务吗)
    • [41.10 Pub/Sub 能做可靠消息队列吗](#41.10 Pub/Sub 能做可靠消息队列吗)
    • [41.11 缓存穿透、击穿、雪崩区别](#41.11 缓存穿透、击穿、雪崩区别)
    • [41.12 缓存和数据库怎么保证一致性](#41.12 缓存和数据库怎么保证一致性)
  • [42. BigKey](#42. BigKey)
    • [42.1 什么是 BigKey](#42.1 什么是 BigKey)
    • [42.2 BigKey 的危害](#42.2 BigKey 的危害)
    • [42.3 BigKey 如何产生](#42.3 BigKey 如何产生)
    • [42.4 BigKey 如何发现](#42.4 BigKey 如何发现)
      • [42.4.1 redis-cli --bigkeys](#42.4.1 redis-cli --bigkeys)
      • [42.4.2 MEMORY USAGE](#42.4.2 MEMORY USAGE)
      • [42.4.3 SCAN 渐进式扫描](#42.4.3 SCAN 渐进式扫描)
      • [42.4.4 业务侧监控](#42.4.4 业务侧监控)
    • [42.5 BigKey 如何删除](#42.5 BigKey 如何删除)
      • [42.5.1 String 类型 BigKey](#42.5.1 String 类型 BigKey)
      • [42.5.2 Hash 类型 BigKey](#42.5.2 Hash 类型 BigKey)
      • [42.5.3 List 类型 BigKey](#42.5.3 List 类型 BigKey)
      • [42.5.4 Set 类型 BigKey](#42.5.4 Set 类型 BigKey)
      • [42.5.5 ZSet 类型 BigKey](#42.5.5 ZSet 类型 BigKey)
    • [42.6 BigKey 生产治理建议](#42.6 BigKey 生产治理建议)
    • [42.7 面试回答模板](#42.7 面试回答模板)
  • [43. MoreKey:海量 key 问题](#43. MoreKey:海量 key 问题)
    • [43.1 什么是 MoreKey](#43.1 什么是 MoreKey)
    • [43.2 MoreKey 的危害](#43.2 MoreKey 的危害)
    • [43.3 为什么不能用 KEYS *](#43.3 为什么不能用 KEYS *)
    • [43.4 应该用 SCAN](#43.4 应该用 SCAN)
    • [43.5 SCAN 使用示例](#43.5 SCAN 使用示例)
    • [43.6 面试回答模板](#43.6 面试回答模板)
  • [44. Redis 分布式锁](#44. Redis 分布式锁)
    • [44.1 为什么需要分布式锁](#44.1 为什么需要分布式锁)
    • [44.2 分布式锁的典型场景](#44.2 分布式锁的典型场景)
    • [44.3 分布式锁需要满足哪些条件](#44.3 分布式锁需要满足哪些条件)
    • [44.4 最简单的错误写法](#44.4 最简单的错误写法)
    • [44.5 正确加锁基础版](#44.5 正确加锁基础版)
    • [44.6 为什么 value 要用 uuid](#44.6 为什么 value 要用 uuid)
    • [44.7 错误解锁方式](#44.7 错误解锁方式)
    • [44.8 正确解锁方式:Lua 脚本](#44.8 正确解锁方式:Lua 脚本)
    • [44.9 基础版 Redis 分布式锁问题](#44.9 基础版 Redis 分布式锁问题)
    • [44.10 面试回答模板](#44.10 面试回答模板)
  • [45. Redis 可重入分布式锁](#45. Redis 可重入分布式锁)
    • [45.1 什么是可重入锁](#45.1 什么是可重入锁)
    • [45.2 为什么 Redis String 锁不支持可重入](#45.2 为什么 Redis String 锁不支持可重入)
    • [45.3 用 Hash 实现可重入锁](#45.3 用 Hash 实现可重入锁)
    • [45.4 加锁逻辑](#45.4 加锁逻辑)
    • [45.5 加锁 Lua 脚本](#45.5 加锁 Lua 脚本)
    • [45.6 解锁逻辑](#45.6 解锁逻辑)
    • [45.7 解锁 Lua 脚本](#45.7 解锁 Lua 脚本)
    • [45.8 面试回答模板](#45.8 面试回答模板)
  • [46. Redis 分布式锁自动续期](#46. Redis 分布式锁自动续期)
    • [46.1 为什么需要自动续期](#46.1 为什么需要自动续期)
    • [46.2 自动续期思路](#46.2 自动续期思路)
    • [46.3 自动续期 Lua 脚本](#46.3 自动续期 Lua 脚本)
    • [46.4 自动续期注意点](#46.4 自动续期注意点)
    • [46.5 面试回答模板](#46.5 面试回答模板)
  • [47. Redisson](#47. Redisson)
    • [47.1 Redisson 是什么](#47.1 Redisson 是什么)
    • [47.2 Redisson 分布式锁特点](#47.2 Redisson 分布式锁特点)
    • [47.3 Redisson 看门狗 watchdog](#47.3 Redisson 看门狗 watchdog)
    • [47.4 Redisson 解锁注意点](#47.4 Redisson 解锁注意点)
    • [47.5 Redisson 面试回答模板](#47.5 Redisson 面试回答模板)
  • [48. RedLock](#48. RedLock)
    • [48.1 RedLock 是什么](#48.1 RedLock 是什么)
    • [48.2 RedLock 基本思想](#48.2 RedLock 基本思想)
    • [48.3 RedLock 的优点](#48.3 RedLock 的优点)
    • [48.4 RedLock 的争议](#48.4 RedLock 的争议)
    • [48.5 面试回答模板](#48.5 面试回答模板)
  • [49. Redis 主从复制](#49. Redis 主从复制)
    • [49.1 主从复制是什么](#49.1 主从复制是什么)
    • [49.2 主从复制流程](#49.2 主从复制流程)
      • [49.2.1 第一次连接:全量复制](#49.2.1 第一次连接:全量复制)
      • [49.2.2 稳定阶段:增量复制](#49.2.2 稳定阶段:增量复制)
      • [49.2.3 断线重连:部分重同步](#49.2.3 断线重连:部分重同步)
    • [49.3 主从复制为什么会延迟](#49.3 主从复制为什么会延迟)
    • [49.4 主从复制缺点](#49.4 主从复制缺点)
    • [49.5 面试回答模板](#49.5 面试回答模板)
  • [50. Redis 哨兵 Sentinel](#50. Redis 哨兵 Sentinel)
    • [50.1 Sentinel 是什么](#50.1 Sentinel 是什么)
    • [50.2 Sentinel 架构](#50.2 Sentinel 架构)
    • [50.3 主观下线 SDOWN](#50.3 主观下线 SDOWN)
    • [50.4 客观下线 ODOWN](#50.4 客观下线 ODOWN)
    • [50.5 Sentinel 故障转移流程](#50.5 Sentinel 故障转移流程)
    • [50.6 Leader Sentinel 如何选出](#50.6 Leader Sentinel 如何选出)
    • [50.7 新 master 如何选择](#50.7 新 master 如何选择)
    • [50.8 Sentinel 缺点](#50.8 Sentinel 缺点)
    • [50.9 面试回答模板](#50.9 面试回答模板)
  • [51. Redis Cluster](#51. Redis Cluster)
    • [51.1 Redis Cluster 是什么](#51.1 Redis Cluster 是什么)
    • [51.2 Cluster 的 16384 个槽](#51.2 Cluster 的 16384 个槽)
    • [51.3 为什么是 16384 个槽](#51.3 为什么是 16384 个槽)
      • [51.3.1 心跳包大小](#51.3.1 心跳包大小)
      • [51.3.2 Redis Cluster 不建议太多 master](#51.3.2 Redis Cluster 不建议太多 master)
      • [51.3.3 槽位少时 bitmap 更容易压缩](#51.3.3 槽位少时 bitmap 更容易压缩)
    • [51.4 为什么 Redis Cluster 不用一致性哈希](#51.4 为什么 Redis Cluster 不用一致性哈希)
    • [51.5 Cluster 读写重定向](#51.5 Cluster 读写重定向)
    • [51.6 redis-cli 连接集群](#51.6 redis-cli 连接集群)
    • [51.7 多 key 操作问题](#51.7 多 key 操作问题)
    • [51.8 hash tag 解决多 key 同槽](#51.8 hash tag 解决多 key 同槽)
    • [51.9 Cluster 常用命令](#51.9 Cluster 常用命令)
    • [51.10 Cluster 是否保证强一致性](#51.10 Cluster 是否保证强一致性)
    • [51.11 面试回答模板](#51.11 面试回答模板)
  • [52. Redis 分区方案对比](#52. Redis 分区方案对比)
    • [52.1 哈希取余](#52.1 哈希取余)
    • [52.2 一致性哈希](#52.2 一致性哈希)
    • [52.3 哈希槽](#52.3 哈希槽)
    • [52.4 面试回答模板](#52.4 面试回答模板)
  • [53. Redis 生产调优](#53. Redis 生产调优)
    • [53.1 禁用危险命令](#53.1 禁用危险命令)
    • [53.2 避免 BigKey](#53.2 避免 BigKey)
    • [53.3 避免 HotKey](#53.3 避免 HotKey)
    • [53.4 合理设置 TTL](#53.4 合理设置 TTL)
    • [53.5 开启 lazy free](#53.5 开启 lazy free)
    • [53.6 合理使用 Pipeline](#53.6 合理使用 Pipeline)
    • [53.7 慢查询排查](#53.7 慢查询排查)
    • [53.8 内存监控](#53.8 内存监控)
    • [53.9 主从复制监控](#53.9 主从复制监控)
    • [53.10 持久化监控](#53.10 持久化监控)
    • [53.11 面试回答模板](#53.11 面试回答模板)
  • [54. Redis 常用排查命令](#54. Redis 常用排查命令)
    • [54.1 基础信息](#54.1 基础信息)
    • [54.2 key 相关](#54.2 key 相关)
    • [54.3 集合长度](#54.3 集合长度)
    • [54.4 BigKey](#54.4 BigKey)
    • [54.5 慢查询](#54.5 慢查询)
    • [54.6 复制状态](#54.6 复制状态)
    • [54.7 集群状态](#54.7 集群状态)
  • [55. Redis 面试高频题最终清单](#55. Redis 面试高频题最终清单)
    • [55.1 基础概念类](#55.1 基础概念类)
      • [1. Redis 是什么?](#1. Redis 是什么?)
      • [2. Redis 和 MySQL 有什么区别?](#2. Redis 和 MySQL 有什么区别?)
      • [3. Redis 为什么快?](#3. Redis 为什么快?)
      • [4. Redis 是单线程吗?](#4. Redis 是单线程吗?)
    • [55.2 数据类型类](#55.2 数据类型类)
      • [5. Redis 有哪些常见数据类型?](#5. Redis 有哪些常见数据类型?)
      • [6. String 底层是什么?](#6. String 底层是什么?)
      • [7. Hash 底层是什么?](#7. Hash 底层是什么?)
      • [8. List 底层是什么?](#8. List 底层是什么?)
      • [9. Set 底层是什么?](#9. Set 底层是什么?)
      • [10. ZSet 底层是什么?](#10. ZSet 底层是什么?)
      • [11. 跳表是什么?](#11. 跳表是什么?)
      • [12. Bitmap 适合什么场景?](#12. Bitmap 适合什么场景?)
      • [13. HyperLogLog 适合什么场景?](#13. HyperLogLog 适合什么场景?)
      • [14. Stream 和 Pub/Sub 区别?](#14. Stream 和 Pub/Sub 区别?)
    • [55.3 过期、淘汰、持久化类](#55.3 过期、淘汰、持久化类)
      • [15. Redis key 过期后会立刻删除吗?](#15. Redis key 过期后会立刻删除吗?)
      • [16. Redis 内存淘汰策略有哪些?](#16. Redis 内存淘汰策略有哪些?)
      • [17. RDB 和 AOF 区别?](#17. RDB 和 AOF 区别?)
      • [18. AOF 重写原理?](#18. AOF 重写原理?)
    • [55.4 缓存问题类](#55.4 缓存问题类)
      • [19. 缓存穿透是什么?怎么解决?](#19. 缓存穿透是什么?怎么解决?)
      • [20. 布隆过滤器是什么?](#20. 布隆过滤器是什么?)
      • [21. 缓存击穿是什么?怎么解决?](#21. 缓存击穿是什么?怎么解决?)
      • [22. 缓存雪崩是什么?怎么解决?](#22. 缓存雪崩是什么?怎么解决?)
      • [23. 缓存和数据库怎么保证一致性?](#23. 缓存和数据库怎么保证一致性?)
    • [55.5 分布式锁类](#55.5 分布式锁类)
      • [24. Redis 分布式锁怎么实现?](#24. Redis 分布式锁怎么实现?)
      • [25. 为什么不能用 SETNX + EXPIRE?](#25. 为什么不能用 SETNX + EXPIRE?)
      • [26. 为什么解锁要用 Lua?](#26. 为什么解锁要用 Lua?)
      • [27. Redis 可重入锁怎么实现?](#27. Redis 可重入锁怎么实现?)
      • [28. Redisson watchdog 是什么?](#28. Redisson watchdog 是什么?)
      • [29. RedLock 是什么?](#29. RedLock 是什么?)
    • [55.6 高可用和集群类](#55.6 高可用和集群类)
      • [30. Redis 主从复制流程?](#30. Redis 主从复制流程?)
      • [31. Sentinel 是什么?](#31. Sentinel 是什么?)
      • [32. Sentinel 主观下线和客观下线区别?](#32. Sentinel 主观下线和客观下线区别?)
      • [33. Sentinel 如何选择新 master?](#33. Sentinel 如何选择新 master?)
      • [34. Redis Cluster 是什么?](#34. Redis Cluster 是什么?)
      • [35. Redis Cluster 为什么是 16384 个槽?](#35. Redis Cluster 为什么是 16384 个槽?)
      • [36. Redis Cluster 支持强一致吗?](#36. Redis Cluster 支持强一致吗?)
      • [37. Redis Cluster 多 key 操作为什么可能失败?](#37. Redis Cluster 多 key 操作为什么可能失败?)
    • [55.7 生产调优类](#55.7 生产调优类)
      • [38. BigKey 有什么危害?](#38. BigKey 有什么危害?)
      • [39. BigKey 怎么发现?](#39. BigKey 怎么发现?)
      • [40. BigKey 怎么删除?](#40. BigKey 怎么删除?)
      • [41. 为什么不能用 KEYS *?](#41. 为什么不能用 KEYS *?)
      • [42. SCAN 有什么特点?](#42. SCAN 有什么特点?)
      • [43. Redis 生产中如何避免缓存雪崩?](#43. Redis 生产中如何避免缓存雪崩?)
      • [44. Redis 生产中如何调优?](#44. Redis 生产中如何调优?)
  • [56. Redis 面试总复习路线](#56. Redis 面试总复习路线)
  • [57. 面试回答原则](#57. 面试回答原则)
    • [57.1 先结论,后展开](#57.1 先结论,后展开)
    • [57.2 必须体现工程风险](#57.2 必须体现工程风险)
    • [57.3 缓存问题要带场景](#57.3 缓存问题要带场景)
    • [57.4 高可用要说明不等于强一致](#57.4 高可用要说明不等于强一致)
    • [57.5 生产题要讲权衡](#57.5 生产题要讲权衡)
  • [58. 最终总结](#58. 最终总结)
  • [Redis 面试讲义补丁:建议补充的增强点](#Redis 面试讲义补丁:建议补充的增强点)
    • [1. HotKey 热点 Key](#1. HotKey 热点 Key)
    • [2. 缓存预热、缓存降级、缓存熔断](#2. 缓存预热、缓存降级、缓存熔断)
    • [3. Redis 限流](#3. Redis 限流)
    • [4. Redis 事务、Lua、Pipeline 的区别再压缩](#4. Redis 事务、Lua、Pipeline 的区别再压缩)
    • [5. Redis Cluster 的脑裂和数据丢失](#5. Redis Cluster 的脑裂和数据丢失)
    • [6. Redis 客户端连接与连接池](#6. Redis 客户端连接与连接池)

目标:每次面试前,按照这份讲义从上到下过一遍,就能系统回顾 Redis 高频考点。

适用方向:后端开发、Java/Go/C++ 服务端、云原生后端、分布式系统基础面试。

核心主线:Redis 是什么 → 为什么快 → 数据类型怎么用 → 底层结构怎么实现 → 持久化与高可用 → 缓存问题 → 分布式锁 → 集群与生产调优。


1. Redis 是什么

1.1 Redis 的定义

Redis 全称是 Remote Dictionary Server,即远程字典服务器。

它是一个基于内存的高性能 Key-Value 数据库,也可以理解为一个支持多种数据结构的内存型 NoSQL 系统。

Redis 的核心特点:

  1. 数据主要存储在内存中,所以读写速度很快。
  2. 支持丰富的数据类型,例如 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、GEO、Stream 等。
  3. 支持持久化,可以通过 RDB、AOF、RDB+AOF 混合持久化把内存数据落盘。
  4. 支持主从复制、哨兵、Cluster 集群,能够实现高可用和水平扩展。
  5. 支持 Lua 脚本、事务、发布订阅、Pipeline、缓存淘汰策略等能力。
  6. 常用于缓存、分布式锁、排行榜、计数器、Session、消息队列、签到打卡、UV 统计、附近的人等业务场景。

1.2 面试回答模板

text 复制代码
Redis 是一个基于内存的高性能 Key-Value 数据库,底层用 C 语言实现。
它不仅仅是缓存,还提供了 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、GEO、Stream 等多种数据结构。
在工程中 Redis 常用于缓存、分布式锁、计数器、排行榜、分布式 Session、消息队列、签到统计、UV 统计等场景。
同时 Redis 还支持 RDB/AOF 持久化、主从复制、哨兵和 Cluster 集群,因此既可以做高性能缓存,也可以支撑一定的高可用和分布式场景。

2. Redis 和 MySQL 的关系

Redis 和 MySQL 不是替代关系,而是配合关系。

对比项 Redis MySQL
类型 NoSQL / Key-Value 关系型数据库
存储位置 主要在内存 主要在磁盘
访问速度 相对较慢
数据模型 Key-Value、多数据结构 表、行、列
适合场景 缓存、计数、排行榜、锁、热点数据 事务、复杂查询、强一致持久数据
一致性 通常最终一致 更适合做权威数据源

面试回答模板:

text 复制代码
Redis 通常放在 MySQL 前面作为缓存层,用来承接高并发读请求,减少数据库压力。
MySQL 作为权威数据源,负责持久化和事务一致性;Redis 负责热点数据加速。
所以二者不是替代关系,而是组合关系:MySQL 保证数据正确性,Redis 提升访问性能。

3. Redis 常见应用场景

3.1 分布式缓存

最常见场景:请求先查 Redis,Redis 没有再查 MySQL,然后把结果回写 Redis。

典型流程:

text 复制代码
客户端请求
  ↓
查询 Redis
  ↓
命中:直接返回
  ↓
未命中:查询 MySQL
  ↓
把结果写入 Redis
  ↓
返回客户端

适合缓存的数据:

  1. 读多写少。
  2. 访问频率高。
  3. 数据允许短时间不一致。
  4. 查询 MySQL 成本较高。
  5. 结果数据体积不要太大。

3.2 分布式 Session

传统单机 Session 存在 JVM 内存中,多台服务部署后 Session 无法共享。

解决方式:

text 复制代码
用户登录成功
  ↓
生成 token/sessionId
  ↓
用户信息写入 Redis
  ↓
后续请求携带 token
  ↓
服务从 Redis 读取用户状态

好处:

  1. 多个服务实例共享登录状态。
  2. 支持水平扩容。
  3. 可通过 TTL 控制登录过期时间。

3.3 分布式锁

用于控制多个服务实例对同一资源的并发访问。

常见场景:

  1. 秒杀扣库存。
  2. 防止重复提交。
  3. 定时任务只允许一个节点执行。
  4. 订单状态流转防并发修改。
  5. 缓存击穿时互斥重建缓存。

核心要求:

text 复制代码
互斥性:同一时刻只能有一个客户端持有锁。
防死锁:锁必须有过期时间。
不误删:只能释放自己的锁。
原子性:加锁和设置过期时间必须原子,解锁判断和删除必须原子。
可重入:同一线程可以重复获得同一把锁。
自动续期:业务未执行完时锁不能提前过期。

3.4 计数器

利用 Redis String 的自增命令:

redis 复制代码
INCR article:read:1001
INCRBY article:like:1001 1
DECR stock:sku:1001

适合:

  1. 阅读数。
  2. 点赞数。
  3. 下载数。
  4. 库存扣减。
  5. 限流计数。

注意:

text 复制代码
INCR/DECR 是 Redis 单命令,具备原子性。
但复杂库存业务不能只靠 DECR,还要考虑库存不能小于 0、订单幂等、事务补偿等问题。

3.5 排行榜

使用 ZSet。

redis 复制代码
ZADD hot:rank 100 "文章A"
ZINCRBY hot:rank 1 "文章A"
ZREVRANGE hot:rank 0 9 WITHSCORES

适合:

  1. 热搜榜。
  2. 礼物榜。
  3. 积分榜。
  4. 销量榜。
  5. 游戏排名。

原因:

text 复制代码
ZSet 每个元素都有 score,Redis 可以按照 score 排序,非常适合排行榜。

3.6 点赞、关注、共同好友

使用 Set。

redis 复制代码
SADD user:1:follow 2
SADD user:2:fans 1

SINTER user:1:follow user:2:follow
SDIFF user:1:follow user:2:follow
SUNION user:1:follow user:2:follow

适合:

  1. 用户关注列表。
  2. 粉丝列表。
  3. 共同关注。
  4. 可能认识的人。
  5. 点赞去重。

3.7 签到打卡

使用 Bitmap。

一个用户一天签到状态只需要 1 bit:

redis 复制代码
SETBIT sign:2026:05:user:1001 3 1
GETBIT sign:2026:05:user:1001 3
BITCOUNT sign:2026:05:user:1001

适合:

  1. 每日签到。
  2. 活跃统计。
  3. 用户是否登录。
  4. 广告是否点击。
  5. 连续打卡判断。

3.8 UV 统计

使用 HyperLogLog。

redis 复制代码
PFADD uv:home:2026-05-04 user1 user2 user3
PFCOUNT uv:home:2026-05-04

适合:

  1. 首页 UV。
  2. 文章 UV。
  3. 搜索关键词去重统计。
  4. 大规模去重计数。

特点:

text 复制代码
HyperLogLog 不保存具体元素,只估算基数。
优点是内存极小,每个 key 大约 12KB。
缺点是有误差,Redis HyperLogLog 标准误差约 0.81%。

3.9 附近的人 / 附近酒店

使用 GEO。

redis 复制代码
GEOADD city 116.403963 39.915119 "天安门"
GEORADIUS city 116.418017 39.914402 10 km WITHDIST WITHCOORD COUNT 10

适合:

  1. 附近的人。
  2. 附近酒店。
  3. 附近门店。
  4. 附近车辆。
  5. 外卖配送范围。

3.10 消息队列

Redis 可以用三种方式做消息队列:

方案 特点 问题
List LPUSH + RPOP / BRPOP,简单队列 不适合复杂 MQ 场景
Pub/Sub 发布订阅,实时广播 不持久化,无 ACK,消息易丢
Stream Redis 5.0 新增,支持持久化、消费组、ACK 更像轻量级 MQ

面试建议:

text 复制代码
如果只是简单异步削峰,可以用 Redis Stream。
如果是核心链路、强可靠消息、复杂消费语义,建议使用 Kafka、RocketMQ、RabbitMQ 等专业 MQ。

4. Redis 为什么快

这是 Redis 高频必问题。

4.1 主要原因

Redis 快,不是单一原因,而是多因素叠加:

  1. 基于内存操作,避免磁盘 IO。
  2. 数据结构设计高效,很多操作时间复杂度是 O(1) 或 O(logN)。
  3. 命令执行主线程单线程,避免锁竞争和线程上下文切换。
  4. 使用 IO 多路复用,一个线程可以处理大量连接。
  5. Redis 协议 RESP 简单,解析成本低。
  6. 支持 Pipeline,减少网络 RTT。
  7. Redis 6/7 引入多 IO 线程,进一步优化网络读写与协议解析。

4.2 面试回答模板

text 复制代码
Redis 快主要有几个原因。
第一,Redis 数据主要在内存中,读写不需要走磁盘随机 IO。
第二,Redis 的数据结构经过专门设计,比如哈希表、跳表、quicklist、listpack 等,常见操作复杂度很低。
第三,Redis 命令执行采用单线程模型,避免了多线程锁竞争和上下文切换。
第四,Redis 使用 IO 多路复用,基于 epoll 等机制,一个线程就可以监听大量客户端连接。
第五,Redis 协议简单,解析成本低,而且可以通过 Pipeline 减少 RTT。
所以 Redis 快不是因为"单线程"本身,而是内存 + 高效数据结构 + IO 多路复用 + 单线程命令执行共同作用。

5. Redis 到底是单线程还是多线程

这个问题不能简单回答"是"或者"不是",要分版本、分模块回答。

5.1 Redis 3.x

传统意义上的单线程:

text 复制代码
网络 IO、命令解析、命令执行、结果返回,主要由一个主线程完成。

5.2 Redis 4.x

开始引入后台线程:

text 复制代码
例如 lazy free、异步删除、后台释放大对象。

比如:

redis 复制代码
UNLINK key
FLUSHDB ASYNC
FLUSHALL ASYNC

这些命令会把真正释放内存的工作交给后台线程,减少主线程阻塞。

5.3 Redis 6/7

引入多 IO 线程:

text 复制代码
网络读写、请求协议解析可以交给多个 IO 线程处理。
真正执行 Redis 命令、操作内存数据,仍然由主线程串行执行。

5.4 面试回答模板

text 复制代码
严格来说,Redis 不是整个系统都单线程。
以前说 Redis 单线程,主要指执行命令和操作内存数据的主流程是单线程。
但 Redis 的持久化、异步删除、AOF 重写、主从同步等本来就可能使用子进程或后台线程。
Redis 6 以后又引入了多 IO 线程,用来处理网络读写和协议解析,但真正的命令执行仍然由主线程完成。
所以更准确的说法是:Redis 命令执行线程是单线程,整个 Redis 进程并不是纯单线程。

6. Redis 为什么以前选择单线程

6.1 原因

Redis 早期选择单线程,主要是工程取舍:

  1. Redis 主要瓶颈通常不是 CPU,而是内存和网络。
  2. 单线程模型简单,开发和维护成本低。
  3. 避免多线程锁竞争。
  4. 避免线程上下文切换。
  5. IO 多路复用已经可以支持大量连接。

6.2 面试回答模板

text 复制代码
Redis 早期选择单线程,是因为 Redis 的主要瓶颈不是 CPU,而是内存容量和网络带宽。
既然 CPU 不是核心瓶颈,使用多线程反而会引入锁竞争、上下文切换和实现复杂度。
Redis 通过单线程命令执行配合 IO 多路复用,已经能支持非常高的并发。
所以单线程不是限制,而是 Redis 在当时场景下的一种工程取舍。

7. IO 多路复用

7.1 为什么需要 IO 多路复用

最原始的 BIO 模型:

text 复制代码
一个连接对应一个线程/进程。
如果连接很多,就需要很多线程。
线程创建、销毁、上下文切换成本很高。

问题:

text 复制代码
如果有 1 万个连接,不可能为每个连接都创建一个线程。
即使用线程池,也会有大量线程阻塞在 read/recv 上。

所以需要 IO 多路复用:

text 复制代码
用一个线程监听多个 socket。
哪个 socket 准备好了,操作系统通知应用程序处理哪个。

7.2 BIO、NIO、IO 多路复用对比

模型 特点 问题
BIO 阻塞 IO,一个连接一个线程 线程多,资源消耗大
NIO 非阻塞 IO,用户线程轮询多个 socket 空轮询多,CPU 浪费
IO 多路复用 内核监听多个 FD,就绪后通知应用 高并发网络服务常用

7.3 BIO

BIO 是 Blocking IO,即阻塞 IO。

特点:

text 复制代码
accept 阻塞等待连接。
read 阻塞等待数据。
一个连接如果不发数据,线程就一直卡住。

问题:

text 复制代码
一个客户端连接后迟迟不发数据,服务端线程阻塞在 read。
如果一个连接一个线程,连接数多时线程资源会被耗尽。

7.4 NIO

NIO 是 Non-Blocking IO,即非阻塞 IO。

特点:

text 复制代码
accept 不阻塞。
read 不阻塞。
如果没有数据,立即返回。

问题:

text 复制代码
用户线程需要不断轮询 socket 是否有数据。
连接很多时,即使只有少量 socket 有数据,也要遍历大量 socket,浪费 CPU。

7.5 IO 多路复用

IO 多路复用的核心:

text 复制代码
把多个 socket 文件描述符交给内核监听。
哪个 socket 就绪,内核通知应用程序。
应用程序只处理就绪的 socket。

一句话:

text 复制代码
一个线程可以同时管理多个网络连接。

8. select、poll、epoll 区别

8.1 select

特点:

text 复制代码
把 fd 集合从用户态拷贝到内核态。
内核遍历 fd,看哪些就绪。
返回后用户还要再次遍历,找到具体就绪 fd。

缺点:

  1. 默认最大 fd 数量有限,通常 1024。
  2. 每次调用都要拷贝 fd 集合。
  3. 每次返回后还要遍历所有 fd。
  4. fdset 不能复用。

8.2 poll

改进:

text 复制代码
用 pollfd 数组替代 bitmap,没有 1024 的硬限制。

缺点:

text 复制代码
本质仍然需要拷贝数组,仍然需要遍历所有 fd。

poll 解决了 select 的 fd 数量限制问题,但没有解决重复拷贝和全量遍历问题。


8.3 epoll

核心改进:

  1. fd 只需要注册一次,不用每次重复拷贝。
  2. 内核维护事件就绪队列。
  3. 哪些 fd 就绪,epoll 直接返回就绪列表。
  4. 不需要遍历所有连接。
  5. 适合高并发连接。

8.4 面试回答模板

text 复制代码
select、poll、epoll 都是 IO 多路复用的实现。
select 使用 bitmap 保存 fd 集合,默认有 1024 限制,而且每次都要把 fd 集合拷贝到内核,返回后还要遍历所有 fd。
poll 用数组解决了 fd 数量限制,但仍然需要拷贝和遍历。
epoll 通过事件注册和就绪队列解决了这两个问题,fd 注册一次即可,哪个 fd 有事件就放入就绪队列,应用只处理真正就绪的 fd。
所以高并发网络服务一般使用 epoll,Redis、Nginx 都大量依赖这种模型。

9. Redis 的文件事件处理器

Redis 基于 Reactor 模式处理网络事件。

组成:

text 复制代码
多个 socket
  ↓
IO 多路复用程序
  ↓
文件事件分派器
  ↓
事件处理器

理解:

text 复制代码
IO 多路复用负责监听哪些连接有事件。
文件事件分派器负责把事件分发给对应处理器。
处理器负责读请求、解析命令、执行命令、写响应。

面试回答模板:

text 复制代码
Redis 使用 Reactor 模式处理网络事件。
多个客户端连接对应多个 socket,Redis 通过 IO 多路复用监听这些 socket。
当某个 socket 可读或可写时,文件事件分派器会把事件分发给对应的事件处理器。
这样 Redis 用一个主线程就可以高效处理大量客户端连接。

10. Redis 数据类型总览

Redis 常见数据类型可以分为传统 5 大类型和扩展 5 大类型。

10.1 传统 5 大类型

类型 特点 典型场景
String 单 key 单 value 缓存、计数器、分布式锁
List 有序列表,双端操作 队列、栈、消息队列
Hash field-value 映射 对象缓存、购物车
Set 无序、唯一 标签、关注、去重、交并差
ZSet 唯一元素 + score 排序 排行榜、延迟队列

10.2 扩展 5 大类型

类型 本质/特点 典型场景
Bitmap 基于 String 的 bit 操作 签到、在线状态、二值统计
HyperLogLog 基数估算 UV 统计
GEO 基于 ZSet 附近的人、附近门店
Stream 消息流 消息队列、消费组
Bitfield 位域操作 多 bit 状态压缩存储

11. String 类型

11.1 String 基本特点

String 是 Redis 最基础的数据类型。

特点:

  1. 一个 key 对应一个 value。
  2. 二进制安全,可以存字符串、数字、图片序列化、对象序列化结果。
  3. 单个 String 最大 512MB。
  4. 可以做缓存、计数器、分布式锁、限流等。

常用命令:

redis 复制代码
SET key value
GET key
MSET k1 v1 k2 v2
MGET k1 k2
INCR key
INCRBY key 10
DECR key
APPEND key value
STRLEN key
SETNX key value
SETEX key seconds value
GETSET key value

11.2 String 的使用场景

11.2.1 缓存对象

redis 复制代码
SET user:1001 '{"id":1001,"name":"Tom"}'
GET user:1001

11.2.2 计数器

redis 复制代码
INCR article:1001:read
INCRBY article:1001:like 1

11.2.3 分布式锁基础版

redis 复制代码
SET lock:order:1001 uuid NX EX 30

注意:

text 复制代码
不要用 SETNX 后再单独 EXPIRE,因为两条命令不是原子的。
正确方式是 SET key value NX EX seconds。

11.3 String 底层编码

String 的底层不是简单 C 字符串,而是 SDS。

String 对象有三种编码:

编码 条件 特点
int 能用 long 表示的整数 节省空间
embstr 短字符串,通常小于等于 44 字节 redisObject 和 SDS 连续分配
raw 长字符串 redisObject 和 SDS 分开分配

11.4 Redis 为什么不用 C 字符串,而要自己设计 SDS

C 字符串问题:

  1. 获取长度需要遍历,时间复杂度 O(N)。
  2. \0 作为结束标志,不能安全保存二进制数据。
  3. 扩容容易造成缓冲区溢出。
  4. 修改字符串频繁分配内存,性能差。

SDS 优点:

  1. 记录 len,获取长度 O(1)。
  2. 二进制安全,根据 len 判断长度,不依赖 \0
  3. 空间预分配,减少扩容次数。
  4. 惰性空间释放,缩短字符串时不立即回收,方便后续复用。
  5. 更适合 Redis 高频读写场景。

面试回答模板:

text 复制代码
Redis 没有直接使用 C 字符串,而是设计了 SDS。
因为 C 字符串获取长度需要遍历,复杂度是 O(N),并且以 '\0' 作为结束标志,不适合保存二进制数据。
SDS 内部维护 len 和 alloc,获取长度是 O(1),同时支持空间预分配和惰性释放,可以减少频繁扩容和内存分配。
所以 SDS 更适合 Redis 这种高性能内存数据库。

12. Hash 类型

12.1 Hash 基本特点

Hash 是一个 key 对应多个 field-value。

可以理解为:

text 复制代码
Map<String, Map<String, Object>>

适合存储对象:

redis 复制代码
HSET user:1001 name Tom age 18 city Tokyo
HGET user:1001 name
HGETALL user:1001

常用命令:

redis 复制代码
HSET key field value
HGET key field
HMSET key field value field value
HMGET key field field
HGETALL key
HDEL key field
HLEN key
HEXISTS key field
HKEYS key
HVALS key
HINCRBY key field increment
HSETNX key field value

12.2 Hash 使用场景

12.2.1 用户对象缓存

redis 复制代码
HSET user:1001 name Tom age 18 sex male

优点:

text 复制代码
可以只修改对象的某个字段,不必整体反序列化和覆盖整个 value。

12.2.2 购物车

redis 复制代码
HSET cart:1001 sku:2001 2
HINCRBY cart:1001 sku:2001 1

12.2.3 可重入分布式锁

redis 复制代码
HSET lock:order uuid:threadId 1
HINCRBY lock:order uuid:threadId 1

12.3 Hash 底层结构

Redis 6:

text 复制代码
ziplist + hashtable

Redis 7:

text 复制代码
listpack + hashtable

转换条件:

text 复制代码
当 field-value 数量较少,并且每个 field/value 较短时,使用 listpack。
当元素数量或 value 长度超过阈值时,转成 hashtable。

常见默认阈值:

text 复制代码
hash-max-listpack-entries 512
hash-max-listpack-value 64

面试回答模板:

text 复制代码
Hash 在 Redis 7 中底层主要是 listpack 和 hashtable。
当元素数量比较少,并且 field/value 比较短时,Redis 使用 listpack 来节省内存。
当元素数量超过阈值,或者 field/value 太大时,会转换成 hashtable,以提升查询和修改效率。
这种转换是单向的,一旦转成 hashtable,一般不会再退回 listpack。

13. List 类型

13.1 List 基本特点

List 是有序列表,可以从左右两端插入或弹出。

常用命令:

redis 复制代码
LPUSH key value
RPUSH key value
LPOP key
RPOP key
LRANGE key start stop
LINDEX key index
LLEN key
LREM key count value
LTRIM key start stop
RPOPLPUSH source destination
LSET key index value
LINSERT key BEFORE|AFTER pivot value

13.2 List 使用场景

13.2.1 栈

redis 复制代码
LPUSH stack a
LPOP stack

13.2.2 队列

redis 复制代码
LPUSH queue a
RPOP queue

13.2.3 阻塞队列

redis 复制代码
BRPOP queue 0

13.2.4 简单消息队列

text 复制代码
生产者 LPUSH
消费者 BRPOP

问题:

  1. 消息确认机制弱。
  2. 消费失败不好处理。
  3. 不适合复杂 MQ 场景。

如果要更可靠,优先考虑 Redis Stream 或专业 MQ。


13.3 List 底层结构

Redis 早期:

text 复制代码
ziplist + linkedlist

Redis 3.2 后:

text 复制代码
quicklist

Redis 7:

text 复制代码
quicklist + listpack

quicklist 可以理解为:

text 复制代码
双向链表 + 紧凑列表

为什么这样设计?

text 复制代码
普通链表每个节点都有前后指针,内存碎片多。
ziplist/listpack 连续内存存储,节省空间,但太大时插入删除成本高。
quicklist 把多个 listpack 串成双向链表,兼顾内存节省和两端操作效率。

面试回答模板:

text 复制代码
Redis List 底层不是简单链表,而是 quicklist。
quicklist 可以理解为多个 listpack 组成的双向链表。
这样既保留了链表两端插入删除快的特点,又通过 listpack 紧凑存储减少内存碎片。
所以 List 很适合做队列、栈,但不适合频繁按下标访问中间元素。

14. Set 类型

14.1 Set 基本特点

Set 是无序、元素唯一的集合。

常用命令:

redis 复制代码
SADD key member
SMEMBERS key
SISMEMBER key member
SREM key member
SCARD key
SRANDMEMBER key count
SPOP key count
SMOVE source destination member
SDIFF key1 key2
SUNION key1 key2
SINTER key1 key2
SINTERCARD numkeys key1 key2

14.2 Set 使用场景

14.2.1 点赞去重

redis 复制代码
SADD article:1001:like user:1
SISMEMBER article:1001:like user:1
SCARD article:1001:like

14.2.2 关注关系

redis 复制代码
SADD user:1:follow user:2
SADD user:2:fans user:1

14.2.3 共同关注

redis 复制代码
SINTER user:1:follow user:2:follow

14.2.4 可能认识的人

redis 复制代码
SDIFF user:friend:2 user:friend:1

14.3 Set 底层结构

Set 的底层结构:

text 复制代码
intset + hashtable

当集合中全是整数,并且元素数量较少时,用 intset。

如果出现非整数元素,或者元素数量超过阈值,转成 hashtable。

面试回答模板:

text 复制代码
Redis Set 底层有 intset 和 hashtable 两种实现。
如果集合元素都是整数,并且数量较少,Redis 会使用 intset 节省内存。
如果元素不是整数,或者数量超过阈值,就会转成 hashtable。
hashtable 查询、插入、删除平均复杂度是 O(1),适合大集合。

15. ZSet 类型

15.1 ZSet 基本特点

ZSet 是有序集合。

特点:

text 复制代码
元素唯一。
每个元素关联一个 score。
按照 score 排序。

常用命令:

redis 复制代码
ZADD key score member
ZRANGE key start stop WITHSCORES
ZREVRANGE key start stop WITHSCORES
ZRANGEBYSCORE key min max WITHSCORES LIMIT offset count
ZSCORE key member
ZCARD key
ZREM key member
ZINCRBY key increment member
ZCOUNT key min max
ZRANK key member
ZREVRANK key member
ZMPOP numkeys key MIN|MAX COUNT count

15.2 ZSet 使用场景

15.2.1 排行榜

redis 复制代码
ZINCRBY rank:article 1 article:1001
ZREVRANGE rank:article 0 9 WITHSCORES

15.2.2 延迟队列

redis 复制代码
ZADD delay:queue timestamp taskId
ZRANGEBYSCORE delay:queue 0 now

15.2.3 最新评论分页

redis 复制代码
ZADD comments:product:1001 timestamp commentId
ZREVRANGE comments:product:1001 0 9

15.3 ZSet 底层结构

Redis 6:

text 复制代码
ziplist + skiplist

Redis 7:

text 复制代码
listpack + skiplist

当元素数量少、元素长度短时:

text 复制代码
使用 listpack 节省内存。

当元素数量多或元素较大时:

text 复制代码
使用 skiplist + dict。

为什么需要 dict?

text 复制代码
dict 用于 O(1) 根据 member 找 score。
skiplist 用于按 score 范围查询和排序。

15.4 跳表是什么

跳表可以理解为:

text 复制代码
有序链表 + 多级索引

普通有序链表查询:

text 复制代码
只能从头遍历,O(N)。

跳表:

text 复制代码
给链表建立多级索引,从高层索引快速跳跃定位,再下降到底层链表。
平均查询复杂度 O(logN)。

面试回答模板:

text 复制代码
跳表是一种可以实现近似二分查找的有序链表。
它通过给链表建立多级索引,用空间换时间,把查询复杂度从 O(N) 降到 O(logN)。
Redis ZSet 使用跳表,是因为跳表实现简单,范围查询方便,插入删除也比较高效。
相比红黑树,跳表更容易实现区间遍历;相比 B+ 树,Redis 数据主要在内存中,不需要像磁盘索引那样追求页级 IO 优化。

16. Bitmap

16.1 Bitmap 是什么

Bitmap 本质上不是一种新的底层结构,而是基于 String 的 bit 操作。

一个 bit 只有 0 和 1,适合表示二值状态。

常用命令:

redis 复制代码
SETBIT key offset value
GETBIT key offset
BITCOUNT key
BITOP AND destkey key1 key2
BITOP OR destkey key1 key2
STRLEN key

16.2 Bitmap 使用场景

16.2.1 用户签到

redis 复制代码
SETBIT sign:user:1001:202605 3 1
GETBIT sign:user:1001:202605 3
BITCOUNT sign:user:1001:202605

16.2.2 日活统计

redis 复制代码
SETBIT active:2026-05-04 userId 1
BITCOUNT active:2026-05-04

16.2.3 连续两天活跃用户

redis 复制代码
BITOP AND active:2days active:2026-05-03 active:2026-05-04
BITCOUNT active:2days

16.3 Bitmap 优缺点

优点:

  1. 极省内存。
  2. 位运算快。
  3. 适合海量二值状态统计。

缺点:

  1. offset 如果很大,会造成空间浪费。
  2. 只适合二值状态。
  3. 不适合存储复杂信息。

面试回答模板:

text 复制代码
Bitmap 适合做签到、活跃状态、是否点击这类二值统计。
因为一个用户一天是否签到只需要 1 bit,相比数据库一行记录节省大量空间。
但 Bitmap 的 offset 如果很稀疏,会浪费空间,所以更适合用户 ID 连续或可以映射成连续 offset 的场景。

17. HyperLogLog

17.1 HyperLogLog 是什么

HyperLogLog 用于基数统计,也就是统计去重后的元素数量。

常用命令:

redis 复制代码
PFADD key element
PFCOUNT key
PFMERGE destkey sourcekey1 sourcekey2

特点:

  1. 不保存具体元素。
  2. 只估算不重复元素数量。
  3. 内存固定且很小。
  4. 有误差,标准误差约 0.81%。

17.2 HyperLogLog 使用场景

17.2.1 UV 统计

redis 复制代码
PFADD uv:home:2026-05-04 user1 user2 user3
PFCOUNT uv:home:2026-05-04

17.2.2 多页面 UV 合并

redis 复制代码
PFMERGE uv:site:2026-05-04 uv:home:2026-05-04 uv:detail:2026-05-04
PFCOUNT uv:site:2026-05-04

17.3 HyperLogLog 和 Set 的区别

对比 Set HyperLogLog
是否精确 精确 近似
是否保存元素 保存 不保存
能否返回具体元素 可以 不可以
内存消耗 随元素数量增长 基本固定
适合场景 精确去重 大规模 UV 估算

面试回答模板:

text 复制代码
如果要精确知道哪些用户访问过,用 Set。
如果只关心大规模 UV 数量,不关心具体用户列表,用 HyperLogLog。
HyperLogLog 用少量固定内存估算基数,适合亿级 UV 统计,但它有小误差,不能返回具体元素。

18. GEO

18.1 GEO 是什么

GEO 用于存储经纬度并做地理位置计算。

底层本质:

text 复制代码
GEO 底层基于 ZSet 实现。

常用命令:

redis 复制代码
GEOADD key longitude latitude member
GEOPOS key member
GEODIST key member1 member2 km
GEORADIUS key longitude latitude radius km WITHDIST WITHCOORD COUNT count
GEORADIUSBYMEMBER key member radius km
GEOHASH key member

18.2 GEO 使用场景

  1. 附近的人。
  2. 附近酒店。
  3. 附近门店。
  4. 附近车辆。
  5. 外卖配送范围。

面试回答模板:

text 复制代码
Redis GEO 适合做 LBS 场景,比如附近的人、附近门店。
它可以把经纬度写入 Redis,然后根据某个经纬度或某个成员查找一定半径内的元素。
底层本质上是基于 ZSet 实现的,Redis 会把经纬度编码成 geohash 后作为 score 存储。

19. Stream

19.1 Stream 是什么

Stream 是 Redis 5.0 引入的数据类型,可以看作 Redis 版轻量级消息队列。

特点:

  1. 消息持久化。
  2. 自动生成全局递增 ID。
  3. 支持阻塞读取。
  4. 支持消费组。
  5. 支持 ACK。
  6. 支持 Pending List 保存已读未确认消息。

常用命令:

redis 复制代码
XADD stream * field value
XRANGE stream - +
XREVRANGE stream + -
XLEN stream
XDEL stream id
XTRIM stream MAXLEN count
XREAD COUNT count BLOCK milliseconds STREAMS stream id

XGROUP CREATE stream group $
XREADGROUP GROUP group consumer COUNT count STREAMS stream >
XACK stream group id
XPENDING stream group
XINFO STREAM stream
XINFO GROUPS stream

19.2 Stream 和 Pub/Sub 对比

对比 Pub/Sub Stream
是否持久化 不持久化 持久化
是否支持 ACK 不支持 支持
是否支持消费组 不支持 支持
消息丢失风险
适合场景 即时广播 轻量级 MQ

面试回答模板:

text 复制代码
Redis Pub/Sub 更像广播,消息不会持久化,也没有 ACK,消费者不在线就会丢消息。
Stream 是 Redis 5.0 引入的消息流,支持消息持久化、消费组、ACK 和 Pending List。
所以如果只是简单通知可以用 Pub/Sub,如果要更可靠的 Redis 消息队列,应该用 Stream。
但如果是核心业务消息,仍然更建议使用 Kafka、RocketMQ 等专业 MQ。

20. Redis 底层数据结构总览

20.1 Redis 常见底层结构

Redis 面试常考底层结构:

text 复制代码
SDS 动态字符串
dict 哈希表
ziplist 压缩列表
listpack 紧凑列表
quicklist 快速列表
intset 整数集合
skiplist 跳表
rax 基数树

20.2 Redis 6 和 Redis 7 数据结构变化

Redis 6

数据类型 底层结构
String int / embstr / raw,底层 SDS
Hash ziplist / hashtable
List quicklist + ziplist
Set intset / hashtable
ZSet ziplist / skiplist

Redis 7

数据类型 底层结构
String int / embstr / raw,底层 SDS
Hash listpack / hashtable
List quicklist
Set intset / hashtable
ZSet listpack / skiplist

核心变化:

text 复制代码
Redis 7 中 listpack 逐步替代 ziplist。

面试回答模板:

text 复制代码
Redis 7 相比 Redis 6,一个重要变化是 listpack 替代 ziplist。
String 仍然基于 SDS,有 int、embstr、raw 三种编码。
Hash 在元素少时使用 listpack,元素多时使用 hashtable。
List 主要是 quicklist。
Set 在整数小集合时使用 intset,否则用 hashtable。
ZSet 小集合用 listpack,大集合用 skiplist + dict。

21. Redis 过期删除策略

21.1 Redis key 过期后会立刻删除吗

不会一定立刻删除。

Redis 主要采用:

text 复制代码
惰性删除 + 定期删除

过期时间到了,只代表这个 key 在逻辑上已经过期,不代表它一定已经从内存中物理删除。


21.2 三种过期删除策略

21.2.1 定时删除 / 立即删除

含义:

text 复制代码
key 到期后,立即删除。

优点:

text 复制代码
内存释放最及时。

缺点:

text 复制代码
对 CPU 不友好。
如果大量 key 同时过期,Redis 需要立刻删除大量 key,会占用主线程 CPU,影响正常请求处理。

总结:

text 复制代码
定时删除是用 CPU 时间换内存空间。

21.2.2 惰性删除

含义:

text 复制代码
key 过期后先不删除。
等下次访问这个 key 时,Redis 检查它是否过期。
如果过期,就删除并返回不存在。

优点:

text 复制代码
对 CPU 友好。
不需要专门花 CPU 去扫描过期 key。

缺点:

text 复制代码
对内存不友好。
如果一个 key 已经过期,但一直没有被访问,它就可能一直占用内存。

总结:

text 复制代码
惰性删除是用内存空间换 CPU 时间。

21.2.3 定期删除

含义:

text 复制代码
Redis 每隔一段时间,随机抽取一部分设置了过期时间的 key 进行检查。
如果发现过期,就删除。

特点:

  1. 不是扫描所有 key。
  2. 是随机抽样检查。
  3. 通过控制扫描频率和执行时长,在 CPU 和内存之间折中。

优点:

text 复制代码
比立即删除更节省 CPU。
比惰性删除更节省内存。

缺点:

text 复制代码
因为不是全量扫描,所以仍然可能有过期 key 没有被及时删除。

21.3 Redis 实际采用什么策略

Redis 实际采用:

text 复制代码
惰性删除 + 定期删除 + 内存淘汰策略兜底

解释:

text 复制代码
惰性删除:访问 key 时发现过期才删除。
定期删除:后台周期性随机抽样删除部分过期 key。
内存淘汰:如果过期 key 没有及时删除,导致内存达到 maxmemory,就触发淘汰策略。

21.4 面试回答模板

text 复制代码
Redis 的 key 过期后不会一定立刻删除。
它主要采用惰性删除和定期删除。
惰性删除是在访问 key 时检查是否过期,过期就删除;定期删除是 Redis 周期性随机抽样部分设置了过期时间的 key 进行检查和删除。
如果这两种方式都没有及时释放内存,Redis 内存达到 maxmemory 后,还会通过内存淘汰策略进行兜底。
所以 Redis 过期删除策略本质是在 CPU 和内存之间做平衡。

22. Redis 内存淘汰策略

22.1 什么时候触发内存淘汰

当 Redis 设置了 maxmemory,并且实际使用内存达到上限时,如果继续写入新数据,就会触发内存淘汰策略。

常用查看命令:

redis 复制代码
INFO memory
CONFIG GET maxmemory
CONFIG GET maxmemory-policy

常用设置命令:

redis 复制代码
CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru

22.2 Redis 8 种淘汰策略

策略 含义
noeviction 不淘汰任何 key,内存满后写命令报错
allkeys-lru 在所有 key 中淘汰最近最少使用的 key
volatile-lru 在设置了过期时间的 key 中淘汰最近最少使用的 key
allkeys-random 在所有 key 中随机淘汰
volatile-random 在设置了过期时间的 key 中随机淘汰
volatile-ttl 在设置了过期时间的 key 中优先淘汰快过期的 key
allkeys-lfu 在所有 key 中淘汰最不常用的 key
volatile-lfu 在设置了过期时间的 key 中淘汰最不常用的 key

22.3 淘汰策略可以从两个维度理解

第一个维度:淘汰范围。

text 复制代码
allkeys:所有 key 都参与淘汰。
volatile:只有设置了过期时间的 key 参与淘汰。

第二个维度:淘汰算法。

text 复制代码
lru:最近最少使用。
lfu:最不经常使用。
random:随机。
ttl:快过期优先。
noeviction:不淘汰。

22.4 LRU 和 LFU 的区别

LRU

LRU 全称:

text 复制代码
Least Recently Used

含义:

text 复制代码
最近最少使用。
看的是某个 key 距离上一次被访问过去了多久。
越久没有访问,越容易被淘汰。

LFU

LFU 全称:

text 复制代码
Least Frequently Used

含义:

text 复制代码
最不经常使用。
看的是某个 key 在一段时间内被访问的频率。
访问次数越少,越容易被淘汰。

举例理解

text 复制代码
A key 过去访问了 100 次,但最近很久没访问。
B key 最近访问了 1 次,但总共只访问过 1 次。

LRU 可能淘汰 A,因为 A 最近没被访问。
LFU 可能淘汰 B,因为 B 访问频率低。

22.5 淘汰策略怎么选

常见选择:

text 复制代码
纯缓存场景:allkeys-lru 或 allkeys-lfu。
冷热数据差异明显:allkeys-lfu。
只希望淘汰带过期时间的缓存数据:volatile-lru 或 volatile-lfu。
不允许 Redis 自动丢数据:noeviction,但应用层要处理写入失败。

生产建议:

  1. Redis 作为纯缓存时,常用 allkeys-lruallkeys-lfu
  2. 如果访问频率差异明显,LFU 更合理。
  3. 如果 Redis 中混有不能被淘汰的关键数据,应该谨慎使用 allkeys。
  4. 配置 maxmemory 时要预留系统内存,避免机器 OOM。
  5. 避免 BigKey,否则淘汰时可能造成卡顿和网络抖动。

22.6 面试回答模板

text 复制代码
Redis 内存达到 maxmemory 后,会根据 maxmemory-policy 选择淘汰策略。
淘汰策略可以分成两个维度:一个是淘汰范围,比如 allkeys 表示所有 key 都参与淘汰,volatile 表示只有设置了过期时间的 key 参与淘汰;另一个是淘汰算法,比如 LRU、LFU、random、TTL。
如果 Redis 只是做缓存,一般选择 allkeys-lru 或 allkeys-lfu。
如果业务冷热访问差异明显,LFU 更适合。
如果不允许 Redis 自动删除数据,可以使用 noeviction,但写入可能报错,需要业务处理。

23. RDB 持久化

23.1 RDB 是什么

RDB 是 Redis 的快照持久化机制。

它会在某个时间点,把 Redis 内存中的数据生成一个快照文件保存到磁盘。

默认文件名通常是:

text 复制代码
dump.rdb

一句话:

text 复制代码
RDB 保存的是某个时间点 Redis 内存数据的全量快照。

23.2 RDB 触发方式

RDB 常见触发方式:

  1. 配置文件中的 save seconds changes 规则自动触发。
  2. 手动执行 SAVE 命令。
  3. 手动执行 BGSAVE 命令。
  4. 执行 SHUTDOWN 且没有开启 AOF。
  5. 主从复制时,主节点会触发 RDB。
  6. 执行 FLUSHDBFLUSHALL 也可能生成 RDB,但里面是空数据,恢复后仍然为空。

23.3 save 配置规则

示例:

conf 复制代码
save 5 2

含义:

text 复制代码
如果 5 秒内至少发生 2 次写操作,就触发一次 RDB 快照。

注意:

text 复制代码
RDB 不是单纯按时间间隔触发,而是按"时间 + 修改次数"共同触发。

23.4 SAVE 和 BGSAVE 的区别

命令 特点 是否推荐线上使用
SAVE 主线程执行,阻塞 Redis,直到 RDB 完成 不推荐
BGSAVE fork 子进程执行,主线程继续处理请求 推荐

SAVE

text 复制代码
SAVE 会阻塞 Redis 主线程。
执行期间 Redis 不能处理其他客户端命令。
线上环境一般禁止使用。

BGSAVE

text 复制代码
BGSAVE 会 fork 一个子进程。
子进程负责把内存数据写入 RDB 文件。
主线程继续处理客户端请求。

23.5 fork 和写时复制

BGSAVE 会 fork 子进程。

fork 后:

text 复制代码
父进程继续处理请求。
子进程负责生成 RDB。
父子进程刚开始共享同一份物理内存。
如果父进程修改了某个内存页,操作系统会复制该页,这就是写时复制。

风险:

text 复制代码
如果 Redis 数据集很大,fork 可能耗时较长。
写时复制期间,如果写入很多,内存可能短时间膨胀。

23.6 RDB 优点

  1. 文件紧凑,占用空间小。
  2. 适合全量备份。
  3. 适合灾难恢复。
  4. 恢复速度通常比 AOF 快。
  5. BGSAVE 下持久化工作由子进程完成,对主线程影响较小。
  6. 可以定期把 RDB 文件传到远程服务器或对象存储做备份。

23.7 RDB 缺点

  1. 两次快照之间的数据可能丢失。
  2. 数据集大时 fork 成本较高。
  3. fork 期间可能造成短暂阻塞。
  4. 写时复制可能导致内存瞬间膨胀。
  5. 对数据完整性要求极高的场景不适合只用 RDB。

23.8 RDB 适合什么场景

适合:

text 复制代码
定期备份。
灾难恢复。
允许分钟级数据丢失。
数据量大但希望快速恢复。

不适合:

text 复制代码
强数据安全场景。
不能接受丢失最近几秒或几分钟写入的场景。

23.9 面试回答模板

text 复制代码
RDB 是 Redis 的快照持久化机制,它会在某个时间点把内存数据生成 dump.rdb 文件。
RDB 可以通过 save 配置自动触发,也可以通过 SAVE 或 BGSAVE 手动触发。
SAVE 会阻塞主线程,线上一般不用;BGSAVE 会 fork 子进程生成快照,主线程继续处理请求。
RDB 的优点是文件小、恢复快、适合备份和灾难恢复。
缺点是两次快照之间的数据可能丢失,并且数据集很大时 fork 和写时复制会带来性能和内存压力。

24. AOF 持久化

24.1 AOF 是什么

AOF 全称是 Append Only File。

它以追加日志的形式记录 Redis 执行过的写命令。

Redis 重启时,会重新执行 AOF 文件中的写命令来恢复数据。

一句话:

text 复制代码
RDB 保存的是数据快照。
AOF 保存的是写命令日志。

开启 AOF:

conf 复制代码
appendonly yes

24.2 AOF 工作流程

AOF 工作流程:

text 复制代码
客户端发送写命令
  ↓
Redis 执行写命令
  ↓
写命令追加到 AOF 缓冲区
  ↓
根据刷盘策略写入磁盘 AOF 文件
  ↓
AOF 文件过大时触发 AOF 重写
  ↓
Redis 重启时加载 AOF 恢复数据

24.3 AOF 三种写回策略

策略 含义 优点 缺点
always 每条写命令都同步刷盘 数据最安全 性能最差
everysec 每秒刷盘一次 性能和安全折中,生产常用 最多丢 1 秒数据
no 由操作系统决定何时刷盘 性能最好 宕机丢数据风险较大

24.4 appendfsync 配置

conf 复制代码
appendfsync always
appendfsync everysec
appendfsync no

生产常用:

conf 复制代码
appendfsync everysec

原因:

text 复制代码
每秒刷盘一次,性能较好。
即使宕机,通常最多丢失 1 秒数据。

24.5 Redis 7 Multi Part AOF

Redis 7 中 AOF 文件从单一文件演进为 Multi Part AOF。

主要包括:

text 复制代码
BASE AOF:基础 AOF,一般由 AOF 重写生成,最多一个。
INCR AOF:增量 AOF,记录重写期间和之后的新写入,可能有多个。
HISTORY AOF:历史 AOF,重写完成后旧文件会变成历史文件,之后自动删除。
manifest:清单文件,记录当前应该加载哪些 AOF 文件。

好处:

text 复制代码
更容易管理 AOF 重写过程。
减少单文件过大的管理复杂度。
提高 AOF 文件切换和恢复的安全性。

24.6 AOF 优点

  1. 数据安全性比 RDB 更好。
  2. everysec 策略通常最多丢 1 秒数据。
  3. AOF 是追加日志,不容易破坏已有内容。
  4. AOF 文件可读性较好,便于人工分析和紧急修复。
  5. 可以通过 redis-check-aof 修复异常 AOF 文件。
  6. 支持 AOF 重写,避免文件无限膨胀。

24.7 AOF 缺点

  1. AOF 文件通常比 RDB 文件大。
  2. 恢复速度通常慢于 RDB,因为要重放写命令。
  3. 每次写命令都需要追加日志,对性能有一定影响。
  4. AOF 重写也需要 fork 子进程,仍然有性能开销。
  5. 如果 fsync 策略是 always,写性能会明显下降。

24.8 AOF 异常修复

如果 AOF 文件损坏,Redis 可能启动失败。

修复命令:

bash 复制代码
redis-check-aof --fix appendonly.aof

修复思想:

text 复制代码
截断或修复 AOF 中不完整的写命令。

24.9 面试回答模板

text 复制代码
AOF 是 Redis 的追加日志持久化机制,它会记录 Redis 执行过的写命令,Redis 重启时通过重放这些命令恢复数据。
AOF 有 always、everysec、no 三种刷盘策略。
always 每条命令都刷盘,最安全但最慢;everysec 每秒刷盘一次,是性能和可靠性的折中,生产最常用;no 由操作系统决定刷盘,性能最好但可靠性较弱。
AOF 的优点是数据更完整,缺点是文件更大、恢复速度比 RDB 慢。

25. AOF 重写机制

25.1 为什么需要 AOF 重写

AOF 会不断追加写命令,文件会越来越大。

例如:

redis 复制代码
SET k1 v1
SET k1 v2
SET k1 v3

实际上恢复最终数据只需要:

redis 复制代码
SET k1 v3

所以 Redis 需要 AOF 重写来压缩历史命令。

一句话:

text 复制代码
AOF 重写就是根据当前内存数据生成一份最小命令集,替代臃肿的旧 AOF 文件。

25.2 AOF 重写触发方式

手动触发

redis 复制代码
BGREWRITEAOF

自动触发

常见配置:

conf 复制代码
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

含义:

text 复制代码
当前 AOF 文件大小超过上次重写后大小的 100%,并且文件大于 64MB 时,触发自动重写。

25.3 AOF 重写原理

流程:

text 复制代码
1. Redis fork 一个 AOF 重写子进程。
2. 子进程根据当前内存数据生成新的 AOF 文件。
3. 主进程继续处理客户端写命令。
4. 主进程把新写命令同时写入旧 AOF 文件和 AOF 重写缓冲区。
5. 子进程完成新 AOF 后通知主进程。
6. 主进程把重写缓冲区中的新命令追加到新 AOF 文件。
7. 用新 AOF 文件替换旧 AOF 文件。

重点:

text 复制代码
AOF 重写不是读取旧 AOF 文件再压缩。
而是直接根据当前 Redis 内存中的数据生成新的 AOF。

25.4 AOF 重写期间为什么还写旧 AOF

因为重写过程中可能宕机。

如果只写新 AOF,而新 AOF 还没完成就宕机,数据恢复会有风险。

所以重写期间:

text 复制代码
旧 AOF 继续追加,保证旧文件始终可用。
新写命令也进入重写缓冲区,最后追加到新 AOF。

25.5 面试回答模板

text 复制代码
AOF 重写是为了解决 AOF 文件不断膨胀的问题。
它不是对旧 AOF 文件逐行压缩,而是根据 Redis 当前内存数据重新生成一份可以恢复当前状态的最小命令集。
重写时 Redis 会 fork 子进程生成新 AOF,主进程继续处理请求,并把新写命令写入旧 AOF 和重写缓冲区。
等子进程完成后,主进程把缓冲区中的命令追加到新 AOF,然后用新文件替换旧文件。
这样既能压缩 AOF 文件,又能保证重写期间 Redis 仍然可用。

26. RDB 和 AOF 对比

26.1 核心对比表

对比项 RDB AOF
持久化内容 某一时刻的数据快照 每条写命令日志
文件大小 较小 较大
恢复速度 较快 较慢
数据安全性 两次快照之间可能丢失 everysec 通常最多丢 1 秒
性能影响 fork 子进程生成快照 持续追加日志,可能重写
适合场景 备份、灾难恢复 对数据完整性要求更高
可读性 二进制文件,不易读 命令日志,可读性较好

26.2 RDB 和 AOF 可以同时开启吗

可以。

如果 RDB 和 AOF 同时开启:

text 复制代码
Redis 重启时优先加载 AOF。

原因:

text 复制代码
通常 AOF 保存的数据比 RDB 更完整。

26.3 生产中怎么选

常见选择:

text 复制代码
只做缓存,允许数据丢失:可以关闭持久化。
需要快速恢复和备份:开启 RDB。
需要较高数据完整性:开启 AOF everysec。
既要恢复快,又要数据完整:开启 RDB + AOF 混合持久化。

26.4 面试回答模板

text 复制代码
RDB 是快照持久化,文件小、恢复快,适合备份和灾难恢复,但可能丢失两次快照之间的数据。
AOF 是写命令日志,数据更完整,everysec 策略通常最多丢 1 秒,但文件更大,恢复速度比 RDB 慢。
如果两者同时开启,Redis 重启时会优先加载 AOF,因为 AOF 通常数据更完整。
生产中常用 AOF everysec,或者开启 RDB+AOF 混合持久化来兼顾恢复速度和数据完整性。

27. RDB-AOF 混合持久化

27.1 混合持久化是什么

混合持久化是 Redis 将 RDB 和 AOF 结合起来使用。

配置:

conf 复制代码
aof-use-rdb-preamble yes

开启后,AOF 文件中:

text 复制代码
前半部分是 RDB 格式的全量快照。
后半部分是 AOF 格式的增量写命令。

27.2 混合持久化优点

  1. RDB 部分恢复速度快。
  2. AOF 部分保证数据更完整。
  3. 文件体积比纯 AOF 更小。
  4. 兼顾恢复性能和数据安全性。

27.3 混合持久化缺点

  1. 文件可读性变差。
  2. 格式更复杂。
  3. 低版本 Redis 可能无法识别。

27.4 面试回答模板

text 复制代码
Redis 混合持久化是 RDB 和 AOF 的结合。
开启 aof-use-rdb-preamble 后,AOF 文件前半部分是 RDB 快照,后半部分是 AOF 增量命令。
这样 Redis 重启时可以先快速加载 RDB 部分,再重放少量 AOF 增量命令。
它既比纯 AOF 恢复快,又比纯 RDB 数据更完整,是生产中比较推荐的持久化方式。

28. 纯缓存模式

28.1 什么是纯缓存模式

如果 Redis 只作为缓存使用,且缓存数据可以从 MySQL 或其他数据源重建,可以关闭 RDB 和 AOF。

关闭 RDB:

conf 复制代码
save ""

关闭 AOF:

conf 复制代码
appendonly no

28.2 什么时候可以关闭持久化

适合:

text 复制代码
Redis 只是纯缓存。
缓存丢失后可以从数据库重建。
系统有完善的缓存预热和限流降级能力。

不适合:

text 复制代码
Redis 中有不能丢的数据。
Redis 被用作消息队列、分布式状态存储、计数器且不能丢失。

28.3 面试回答模板

text 复制代码
如果 Redis 只是纯缓存,并且所有数据都可以从数据库或其他持久化系统重建,可以关闭 RDB 和 AOF,减少磁盘 IO。
但如果 Redis 中保存了不能丢失的数据,例如重要计数、消息、分布式状态,就不应该关闭持久化。
是否开启持久化要看 Redis 在系统中的定位:是纯缓存,还是承担了部分数据存储职责。

29. Redis 事务

29.1 Redis 事务是什么

Redis 事务是一组命令的集合。

相关命令:

redis 复制代码
MULTI
EXEC
DISCARD
WATCH
UNWATCH

基本流程:

redis 复制代码
MULTI
SET k1 v1
SET k2 v2
EXEC

含义:

text 复制代码
MULTI:开启事务,后续命令进入队列。
EXEC:执行事务队列中的所有命令。
DISCARD:放弃事务。
WATCH:监控 key,用于乐观锁。
UNWATCH:取消监控。

29.2 Redis 事务特点

Redis 事务特点:

  1. 命令会按顺序入队。
  2. EXEC 后按顺序执行。
  3. 执行期间不会被其他客户端命令插入。
  4. 没有传统数据库隔离级别。
  5. 不支持传统意义上的回滚。
  6. 可以配合 WATCH 实现乐观锁。

29.3 Redis 事务错误情况

入队阶段错误

例如命令语法错误:

redis 复制代码
MULTI
SET k1 v1
错误命令
EXEC

结果:

text 复制代码
整个事务不会执行。

执行阶段错误

例如类型错误:

redis 复制代码
SET k1 v1
MULTI
INCR k1
SET k2 v2
EXEC

结果:

text 复制代码
INCR k1 执行失败,但 SET k2 v2 仍然会执行。
Redis 不会回滚已经执行的命令。

29.4 WATCH 乐观锁

WATCH 可以监控一个或多个 key。

示例:

redis 复制代码
WATCH balance
MULTI
DECRBY balance 100
EXEC

如果 EXEC 之前,balance 被其他客户端修改:

text 复制代码
事务执行失败。

这类似 CAS:

text 复制代码
先观察版本。
提交前检查版本是否变化。
变化则提交失败。

29.5 Redis 事务和 MySQL 事务区别

对比项 Redis 事务 MySQL 事务
原子性 不保证全部成功或全部失败 支持 ACID 原子性
回滚 不支持运行时错误回滚 支持回滚
隔离级别 没有隔离级别概念 支持多个隔离级别
持久性 依赖 RDB/AOF 依赖 redo log 等
主要用途 简单批量顺序执行 强一致复杂事务

29.6 面试回答模板

text 复制代码
Redis 事务通过 MULTI、EXEC 实现。
MULTI 后命令不会立即执行,而是进入队列,EXEC 后按顺序执行。
Redis 事务执行期间不会被其他客户端命令插入,但它不支持传统数据库意义上的回滚,也没有隔离级别。
如果命令入队阶段出现语法错误,整个事务不会执行;如果执行阶段某条命令因为类型错误失败,其他命令仍然会继续执行。
如果需要乐观锁,可以使用 WATCH 监控 key,EXEC 前 key 被修改则事务失败。

30. Pipeline 管道

30.1 Pipeline 解决什么问题

Redis 是客户端-服务端模型。

普通请求模型:

text 复制代码
客户端发送命令
  ↓
服务端执行
  ↓
服务端返回结果
  ↓
客户端收到结果后再发下一条命令

如果有大量命令,就会产生大量网络 RTT。

Pipeline 模型:

text 复制代码
客户端一次性发送多条命令
  ↓
Redis 依次执行
  ↓
一次性返回多个结果

核心作用:

text 复制代码
减少网络往返次数 RTT。

30.2 Pipeline 示例

普通方式:

text 复制代码
SET k1 v1
等待返回
SET k2 v2
等待返回
SET k3 v3
等待返回

Pipeline:

text 复制代码
一次性发送:
SET k1 v1
SET k2 v2
SET k3 v3

然后一次性接收结果。

30.3 Pipeline 和事务区别

对比项 Pipeline Redis 事务
主要目的 减少网络 RTT,提高吞吐 保证命令按顺序执行且执行期间不插队
是否原子 Redis 事务也不支持传统回滚,但执行期间不插队
发送方式 一次性批量发送 MULTI 后入队,EXEC 后执行
是否能混合命令 可以 可以
失败处理 某条失败不影响其他命令 执行阶段某条失败也不影响其他命令

30.4 Pipeline 和 MGET/MSET 区别

对比项 MGET/MSET Pipeline
命令类型 Redis 原生命令 客户端批处理机制
支持命令 只能批量 get/set 类似命令 可以批量任意多个命令
原子性 单条 Redis 命令天然原子 多条命令整体不原子
主要作用 批量读写 减少 RTT

30.5 Pipeline 注意点

  1. Pipeline 一次不要发送过多命令,否则 Redis 输出缓冲区可能变大。
  2. Pipeline 不保证整体原子性。
  3. Pipeline 中某条命令失败,不影响其他命令执行。
  4. Pipeline 适合批量写入、批量查询、初始化数据等场景。
  5. 如果命令之间有依赖关系,不适合简单使用 Pipeline。

30.6 面试回答模板

text 复制代码
Pipeline 主要解决 Redis 客户端和服务端之间网络 RTT 过多的问题。
普通 Redis 命令是一问一答,如果有大量命令,会有大量网络往返。
Pipeline 可以把多条命令一次性发送给 Redis,Redis 依次执行后一次性返回结果,从而提高吞吐。
Pipeline 不是事务,不保证整体原子性;如果其中某条命令失败,其他命令仍然会执行。

31. 发布订阅 Pub/Sub

31.1 Pub/Sub 是什么

Pub/Sub 是发布订阅模式。

角色:

text 复制代码
Publisher:发布者。
Subscriber:订阅者。
Channel:频道。

常用命令:

redis 复制代码
SUBSCRIBE channel
PUBLISH channel message
PSUBSCRIBE pattern
UNSUBSCRIBE channel
PUBSUB CHANNELS
PUBSUB NUMSUB channel

31.2 Pub/Sub 使用流程

text 复制代码
客户端 A 订阅 channel1
  ↓
客户端 B 向 channel1 发布消息
  ↓
客户端 A 收到消息

31.3 Pub/Sub 缺点

  1. 消息不持久化。
  2. 订阅者不在线,消息直接丢失。
  3. 没有 ACK 机制。
  4. 不能保证消息消费成功。
  5. 不能支持消费组。
  6. 不适合做可靠消息队列。

31.4 Pub/Sub 和 Stream 对比

对比项 Pub/Sub Stream
消息持久化 不支持 支持
ACK 确认 不支持 支持
消费组 不支持 支持
离线消息 会丢失 可保留
适合场景 简单广播通知 轻量级消息队列

31.5 面试回答模板

text 复制代码
Redis Pub/Sub 可以实现发布订阅,但生产中不建议用它做核心消息队列。
因为 Pub/Sub 消息不持久化,订阅者不在线消息就会丢失,也没有 ACK 机制,不能保证消费成功。
如果只是简单广播通知可以使用 Pub/Sub。
如果想用 Redis 做更可靠的消息队列,更推荐 Stream。
如果是核心业务消息,应该优先使用 Kafka、RocketMQ、RabbitMQ 等专业 MQ。

32. Lua 脚本

32.1 Redis 为什么需要 Lua

Redis 单条命令是原子的,但多条命令组合不一定原子。

例如分布式锁解锁:

text 复制代码
先 GET 判断 value 是不是自己的。
再 DEL 删除 key。

如果分成两条命令,中间可能发生锁过期、其他线程加锁,导致误删。

Lua 可以把多条 Redis 命令组合成一个脚本,在 Redis 中原子执行。


32.2 Lua 基本命令

redis 复制代码
EVAL script numkeys key [key ...] arg [arg ...]

示例:

redis 复制代码
EVAL "return redis.call('get', KEYS[1])" 1 k1

含义:

text 复制代码
KEYS[1] 表示第一个 key。
ARGV[1] 表示第一个参数。

32.3 Lua 解锁脚本

lua 复制代码
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

作用:

text 复制代码
如果锁的 value 等于当前客户端 uuid,才删除锁。
否则不删除。

32.4 Lua 的优点

  1. 多条命令原子执行。
  2. 减少客户端和 Redis 的网络交互。
  3. 可以封装复杂逻辑。
  4. 适合分布式锁、限流、库存扣减等场景。

32.5 Lua 注意点

  1. Lua 脚本不要写太长。
  2. 不要在 Lua 中执行耗时逻辑。
  3. Lua 执行期间会阻塞 Redis 主线程。
  4. Lua 脚本要保证可预测,不要写死循环。
  5. Redis Cluster 中 Lua 涉及的 key 最好在同一个 slot。

32.6 面试回答模板

text 复制代码
Redis 单条命令是原子的,但多条命令组合不一定原子。
Lua 脚本可以把多条 Redis 命令封装成一个脚本,在 Redis 中一次性执行,从而保证原子性。
典型场景是分布式锁解锁,要先判断 value 是不是自己的 uuid,再删除 key。
如果不用 Lua,GET 和 DEL 之间可能发生并发问题;用 Lua 可以避免误删别人的锁。
但 Lua 脚本执行期间会阻塞 Redis 主线程,所以脚本不能太复杂。

33. 缓存三大问题总览

Redis 面试中缓存三大问题非常高频:

text 复制代码
缓存穿透
缓存击穿
缓存雪崩

三者区别:

问题 核心原因 请求对象 典型现象 解决重点
缓存穿透 查询不存在的数据 非法 key 或不存在 key Redis 没有,MySQL 也没有,请求一直打 DB 空对象缓存、布隆过滤器
缓存击穿 热点 key 突然失效 单个热点 key 大量请求同时打 DB 互斥锁、逻辑过期、热点 key 不过期
缓存雪崩 大量 key 同时失效或 Redis 挂掉 大量 key 大面积请求打 DB 随机 TTL、高可用、限流降级

34. 缓存穿透

34.1 缓存穿透是什么

缓存穿透指的是:

text 复制代码
查询一个 Redis 中不存在、数据库中也不存在的数据。

请求流程:

text 复制代码
请求查询 key
  ↓
Redis 没有
  ↓
MySQL 也没有
  ↓
不写缓存
  ↓
下一次请求继续打 MySQL

如果攻击者大量请求不存在的 key,会导致数据库压力暴增。


34.2 缓存穿透的典型场景

  1. 用户传入非法 ID,例如 id=-1
  2. 查询数据库中不存在的商品。
  3. 恶意攻击构造大量随机 key。
  4. 爬虫不断访问不存在资源。

34.3 解决方案一:缓存空对象

做法:

text 复制代码
如果 Redis 没有,MySQL 也没有,就把空结果写入 Redis。
比如写入 "null"、"defaultNull"、空对象。
同时设置较短过期时间。

示例:

text 复制代码
customer:999999 -> defaultNull
TTL 60 秒

优点:

text 复制代码
实现简单。
对于相同不存在 key 的重复请求,可以直接命中 Redis。

缺点:

text 复制代码
如果攻击者每次构造不同 key,Redis 会被写入大量空对象。
会造成缓存污染和内存浪费。

34.4 解决方案二:布隆过滤器

做法:

text 复制代码
把所有合法 key 提前放入布隆过滤器。
请求先查布隆过滤器。
如果布隆过滤器判断不存在,直接返回。
如果布隆过滤器判断可能存在,再查 Redis 和 MySQL。

流程:

text 复制代码
请求 key
  ↓
布隆过滤器判断
  ↓
一定不存在:直接返回
  ↓
可能存在:查询 Redis
  ↓
Redis 未命中:查询 MySQL

布隆过滤器特点:

text 复制代码
有,可能有。
无,一定无。

34.5 解决方案三:参数校验和限流

在进入缓存层之前先做校验:

text 复制代码
id 必须大于 0。
用户 token 必须合法。
分页参数不能过大。
接口访问频率不能异常。

配合限流:

text 复制代码
Nginx 限流。
网关限流。
Sentinel 限流。
Redis 计数器限流。

34.6 面试回答模板

text 复制代码
缓存穿透是查询一个 Redis 和数据库都不存在的数据,导致每次请求都打到数据库。
解决方式可以缓存空对象,也就是数据库查不到时,把一个 defaultNull 写入 Redis,并设置较短 TTL。
但空对象缓存挡不住大量随机 key 攻击,所以更好的方式是使用布隆过滤器,把合法 key 提前放进去,请求先经过布隆过滤器判断,不存在就直接拦截。
此外还要配合参数校验、接口限流和黑名单,避免恶意请求打穿数据库。

35. 布隆过滤器

35.1 布隆过滤器是什么

布隆过滤器由两部分组成:

text 复制代码
一个很长的 bit 数组。
多个 hash 函数。

添加元素时:

text 复制代码
用多个 hash 函数计算多个位置。
把这些位置的 bit 设置为 1。

查询元素时:

text 复制代码
用同样的 hash 函数计算多个位置。
如果任意一个位置是 0,说明一定不存在。
如果所有位置都是 1,说明可能存在。

35.2 为什么布隆过滤器会误判

原因:

text 复制代码
hash 冲突。

多个不同元素可能把同一些 bit 位置设置为 1。

所以查询某个不存在的元素时,它对应的位置可能刚好都被其他元素设置成了 1。

这时布隆过滤器会误判为"可能存在"。


35.3 为什么布隆过滤器不能删除

因为一个 bit 位可能被多个元素共享。

如果删除某个元素时直接把对应 bit 改成 0,可能影响其他元素的判断。

所以普通布隆过滤器:

text 复制代码
可以添加。
可以查询。
不适合删除。

如果需要删除,可以了解:

text 复制代码
Counting Bloom Filter
Cuckoo Filter

35.4 布隆过滤器优缺点

优点:

  1. 插入和查询效率高。
  2. 内存占用小。
  3. 适合海量数据存在性判断。
  4. 能有效防止缓存穿透。

缺点:

  1. 有误判。
  2. 不能返回具体数据。
  3. 普通布隆过滤器不支持删除。
  4. 初始化容量要合理,否则误判率会上升。
  5. 实际元素数量远超预估容量时,需要重建过滤器。

35.5 面试回答模板

text 复制代码
布隆过滤器本质是 bit 数组加多个 hash 函数。
添加元素时,用多个 hash 函数计算多个位置并置 1;查询时,如果任意一个位置是 0,说明一定不存在;如果所有位置都是 1,只能说明可能存在。
所以布隆过滤器的特点是:无,一定无;有,可能有。
它适合解决缓存穿透,把合法 key 提前放入布隆过滤器,请求进来先判断,不存在就直接拦截。
缺点是存在误判,并且普通布隆过滤器不适合删除元素。

36. 缓存击穿

36.1 缓存击穿是什么

缓存击穿指的是:

text 复制代码
某个热点 key 在过期瞬间,大量并发请求同时访问这个 key。
因为 Redis 未命中,这些请求同时打到数据库。

核心:

text 复制代码
热点 key 失效。
大量请求同时打 MySQL。

36.2 缓存击穿和缓存穿透的区别

对比 缓存穿透 缓存击穿
查询数据 Redis 和 MySQL 都没有 MySQL 中有,Redis 临时没有
key 类型 不存在 key 热点 key
请求特点 可能是恶意随机请求 大量请求集中访问同一个热点 key
解决重点 空对象缓存、布隆过滤器 互斥锁、逻辑过期、热点 key 不过期

36.3 解决方案一:热点 key 不设置过期时间

做法:

text 复制代码
对于特别热点的数据,不设置物理过期时间。
通过后台任务定期刷新缓存。

优点:

text 复制代码
不会因为过期导致击穿。

缺点:

text 复制代码
数据可能不够实时。
需要额外机制刷新缓存。

36.4 解决方案二:互斥锁重建缓存

流程:

text 复制代码
大量请求访问热点 key
  ↓
Redis 未命中
  ↓
只有一个线程抢到互斥锁
  ↓
抢到锁的线程查询 MySQL 并回写 Redis
  ↓
其他线程等待、重试或快速失败

伪代码:

text 复制代码
value = getRedis(key)
if value != null:
    return value

if tryLock(lockKey):
    try:
        value = getMysql(key)
        setRedis(key, value)
        return value
    finally:
        unlock(lockKey)
else:
    sleep 一小段时间后重试 Redis

优点:

text 复制代码
保护数据库,只允许一个线程回源。

缺点:

text 复制代码
请求等待时间变长。
锁实现复杂。
高并发下要防止死锁和锁过期问题。

36.5 解决方案三:逻辑过期

做法:

text 复制代码
Redis 中存储 value + expireTime。
key 本身不设置物理过期时间。
查询时判断 expireTime 是否过期。

流程:

text 复制代码
如果逻辑未过期:
    直接返回缓存值

如果逻辑已过期:
    尝试获取互斥锁
    获取成功:开启后台线程重建缓存
    获取失败:直接返回旧值

优点:

text 复制代码
不会让大量请求阻塞。
不会让请求直接打到数据库。
适合高并发热点数据。

缺点:

text 复制代码
短时间内可能返回旧数据。
实现比普通缓存复杂。

36.6 解决方案四:双缓存

思路:

text 复制代码
设置 A/B 两个缓存。
A 缓存过期时间较短。
B 缓存过期时间比 A 略长。
A 失效时,用 B 兜底,避免请求打到数据库。

示例:

text 复制代码
jhs:a 过期时间 1 天
jhs:b 过期时间 1 天 + 10 秒

流程:

text 复制代码
先查 A。
A 没有再查 B。
B 也没有才考虑回源数据库。

36.7 面试回答模板

text 复制代码
缓存击穿是热点 key 突然失效,大量请求同时访问这个 key,导致请求集中打到数据库。
它和缓存穿透不同,穿透是数据本身不存在,击穿是数据存在但热点缓存失效了。
解决方式有几种:热点 key 不设置物理过期时间,通过后台任务刷新;或者使用互斥锁,只让一个线程回源数据库并重建缓存;更高并发的场景可以用逻辑过期,先返回旧值,再异步刷新缓存;也可以用双缓存方案做兜底。

37. 缓存雪崩

37.1 缓存雪崩是什么

缓存雪崩指的是:

text 复制代码
大量 key 在同一时间过期,或者 Redis 整体不可用,导致大量请求直接打到数据库。

两类原因:

text 复制代码
1. Redis 主机挂了,缓存整体不可用。
2. 大量 key 设置了相同过期时间,集中失效。

37.2 缓存雪崩危害

text 复制代码
Redis 大面积失效
  ↓
请求全部打到数据库
  ↓
数据库连接数暴涨
  ↓
数据库响应变慢甚至宕机
  ↓
服务整体不可用

37.3 解决方案一:过期时间加随机值

错误做法:

text 复制代码
大量 key 都设置 3600 秒过期。

改进:

text 复制代码
基础过期时间 + 随机时间

例如:

text 复制代码
3600 秒 + random(0, 600) 秒

作用:

text 复制代码
错开 key 的过期时间,避免集中失效。

37.4 解决方案二:热点数据预热

做法:

text 复制代码
系统启动前或活动开始前,把热点数据提前加载到 Redis。

适合:

  1. 秒杀商品。
  2. 首页数据。
  3. 热榜数据。
  4. 字典数据。
  5. 活动页数据。

37.5 解决方案三:Redis 高可用

通过以下方式提高 Redis 可用性:

text 复制代码
主从复制
哨兵 Sentinel
Redis Cluster
云 Redis 高可用版

作用:

text 复制代码
避免单点 Redis 挂掉导致缓存整体不可用。

37.6 解决方案四:多级缓存

例如:

text 复制代码
本地缓存 Caffeine / Guava / Ehcache
  ↓
Redis
  ↓
MySQL

好处:

text 复制代码
Redis 不可用时,本地缓存可以短暂兜底。

注意:

text 复制代码
本地缓存会带来一致性问题。
适合热点、允许短时间不一致的数据。

37.7 解决方案五:限流、降级、熔断

缓存雪崩时,需要保护数据库。

常见手段:

text 复制代码
Sentinel 限流。
Hystrix / Resilience4j 熔断降级。
网关限流。
Nginx 限流。
接口返回降级数据。

37.8 解决方案六:开启持久化

开启 RDB/AOF:

text 复制代码
Redis 故障恢复后可以快速恢复缓存数据。

但注意:

text 复制代码
持久化不是防止雪崩的根本方案。
它主要帮助 Redis 重启后恢复数据。

37.9 面试回答模板

text 复制代码
缓存雪崩是大量 key 同时失效,或者 Redis 整体不可用,导致大量请求直接打到数据库。
解决方案包括:给 key 的过期时间加随机值,避免集中失效;热点数据提前预热;Redis 使用主从、哨兵或 Cluster 提升高可用;服务侧使用本地缓存做兜底;同时配合限流、熔断、降级保护数据库;最后可以开启 RDB/AOF,帮助 Redis 故障后快速恢复。

38. 缓存预热

38.1 缓存预热是什么

缓存预热指的是:

text 复制代码
系统启动前、活动开始前,提前把热点数据加载到 Redis。

目的:

text 复制代码
避免系统刚启动时 Redis 是空的,大量请求直接打到数据库。

38.2 适合预热的数据

  1. 秒杀商品。
  2. 首页推荐。
  3. 热榜。
  4. 字典表。
  5. 系统配置。
  6. 活动页数据。
  7. 高访问量商品详情。

38.3 实现方式

  1. 项目启动时通过 @PostConstruct 加载。
  2. 定时任务定期刷新。
  3. 后台管理系统手动触发预热。
  4. 通过 MQ 异步加载。
  5. 发布活动前由运营平台触发。

38.4 面试回答模板

text 复制代码
缓存预热就是在系统启动或活动开始前,把热点数据提前加载到 Redis。
这样用户请求进来时可以直接命中缓存,避免冷启动阶段大量请求打到数据库。
比如秒杀商品、首页推荐、排行榜、字典表、活动页数据,都适合提前预热。

39. 缓存和数据库双写一致性

39.1 为什么会有一致性问题

因为同一份数据同时存在于:

text 复制代码
MySQL
Redis

只要出现双写,就会存在:

  1. 写数据库成功,写缓存失败。
  2. 写缓存成功,写数据库失败。
  3. 多线程并发写顺序错乱。
  4. 删除缓存失败。
  5. 主从延迟。
  6. 消息重试失败。

所以缓存和数据库很难做到强一致,工程上通常追求:

text 复制代码
最终一致性

39.2 四种更新策略

39.2.1 先更新数据库,再更新缓存

流程:

text 复制代码
更新 MySQL
  ↓
更新 Redis

问题一:缓存更新失败。

text 复制代码
MySQL 更新成功。
Redis 更新失败。
最终 MySQL 是新值,Redis 是旧值。

问题二:并发覆盖。

text 复制代码
线程 A 更新 MySQL 为 100。
线程 B 更新 MySQL 为 80。
线程 B 更新 Redis 为 80。
线程 A 更新 Redis 为 100。
最终 MySQL=80,Redis=100。

结论:

text 复制代码
不推荐。

39.2.2 先更新缓存,再更新数据库

流程:

text 复制代码
更新 Redis
  ↓
更新 MySQL

问题:

text 复制代码
Redis 更新成功。
MySQL 更新失败。
最终 Redis 是新值,MySQL 是旧值。

并发下也可能出现写顺序错乱。

结论:

text 复制代码
不推荐。

39.2.3 先删除缓存,再更新数据库

流程:

text 复制代码
删除 Redis
  ↓
更新 MySQL

并发问题:

text 复制代码
线程 A 删除 Redis。
线程 A 正在更新 MySQL,还没提交。
线程 B 查询 Redis,发现没有。
线程 B 查询 MySQL,读到旧值。
线程 B 把旧值写回 Redis。
线程 A 更新 MySQL 成功。
最终 Redis 仍然是旧值。

解决方式:

text 复制代码
延时双删。

但延时双删的 sleep 时间不好确定。

结论:

text 复制代码
可以用,但不是最推荐。

39.2.4 先更新数据库,再删除缓存

流程:

text 复制代码
更新 MySQL
  ↓
删除 Redis

这是工程中更常见的方案。

原因:

text 复制代码
MySQL 是权威数据源。
Redis 是缓存,删除失败可以补偿。

可能问题:

text 复制代码
删除缓存失败。

解决:

  1. 删除失败重试。
  2. MQ 异步重试。
  3. canal 监听 binlog 删除缓存。
  4. 设置缓存 TTL 兜底。
  5. 定时任务校验修复。

结论:

text 复制代码
推荐用于大多数业务场景。

39.3 为什么不是更新缓存,而是删除缓存

原因一:避免并发覆盖。

text 复制代码
直接更新缓存,在并发写下可能出现旧请求覆盖新缓存。

原因二:缓存可能不是数据库原始值。

text 复制代码
缓存可能是多个表聚合结果。
更新成本高。

原因三:删除更简单。

text 复制代码
删除缓存后,下一次读请求会从数据库加载最新值再回填。

39.4 延时双删

流程:

text 复制代码
删除缓存
  ↓
更新数据库
  ↓
sleep 一段时间
  ↓
再次删除缓存

作用:

text 复制代码
第二次删除用于清理并发读请求写回的旧缓存。

sleep 时间如何确定:

text 复制代码
要大于一次读数据库 + 写缓存的耗时。
一般需要结合业务压测和监控评估。

缺点:

  1. sleep 时间不好估计。
  2. 同步 sleep 会影响写接口吞吐。
  3. 不是强一致,只是降低不一致概率。

39.5 MQ 重试方案

流程:

text 复制代码
更新数据库成功
  ↓
删除缓存
  ↓
如果删除失败,把删除消息写入 MQ
  ↓
消费者异步重试删除缓存
  ↓
多次失败后告警

优点:

text 复制代码
提高删除缓存成功率。
解耦主业务流程。

缺点:

text 复制代码
引入 MQ,系统复杂度上升。
需要处理消息重复消费和失败告警。

39.6 canal 监听 binlog 方案

canal 可以模拟 MySQL slave,订阅 MySQL binlog。

流程:

text 复制代码
业务更新 MySQL
  ↓
MySQL 写 binlog
  ↓
canal 监听 binlog
  ↓
canal 客户端接收变更事件
  ↓
删除或更新 Redis

适合:

  1. 缓存最终一致性。
  2. 搜索索引同步。
  3. 数据异构同步。
  4. 多系统数据同步。

优点:

text 复制代码
业务代码侵入较少。
可以根据数据库变更统一更新缓存。

缺点:

text 复制代码
链路更长。
依赖 binlog。
canal 服务本身也要保证高可用。

39.7 强一致能做到吗

理论上可以通过加锁、串行化、分布式事务等方式尽量接近强一致。

但生产中一般不推荐对缓存系统追求强一致。

原因:

text 复制代码
Redis 是缓存层。
缓存的目标是提升性能。
如果为了强一致引入大量锁和同步等待,可能损失缓存意义。

工程上通常选择:

text 复制代码
最终一致性

手段:

  1. 先更新数据库,再删除缓存。
  2. 删除失败重试。
  3. MQ 补偿。
  4. canal 监听 binlog。
  5. TTL 兜底。
  6. 定时校验。

39.8 面试回答模板

text 复制代码
缓存和数据库双写很难保证强一致,工程上通常追求最终一致性。
常见方案是先更新数据库,再删除缓存。
因为 MySQL 是权威数据源,Redis 只是缓存,加速读请求。
不推荐直接更新缓存,因为并发写下容易出现旧请求覆盖新缓存,而且缓存值可能是复杂计算结果,更新成本高。
如果删除缓存失败,可以通过 MQ 重试、canal 监听 binlog、定时任务校验和缓存 TTL 兜底来保证最终一致性。

40. 四种缓存更新策略面试总结

策略 是否推荐 问题
先更新数据库,再更新缓存 不推荐 并发下旧值覆盖新值,缓存更新失败导致不一致
先更新缓存,再更新数据库 不推荐 数据库更新失败会导致 Redis 和 MySQL 不一致
先删除缓存,再更新数据库 一般不推荐 并发读可能把旧值写回缓存
先更新数据库,再删除缓存 推荐 删除失败需要重试补偿,最终一致

最终推荐:

text 复制代码
先更新数据库,再删除缓存。
删除失败用 MQ/canal/重试/TTL 兜底。

41. 本段面试高频题速查

41.1 Redis key 过期后立即删除吗

text 复制代码
不会。
Redis 主要采用惰性删除和定期删除。
访问 key 时发现过期会删除,这是惰性删除。
Redis 也会周期性随机抽样检查部分过期 key,这是定期删除。
如果内存达到 maxmemory,还会触发内存淘汰策略兜底。

41.2 Redis 内存淘汰策略有哪些

text 复制代码
noeviction、allkeys-lru、volatile-lru、allkeys-random、volatile-random、volatile-ttl、allkeys-lfu、volatile-lfu。
可以从两个维度理解:allkeys/volatile 表示淘汰范围,lru/lfu/random/ttl 表示淘汰算法。

41.3 LRU 和 LFU 区别

text 复制代码
LRU 看最近是否使用,淘汰最长时间没有访问的 key。
LFU 看访问频率,淘汰一段时间内访问次数最少的 key。
如果冷热数据非常明显,LFU 往往比 LRU 更适合。

41.4 RDB 和 AOF 区别

text 复制代码
RDB 是快照,保存某个时间点的全量数据,文件小、恢复快,但可能丢失两次快照之间的数据。
AOF 是追加写命令日志,数据更完整,但文件更大、恢复更慢。
生产中常用 AOF everysec 或 RDB+AOF 混合持久化。

41.5 SAVE 和 BGSAVE 区别

text 复制代码
SAVE 在主线程执行,会阻塞 Redis,线上不推荐。
BGSAVE 会 fork 子进程生成 RDB,主线程继续处理请求,生产中更常用。

41.6 AOF 三种刷盘策略

text 复制代码
always:每条写命令都刷盘,最安全但最慢。
everysec:每秒刷盘一次,性能和安全折中,生产最常用。
no:由操作系统决定刷盘,性能最好但可靠性较弱。

41.7 AOF 重写原理

text 复制代码
AOF 重写不是读取旧 AOF 文件压缩,而是根据当前 Redis 内存数据重新生成最小命令集。
重写时 Redis fork 子进程生成新 AOF,主进程继续处理请求,并把新写命令写入旧 AOF 和重写缓冲区。
子进程完成后,主进程把缓冲区内容追加到新 AOF,再替换旧 AOF。

41.8 Redis 事务支持回滚吗

text 复制代码
不支持传统数据库意义上的回滚。
Redis 事务通过 MULTI/EXEC 把命令入队后顺序执行。
如果入队阶段语法错误,整个事务不会执行。
如果执行阶段某条命令失败,其他命令仍然会继续执行。

41.9 Pipeline 是事务吗

text 复制代码
不是。
Pipeline 只是把多条命令批量发送,减少网络 RTT,提高吞吐。
它不保证整体原子性,也不保证失败回滚。

41.10 Pub/Sub 能做可靠消息队列吗

text 复制代码
不适合。
Pub/Sub 消息不持久化,消费者不在线消息会丢,也没有 ACK 机制。
如果想用 Redis 做更可靠的消息队列,应该用 Stream。
核心业务消息建议使用 Kafka、RocketMQ、RabbitMQ 等专业 MQ。

41.11 缓存穿透、击穿、雪崩区别

text 复制代码
缓存穿透:查不存在数据,Redis 和 MySQL 都没有。解决:空对象缓存、布隆过滤器、参数校验、限流。
缓存击穿:热点 key 突然失效,大量请求打 MySQL。解决:互斥锁、逻辑过期、热点 key 不过期。
缓存雪崩:大量 key 同时失效或 Redis 挂掉,大量请求打 MySQL。解决:随机 TTL、高可用、多级缓存、限流降级。

41.12 缓存和数据库怎么保证一致性

text 复制代码
很难保证强一致,通常保证最终一致。
主流方案是先更新数据库,再删除缓存。
删除失败时,通过 MQ 重试、canal 监听 binlog、定时补偿和缓存 TTL 兜底。
不推荐直接更新缓存,因为并发下容易出现旧值覆盖新值。

42. BigKey

42.1 什么是 BigKey

BigKey 不是指 key 字符串本身很大,而是指 key 对应的 value 很大。

常见判断标准:

text 复制代码
String 类型:value 超过 10KB 可以认为偏大。
Hash/List/Set/ZSet 类型:元素个数超过 5000 可以认为是 BigKey。

注意:

text 复制代码
BigKey 没有绝对统一标准。
实际生产中要结合业务 QPS、网络带宽、Redis 实例规格、延迟要求综合判断。

42.2 BigKey 的危害

BigKey 的危害主要有以下几类:

  1. 内存分布不均,导致 Redis Cluster 数据倾斜。
  2. 读取 BigKey 会占用大量网络带宽,导致网络阻塞。
  3. 删除 BigKey 可能阻塞 Redis 主线程。
  4. 集群迁移、扩容、缩容时成本高。
  5. 过期删除 BigKey 时可能造成 Redis 卡顿。
  6. 客户端读取超时,影响业务响应。
  7. 主从同步时,大对象传输会增加复制延迟。

42.3 BigKey 如何产生

常见产生原因:

  1. 社交场景中,明星粉丝列表不断增长。
  2. 热门文章点赞列表无限增长。
  3. 某个报表统计长期累加。
  4. 一个 Hash 存储过多字段。
  5. 一个 List 当成消息队列但不消费。
  6. 一个 Set/ZSet 长期不清理。
  7. 缓存对象设计不合理,把大量数据塞进一个 key。

42.4 BigKey 如何发现

42.4.1 redis-cli --bigkeys

命令:

bash 复制代码
redis-cli -h 127.0.0.1 -p 6379 -a password --bigkeys

作用:

text 复制代码
扫描 Redis,找出每种数据类型中最大的 key。

优点:

text 复制代码
使用简单。
可以快速发现每种类型的 top bigkey。

缺点:

text 复制代码
只能找出每种类型最大的 key。
不能列出所有超过指定阈值的 key。

降低扫描影响:

bash 复制代码
redis-cli -h 127.0.0.1 -p 6379 -a password --bigkeys -i 0.1

解释:

text 复制代码
-i 0.1 表示每扫描一定数量 key 后休眠 0.1 秒,降低对线上 Redis 的冲击。

42.4.2 MEMORY USAGE

命令:

redis 复制代码
MEMORY USAGE key

作用:

text 复制代码
查看某个 key 实际占用的内存字节数。

42.4.3 SCAN 渐进式扫描

不推荐:

redis 复制代码
KEYS *

推荐:

redis 复制代码
SCAN cursor MATCH pattern COUNT count

原因:

text 复制代码
SCAN 是渐进式扫描,不会像 KEYS 一样一次性阻塞 Redis 主线程。

42.4.4 业务侧监控

可以在业务写入 Redis 前做防护:

text 复制代码
String 写入前检查 value 大小。
集合类型写入后检查长度。
超过阈值记录日志或告警。

常用命令:

redis 复制代码
STRLEN key
HLEN key
LLEN key
SCARD key
ZCARD key
MEMORY USAGE key

42.5 BigKey 如何删除

核心思想:

text 复制代码
不要直接 DEL 大 key。
要渐进式删除,或者使用异步删除。

42.5.1 String 类型 BigKey

如果 String 很大,可以优先使用:

redis 复制代码
UNLINK big:string

说明:

text 复制代码
UNLINK 会把真正释放内存的工作交给后台线程,减少主线程阻塞。

42.5.2 Hash 类型 BigKey

思路:

text 复制代码
用 HSCAN 分批扫描 field。
用 HDEL 分批删除 field。
最后删除 key。

伪流程:

text 复制代码
cursor = 0
do:
    cursor, fields = HSCAN big_hash cursor COUNT 100
    HDEL big_hash fields...
while cursor != 0
UNLINK big_hash

42.5.3 List 类型 BigKey

思路:

text 复制代码
用 LTRIM 分段裁剪,逐步缩小 List。

示例:

redis 复制代码
LTRIM big_list 1000 -1
LTRIM big_list 1000 -1
LTRIM big_list 1000 -1

最后:

redis 复制代码
UNLINK big_list

42.5.4 Set 类型 BigKey

思路:

text 复制代码
用 SSCAN 分批扫描元素。
用 SREM 分批删除元素。

伪流程:

text 复制代码
cursor = 0
do:
    cursor, members = SSCAN big_set cursor COUNT 100
    SREM big_set members...
while cursor != 0
UNLINK big_set

42.5.5 ZSet 类型 BigKey

思路一:

text 复制代码
用 ZSCAN 分批扫描元素。
用 ZREM 分批删除元素。

思路二:

text 复制代码
用 ZREMRANGEBYRANK 按排名范围分批删除。

示例:

redis 复制代码
ZREMRANGEBYRANK big_zset 0 999
ZREMRANGEBYRANK big_zset 0 999
ZREMRANGEBYRANK big_zset 0 999

最后:

redis 复制代码
UNLINK big_zset

42.6 BigKey 生产治理建议

  1. 设计阶段避免把无限增长的数据放入一个 key。
  2. 对集合类型设置合理拆分规则。
  3. 对热点大对象做分片。
  4. 对大集合设置过期或定期清理。
  5. 使用 SCAN 类命令渐进处理。
  6. 删除大对象优先使用 UNLINK。
  7. 开启 lazy free 相关配置。
  8. 做 Redis key 级别监控和告警。
  9. 禁止业务使用 KEYS * 扫描线上库。

42.7 面试回答模板

text 复制代码
BigKey 不是 key 本身很大,而是 key 对应的 value 很大。
比如 String value 超过 10KB,或者 Hash/List/Set/ZSet 元素数量超过几千,都可能成为 BigKey。
BigKey 的危害包括内存倾斜、网络阻塞、删除阻塞、集群迁移困难、主从复制延迟等。
发现 BigKey 可以用 redis-cli --bigkeys、MEMORY USAGE、SCAN 配合类型长度命令。
删除 BigKey 不能直接 DEL,因为可能阻塞主线程。
String 可以用 UNLINK;Hash/Set/ZSet 要用 HSCAN/SSCAN/ZSCAN 分批删除;List 可以用 LTRIM 渐进裁剪。
核心思想是渐进式处理,避免单次操作阻塞 Redis。

43. MoreKey:海量 key 问题

43.1 什么是 MoreKey

MoreKey 指 Redis 中 key 数量非常多。

例如:

text 复制代码
几千万 key。
上亿 key。
大量短生命周期 key。
大量无规律 key。

MoreKey 本身不一定是错误,但会带来管理、扫描、过期删除、内存开销等问题。


43.2 MoreKey 的危害

  1. key 元数据占用大量内存。
  2. 过期删除压力变大。
  3. RDB/AOF 文件增大。
  4. 主从同步成本变高。
  5. 全量扫描风险高。
  6. 内存碎片可能增加。
  7. 监控和排查困难。

43.3 为什么不能用 KEYS *

错误命令:

redis 复制代码
KEYS *

问题:

text 复制代码
KEYS * 会一次性遍历整个 keyspace。
Redis 是单线程执行命令,这期间其他请求会被阻塞。
如果 key 数量很多,可能造成 Redis 卡顿甚至服务超时。

43.4 应该用 SCAN

SCAN 命令:

redis 复制代码
SCAN cursor MATCH pattern COUNT count

特点:

  1. 游标式遍历。
  2. 每次只扫描一部分 key。
  3. cursor 返回 0 表示遍历结束。
  4. 不会像 KEYS 一样长时间阻塞 Redis。
  5. 可能返回重复 key,需要客户端去重。
  6. COUNT 只是建议值,不保证每次返回固定数量。

43.5 SCAN 使用示例

redis 复制代码
SCAN 0 MATCH user:* COUNT 100

返回:

text 复制代码
新的 cursor
一批 key

继续扫描:

redis 复制代码
SCAN 新cursor MATCH user:* COUNT 100

直到:

text 复制代码
cursor = 0

43.6 面试回答模板

text 复制代码
生产环境不能使用 KEYS * 扫描 Redis,因为 KEYS 会一次性遍历所有 key,Redis 主线程会被阻塞。
如果 key 数量达到百万、千万级,会导致 Redis 请求超时。
应该使用 SCAN 命令,SCAN 是基于游标的渐进式遍历,每次只扫描一部分 key。
不过 SCAN 可能返回重复 key,也不保证每次返回数量固定,所以客户端要能处理重复,并根据 cursor 是否为 0 判断是否结束。

44. Redis 分布式锁

44.1 为什么需要分布式锁

单机锁:

text 复制代码
synchronized
ReentrantLock

只能锁住当前 JVM。

在分布式部署中:

text 复制代码
服务实例 A 在 JVM1。
服务实例 B 在 JVM2。
服务实例 C 在 JVM3。

它们的本地锁彼此不可见。

所以需要一个所有服务都能访问的公共组件来协调锁,例如:

text 复制代码
Redis
ZooKeeper
etcd
MySQL

44.2 分布式锁的典型场景

  1. 秒杀扣库存。
  2. 防止重复提交订单。
  3. 定时任务只允许一个节点执行。
  4. 缓存击穿时只允许一个线程重建缓存。
  5. 用户账户余额更新。
  6. 订单状态流转。
  7. 优惠券领取。

44.3 分布式锁需要满足哪些条件

一把合格的分布式锁至少要满足:

text 复制代码
互斥性:同一时刻只能有一个客户端持有锁。
防死锁:持锁客户端崩溃后,锁最终能释放。
不误删:只能释放自己的锁。
原子性:加锁、设置过期时间、解锁判断删除都要原子。
可重入:同一线程可以重复进入同一把锁。
自动续期:业务没执行完时,锁不能提前过期。
高可用:锁服务不能成为单点。

44.4 最简单的错误写法

错误写法:

redis 复制代码
SETNX lock value
EXPIRE lock 30

问题:

text 复制代码
SETNX 和 EXPIRE 是两条命令。
如果 SETNX 成功后服务宕机,EXPIRE 没有执行,锁就永远不会释放,产生死锁。

44.5 正确加锁基础版

正确写法:

redis 复制代码
SET lock uuid NX EX 30

含义:

text 复制代码
NX:key 不存在才设置。
EX 30:设置 30 秒过期时间。
uuid:唯一标识当前客户端,防止误删。

优点:

text 复制代码
加锁和设置过期时间在一条命令中完成,具备原子性。

44.6 为什么 value 要用 uuid

如果 value 固定,例如都是 "1",就无法判断锁是谁加的。

使用 uuid 的目的:

text 复制代码
解锁时判断当前锁是不是自己持有的。
如果不是自己的锁,就不能删除。

value 通常可以设计为:

text 复制代码
UUID + ThreadId

例如:

text 复制代码
8f8a9cxx-xxxx:thread-12

44.7 错误解锁方式

错误方式:

redis 复制代码
DEL lock

问题:

text 复制代码
线程 A 获取锁,锁过期时间 30 秒。
线程 A 业务执行了 40 秒。
第 30 秒时锁过期。
线程 B 获取了同一把锁。
第 40 秒线程 A 执行完,直接 DEL lock。
结果线程 A 把线程 B 的锁删除了。

这叫:

text 复制代码
误删别人的锁。

44.8 正确解锁方式:Lua 脚本

解锁必须保证两步原子:

text 复制代码
判断 value 是不是自己的。
如果是自己的,才删除。

Lua 脚本:

lua 复制代码
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

含义:

text 复制代码
KEYS[1]:锁 key。
ARGV[1]:当前客户端 uuid。

为什么要用 Lua:

text 复制代码
GET 和 DEL 分开执行不是原子的。
Lua 脚本在 Redis 中作为一个整体执行,可以保证判断和删除的原子性。

44.9 基础版 Redis 分布式锁问题

即使用了:

redis 复制代码
SET lock uuid NX EX 30

和 Lua 解锁,仍然有问题:

  1. 不支持可重入。
  2. 业务执行时间超过锁 TTL,锁会提前过期。
  3. 主从异步复制下可能丢锁。
  4. 自己实现重试、续期、释放比较复杂。
  5. 单 Redis 节点存在单点问题。

44.10 面试回答模板

text 复制代码
Redis 分布式锁不能简单用 SETNX。
正确的基础加锁方式是 SET key uuid NX EX ttl,这样加锁和设置过期时间在一条命令里完成,具备原子性。
解锁时不能直接 DEL,而要用 Lua 脚本先判断 value 是不是自己的 uuid,如果是才删除,避免误删别人的锁。
但基础版 Redis 锁还不支持可重入和自动续期,业务时间超过 TTL 时锁可能提前过期,所以生产中通常会使用 Redisson。

45. Redis 可重入分布式锁

45.1 什么是可重入锁

可重入锁也叫递归锁。

含义:

text 复制代码
同一个线程已经获得某把锁后,再次进入需要同一把锁的代码块时,可以再次获得锁,而不会被自己阻塞。

Java 中:

text 复制代码
synchronized 是可重入锁。
ReentrantLock 是可重入锁。

45.2 为什么 Redis String 锁不支持可重入

普通 Redis 锁:

text 复制代码
lock -> uuid

它只能表示:

text 复制代码
有没有锁。
锁是谁的。

但不能表示:

text 复制代码
同一线程重入了几次。

因此不适合做可重入锁。


45.3 用 Hash 实现可重入锁

可以用 Redis Hash:

text 复制代码
lockName -> {
    uuid:threadId -> count
}

例如:

redis 复制代码
HSET order:lock 8f8a:12 1

含义:

text 复制代码
order:lock 是锁名。
8f8a:12 是当前客户端 UUID + 线程 ID。
1 是重入次数。

45.4 加锁逻辑

加锁逻辑:

text 复制代码
如果锁 key 不存在:
    HINCRBY lock uuid:threadId 1
    EXPIRE lock ttl
    加锁成功

如果锁 key 存在,并且 field 是当前线程:
    HINCRBY lock uuid:threadId 1
    EXPIRE lock ttl
    重入成功

如果锁 key 存在,但不是当前线程:
    加锁失败

45.5 加锁 Lua 脚本

lua 复制代码
if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then
    redis.call('hincrby', KEYS[1], ARGV[1], 1)
    redis.call('expire', KEYS[1], ARGV[2])
    return 1
else
    return 0
end

参数说明:

text 复制代码
KEYS[1]:锁名称。
ARGV[1]:uuid:threadId。
ARGV[2]:过期时间。

45.6 解锁逻辑

解锁逻辑:

text 复制代码
如果 field 不存在:
    说明不是自己的锁,返回 nil。

如果 HINCRBY -1 后结果为 0:
    说明重入次数全部释放,删除整个锁 key。

如果 HINCRBY -1 后结果大于 0:
    说明还有重入层数,只减少次数,不删除锁。

45.7 解锁 Lua 脚本

lua 复制代码
if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then
    return nil
elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 then
    return redis.call('del', KEYS[1])
else
    return 0
end

45.8 面试回答模板

text 复制代码
普通 Redis String 锁只能表示有没有锁,不能记录重入次数,所以不支持可重入。
要实现可重入,可以使用 Redis Hash。
key 是锁名,field 是 uuid:threadId,value 是重入次数。
加锁时,如果锁不存在,或者锁已经被当前线程持有,就 HINCRBY 加 1 并刷新过期时间;否则加锁失败。
解锁时,HINCRBY 减 1,如果减到 0 就删除锁,否则只减少重入次数。
这些逻辑要用 Lua 脚本保证原子性。

46. Redis 分布式锁自动续期

46.1 为什么需要自动续期

问题场景:

text 复制代码
锁过期时间设置 30 秒。
业务执行时间需要 60 秒。
30 秒后锁过期,其他线程获得锁。
此时原线程还没执行完,两个线程同时进入临界区。

这会破坏互斥性。


46.2 自动续期思路

思路:

text 复制代码
加锁成功后,启动后台定时任务。
每隔 expireTime / 3 检查一次。
如果锁仍然属于当前线程,就重新设置过期时间。

例如:

text 复制代码
锁过期时间 30 秒。
每 10 秒检查一次。
如果锁还在,就重新 EXPIRE 30 秒。

46.3 自动续期 Lua 脚本

lua 复制代码
if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then
    return redis.call('expire', KEYS[1], ARGV[2])
else
    return 0
end

含义:

text 复制代码
如果锁仍然由当前线程持有,就刷新过期时间。
如果锁已经不是当前线程的,不续期。

46.4 自动续期注意点

  1. 续期线程要在解锁后停止。
  2. 续期必须判断锁是否属于当前线程。
  3. 续期频率不能太高,避免 Redis 压力过大。
  4. 业务卡死时,续期可能导致锁长期不释放。
  5. 如果手动指定锁租约时间,通常不再自动续期。

46.5 面试回答模板

text 复制代码
Redis 分布式锁需要自动续期,是因为业务执行时间可能超过锁的 TTL。
如果锁提前过期,其他线程就能获取锁,破坏互斥性。
自动续期的思路是:加锁成功后启动后台定时任务,每隔锁过期时间的三分之一检查一次,如果锁仍然属于当前线程,就重新设置过期时间。
Redisson 的 watchdog 就是这种机制。

47. Redisson

47.1 Redisson 是什么

Redisson 是一个基于 Redis 的 Java 客户端框架。

它封装了很多分布式对象和工具,例如:

text 复制代码
分布式锁 RLock
可重入锁
公平锁
读写锁
信号量
闭锁
延迟队列
分布式集合
分布式 Map

在分布式锁场景中,Redisson 比手写 Redis 锁更成熟。


47.2 Redisson 分布式锁特点

Redisson 的 RLock 支持:

  1. 可重入。
  2. 自动续期 watchdog。
  3. Lua 脚本保证原子性。
  4. 锁释放校验。
  5. 阻塞等待。
  6. 尝试加锁。
  7. 支持公平锁、读写锁等扩展。

47.3 Redisson 看门狗 watchdog

如果调用:

java 复制代码
lock.lock();

没有指定 leaseTime,Redisson 会启动 watchdog。

默认行为:

text 复制代码
默认锁过期时间 30 秒。
每隔 10 秒检查一次。
如果当前线程仍然持有锁,就把锁过期时间重新设置为 30 秒。

如果调用:

java 复制代码
lock.lock(10, TimeUnit.SECONDS);

指定了 leaseTime:

text 复制代码
通常不会启动 watchdog。
锁会在指定时间后自动释放。

47.4 Redisson 解锁注意点

高并发下,解锁前要判断:

java 复制代码
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
    lock.unlock();
}

原因:

text 复制代码
防止当前线程释放不属于自己的锁。

47.5 Redisson 面试回答模板

text 复制代码
Redisson 是 Redis 官方生态中常用的 Java 客户端之一,它封装了成熟的分布式锁实现。
Redisson 的 RLock 支持可重入、Lua 原子加解锁、自动续期 watchdog。
如果调用 lock.lock() 没有指定租约时间,Redisson 默认锁时间是 30 秒,并且 watchdog 每隔 10 秒续期一次。
如果业务还没执行完,它会自动延长锁过期时间,避免锁提前释放。
所以生产中一般不建议手写复杂 Redis 分布式锁,而是优先使用 Redisson。

48. RedLock

48.1 RedLock 是什么

RedLock 是 Redis 作者提出的一种基于多个 Redis master 节点的分布式锁算法。

目的:

text 复制代码
解决单个 Redis 节点宕机导致锁不可用或锁安全失效的问题。

48.2 RedLock 基本思想

假设有 5 个相互独立的 Redis master 节点。

客户端加锁流程:

text 复制代码
1. 获取当前时间。
2. 依次向 5 个 Redis master 尝试加锁。
3. 每个节点使用 SET key value NX PX ttl。
4. 如果超过半数节点加锁成功,例如 5 个中至少 3 个成功。
5. 并且总耗时小于锁有效期。
6. 则认为加锁成功。
7. 如果失败,就释放已经加锁成功的节点。

48.3 RedLock 的优点

  1. 避免单 Redis 节点故障导致锁不可用。
  2. 多数节点成功才认为加锁成功。
  3. 比单节点 Redis 锁容错性更好。

48.4 RedLock 的争议

RedLock 在业界有争议。

争议点:

text 复制代码
在网络分区、时钟漂移、GC 暂停等极端情况下,仍然可能存在安全问题。

实际建议:

text 复制代码
普通业务:Redisson 单 Redis/主从锁通常够用。
强一致场景:优先考虑 ZooKeeper、etcd 这类 CP 系统。

48.5 面试回答模板

text 复制代码
RedLock 是 Redis 作者提出的多 Redis 节点分布式锁算法。
它要求客户端向多个互相独立的 Redis master 加锁,只有超过半数节点加锁成功,并且耗时小于锁有效期,才认为加锁成功。
RedLock 相比单 Redis 节点锁容错性更好,但在网络分区、时钟漂移、长时间 GC 等极端情况下仍然有争议。
所以普通业务可以用 Redisson,强一致锁场景更适合 ZooKeeper 或 etcd。

49. Redis 主从复制

49.1 主从复制是什么

主从复制是 Redis 的数据复制机制。

典型结构:

text 复制代码
master 负责写。
replica/slave 负责读。
master 把写入数据异步复制给 replica。

作用:

  1. 数据备份。
  2. 读写分离。
  3. 故障恢复基础。
  4. 提高读并发。
  5. 哨兵和 Cluster 高可用的基础。

49.2 主从复制流程

49.2.1 第一次连接:全量复制

流程:

text 复制代码
1. replica 启动后连接 master。
2. replica 向 master 发送同步请求。
3. master 执行 BGSAVE,生成 RDB 文件。
4. master 把 RDB 文件发送给 replica。
5. replica 清空自身旧数据,加载 RDB。
6. master 把生成 RDB 期间缓存的写命令发送给 replica。
7. replica 执行这些写命令,追上 master。

49.2.2 稳定阶段:增量复制

全量复制完成后:

text 复制代码
master 后续收到新的写命令,会持续发送给 replica。
replica 执行这些写命令,保持数据同步。

49.2.3 断线重连:部分重同步

Redis 主从会维护:

text 复制代码
replication offset
repl backlog buffer
master runid

如果 replica 短暂断线后重连:

text 复制代码
master 检查 replica 的 offset。
如果 backlog 中还保存着断线期间的数据,就进行部分重同步。
如果 backlog 中没有了,就重新全量复制。

49.3 主从复制为什么会延迟

原因:

  1. master 写压力大。
  2. 网络传输延迟。
  3. replica 执行命令慢。
  4. 大 key 复制成本高。
  5. replica 数量多,master 复制压力变大。
  6. RDB 全量同步时开销大。

49.4 主从复制缺点

  1. 异步复制存在数据延迟。
  2. master 宕机后,普通主从不会自动切换。
  3. master 写成功但未复制给 replica 时宕机,可能丢数据。
  4. 从节点越多,主节点同步压力越大。
  5. 读从库可能读到旧数据。

49.5 面试回答模板

text 复制代码
Redis 主从复制是 master 写、replica 读,master 把数据异步同步给 replica。
第一次连接时通常进行全量复制:master 执行 BGSAVE 生成 RDB,发送给 replica,replica 加载后再执行复制期间的增量命令。
之后进入增量复制阶段,master 持续把新的写命令同步给 replica。
如果 replica 短暂断线,可以通过复制 offset 和 repl backlog 做部分重同步;如果 backlog 中没有对应数据,就需要重新全量复制。
主从复制可以提高读能力和容灾能力,但由于是异步复制,所以存在延迟和数据丢失风险。

50. Redis 哨兵 Sentinel

50.1 Sentinel 是什么

Sentinel 是 Redis 的高可用方案。

它主要解决:

text 复制代码
master 宕机后无人自动切换的问题。

Sentinel 的功能:

  1. 监控 master 和 replica。
  2. 判断 master 是否下线。
  3. 自动进行故障转移。
  4. 选举新 master。
  5. 通知客户端新的 master 地址。
  6. 让旧 master 恢复后降级为 replica。

50.2 Sentinel 架构

典型架构:

text 复制代码
3 个 Sentinel
1 个 master
2 个 replica

为什么 Sentinel 通常部署奇数个:

text 复制代码
方便投票。
避免票数对半。
提升容错能力。

50.3 主观下线 SDOWN

SDOWN 全称:

text 复制代码
Subjectively Down

含义:

text 复制代码
某一个 Sentinel 自己认为 master 不可用。

判断依据:

text 复制代码
Sentinel 向 master 发送 PING。
如果超过 down-after-milliseconds 没有收到有效回复,就认为 master 主观下线。

注意:

text 复制代码
主观下线只是单个 Sentinel 的判断,可能是网络抖动或 Sentinel 自己的问题。

50.4 客观下线 ODOWN

ODOWN 全称:

text 复制代码
Objectively Down

含义:

text 复制代码
多个 Sentinel 达成一致,认为 master 确实不可用。

判断依据:

text 复制代码
达到 quorum 指定的票数。

例如:

conf 复制代码
sentinel monitor mymaster 127.0.0.1 6379 2

其中:

text 复制代码
2 表示至少 2 个 Sentinel 认为 master 下线,才进入客观下线。

50.5 Sentinel 故障转移流程

完整流程:

text 复制代码
1. Sentinel 持续监控 master 和 replica。
2. 某个 Sentinel 判断 master 主观下线。
3. 多个 Sentinel 投票确认 master 客观下线。
4. Sentinel 之间选举出 Leader Sentinel。
5. Leader Sentinel 从 replica 中选择一个新的 master。
6. 对新 master 执行 SLAVEOF NO ONE。
7. 让其他 replica 复制新的 master。
8. 通知客户端 master 地址变化。
9. 旧 master 恢复后,被降级为新 master 的 replica。

50.6 Leader Sentinel 如何选出

Sentinel 之间会进行领导者选举。

基本思想类似:

text 复制代码
先到先得。
一个 Sentinel 请求成为 Leader。
其他 Sentinel 如果本轮还没投票,就投给它。
获得多数票的 Sentinel 成为 Leader。

50.7 新 master 如何选择

选择规则通常按以下优先级:

text 复制代码
1. replica-priority 优先级最高的从节点,数值越小优先级越高。
2. 复制 offset 最大的从节点,说明数据最接近旧 master。
3. runid 最小的从节点。

解释:

text 复制代码
优先选数据最新、状态健康、优先级高的 replica。

50.8 Sentinel 缺点

  1. 不能解决数据分片问题。
  2. 写能力仍然受单 master 限制。
  3. 异步复制仍然可能丢数据。
  4. 故障转移期间可能短暂不可用。
  5. 客户端需要支持 Sentinel 模式。
  6. Sentinel 本身也需要高可用部署。

50.9 面试回答模板

text 复制代码
Sentinel 是 Redis 的高可用方案,主要负责监控、故障判断、自动故障转移和通知客户端。
Sentinel 会先判断 master 主观下线,也就是单个 Sentinel 认为 master 不可用;然后多个 Sentinel 达到 quorum 后确认客观下线。
之后 Sentinel 之间选出 Leader,由 Leader 从从节点中选择一个数据最新、优先级最高的节点提升为新 master。
其他从节点会改为复制新 master,旧 master 恢复后会被降级为从节点。
Sentinel 能解决自动故障转移,但不能解决水平扩容问题,也不能保证数据零丢失。

51. Redis Cluster

51.1 Redis Cluster 是什么

Redis Cluster 是 Redis 官方集群方案。

它解决的问题:

text 复制代码
单机内存容量有限。
单机写能力有限。
单点故障。

特点:

  1. 多 master。
  2. 每个 master 可以有 replica。
  3. 数据按 slot 分片。
  4. 支持自动故障转移。
  5. 支持水平扩容和缩容。
  6. 客户端通过 slot 路由访问对应节点。

51.2 Cluster 的 16384 个槽

Redis Cluster 内置:

text 复制代码
16384 个 hash slot。

key 到 slot 的计算公式:

text 复制代码
HASH_SLOT = CRC16(key) % 16384

每个 master 负责一部分 slot。

例如 3 个 master:

text 复制代码
master1 负责 0 - 5460
master2 负责 5461 - 10922
master3 负责 10923 - 16383

51.3 为什么是 16384 个槽

CRC16 可以产生:

text 复制代码
2^16 = 65536

但 Redis Cluster 没有使用 65536 个槽,而是使用 16384 个槽。

原因主要有三个:

51.3.1 心跳包大小

Redis Cluster 节点之间需要频繁发送心跳包。

心跳包中会携带节点负责的 slot bitmap。

如果槽位是 65536:

text 复制代码
65536 / 8 = 8192 bytes = 8KB

如果槽位是 16384:

text 复制代码
16384 / 8 = 2048 bytes = 2KB

槽位太多,会让心跳包更大,浪费网络带宽。


51.3.2 Redis Cluster 不建议太多 master

Redis Cluster 通常不建议 master 节点超过 1000 个。

对于 1000 个以内的 master:

text 复制代码
16384 个槽已经足够分配。

51.3.3 槽位少时 bitmap 更容易压缩

如果节点数量比较少,槽位数量太大,bitmap 压缩率可能不高。

16384 是槽位数量、网络开销、集群规模之间的折中。


51.4 为什么 Redis Cluster 不用一致性哈希

一致性哈希的问题:

text 复制代码
节点少时容易数据倾斜。
需要引入虚拟节点。
扩容缩容时数据迁移管理复杂。

哈希槽的好处:

text 复制代码
key 先映射到固定数量的 slot。
slot 再分配给节点。
扩容缩容时以 slot 为单位迁移数据。

所以 Redis Cluster 使用哈希槽,而不是传统一致性哈希。


51.5 Cluster 读写重定向

如果客户端访问了错误节点,Redis 会返回重定向。

常见重定向:

text 复制代码
MOVED
ASK

MOVED

含义:

text 复制代码
key 对应的 slot 已经确定属于另一个节点。
客户端应该以后都访问新的节点。

ASK

含义:

text 复制代码
slot 正在迁移中。
客户端本次请求临时访问目标节点。

51.6 redis-cli 连接集群

普通连接:

bash 复制代码
redis-cli -a 123456 -p 6381

集群模式连接:

bash 复制代码
redis-cli -a 123456 -p 6381 -c

-c 的作用:

text 复制代码
开启集群模式,支持自动重定向。

51.7 多 key 操作问题

Redis Cluster 中,如果多个 key 不在同一个 slot,不能执行多 key 命令。

例如可能失败:

redis 复制代码
MGET k1 k2
MSET k1 v1 k2 v2

原因:

text 复制代码
k1 和 k2 可能属于不同 slot。
不同 slot 可能在不同 master 上。
Redis Cluster 不支持跨 slot 多 key 原子操作。

51.8 hash tag 解决多 key 同槽

可以使用 {} 指定 hash tag。

示例:

redis 复制代码
MSET user:{1001}:name Tom user:{1001}:age 18

Redis 计算 slot 时只计算 {} 中的内容:

text 复制代码
1001

因此这些 key 会落到同一个 slot。


51.9 Cluster 常用命令

查看节点:

redis 复制代码
CLUSTER NODES

查看集群信息:

redis 复制代码
CLUSTER INFO

查看 key 对应 slot:

redis 复制代码
CLUSTER KEYSLOT key

查看某个 slot 中 key 数量:

redis 复制代码
CLUSTER COUNTKEYSINSLOT slot

查看集群状态:

bash 复制代码
redis-cli -a password --cluster check ip:port

创建集群:

bash 复制代码
redis-cli -a password --cluster create --cluster-replicas 1 ip1:port1 ip2:port2 ip3:port3 ip4:port4 ip5:port5 ip6:port6

添加节点:

bash 复制代码
redis-cli -a password --cluster add-node newIp:newPort existIp:existPort

重新分配槽位:

bash 复制代码
redis-cli -a password --cluster reshard ip:port

删除节点:

bash 复制代码
redis-cli -a password --cluster del-node ip:port nodeId

手动故障转移:

redis 复制代码
CLUSTER FAILOVER

51.10 Cluster 是否保证强一致性

不保证。

原因:

text 复制代码
Redis Cluster 的主从复制仍然是异步的。

场景:

text 复制代码
客户端向 master 写入成功。
master 还没来得及把数据复制给 replica。
master 宕机。
replica 被提升为新 master。
刚才那条写入丢失。

因此:

text 复制代码
Redis Cluster 更偏向 AP。
不保证强一致。

51.11 面试回答模板

text 复制代码
Redis Cluster 是 Redis 官方集群方案,它通过 16384 个哈希槽实现数据分片。
每个 key 通过 CRC16(key) % 16384 计算槽位,每个 master 负责一部分 slot。
Cluster 支持多个 master,每个 master 可以有 replica,所以可以实现水平扩容和故障转移。
Redis Cluster 使用 16384 个槽,是为了在槽位数量、心跳包大小和集群规模之间做折中。
如果访问了错误节点,Redis 会返回 MOVED 或 ASK 重定向。
需要注意的是,Cluster 不支持跨 slot 的多 key 操作,除非使用 hash tag 让多个 key 落到同一个 slot。
Redis Cluster 复制仍然是异步的,所以不保证强一致,极端情况下可能丢失已写入数据。

52. Redis 分区方案对比

52.1 哈希取余

公式:

text 复制代码
hash(key) % N

优点:

text 复制代码
简单直接。

缺点:

text 复制代码
扩容或缩容时 N 改变,大量 key 的映射结果都会变化,导致大规模数据迁移。

适合:

text 复制代码
节点数量固定的小规模系统。

52.2 一致性哈希

思想:

text 复制代码
把 hash 空间组织成一个环。
节点和 key 都映射到环上。
key 顺时针找到第一个节点。

优点:

text 复制代码
节点增删时,只影响相邻区间的数据。

缺点:

text 复制代码
节点少时容易数据倾斜。
通常需要虚拟节点解决。

52.3 哈希槽

思想:

text 复制代码
key 先映射到固定数量的 slot。
slot 再分配给 Redis 节点。

优点:

  1. slot 数量固定。
  2. 数据迁移以 slot 为单位。
  3. 扩容缩容更容易。
  4. 节点和数据之间多了一层 slot,管理更清晰。
  5. Redis Cluster 官方采用。

52.4 面试回答模板

text 复制代码
常见分区方案有哈希取余、一致性哈希和哈希槽。
哈希取余最简单,但扩容缩容时节点数量变化,会导致大量 key 重新映射。
一致性哈希把节点和 key 映射到 hash 环上,节点增删只影响相邻区间,但节点少时容易数据倾斜,需要虚拟节点。
Redis Cluster 使用哈希槽,key 先映射到 16384 个 slot,再由 slot 分配给节点。扩容缩容时只需要迁移 slot,管理更方便。

53. Redis 生产调优

53.1 禁用危险命令

危险命令:

redis 复制代码
KEYS *
FLUSHDB
FLUSHALL
CONFIG
SHUTDOWN
DEBUG

风险:

  1. KEYS * 会阻塞 Redis。
  2. FLUSHDB/FLUSHALL 会清空数据。
  3. CONFIG 可能修改线上配置。
  4. DEBUG 可能制造阻塞或安全风险。
  5. SHUTDOWN 会关闭 Redis。

可以通过 rename-command 禁用:

conf 复制代码
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""

53.2 避免 BigKey

治理方式:

  1. 设计阶段拆分大对象。
  2. 集合类型控制元素数量。
  3. 使用 Hash 分桶。
  4. 删除时用 UNLINK 或渐进式删除。
  5. 使用监控定期发现 BigKey。
  6. 对热点 BigKey 做本地缓存或拆分。

53.3 避免 HotKey

HotKey 指某个 key 被极高频访问。

危害:

text 复制代码
单个 Redis 节点压力过大。
网络带宽集中。
可能导致请求排队和超时。

解决方案:

  1. 本地缓存。
  2. 多副本缓存。
  3. key 拆分。
  4. 读写分离。
  5. 热点探测和动态扩容。
  6. 限流降级。

53.4 合理设置 TTL

建议:

  1. 不要大量 key 设置相同 TTL。
  2. TTL 加随机值。
  3. 热点 key 可以逻辑过期。
  4. 业务 key 设置合理过期,避免无期限增长。
  5. 防止缓存雪崩。

示例:

text 复制代码
过期时间 = 基础时间 + 随机时间

53.5 开启 lazy free

相关命令:

redis 复制代码
UNLINK key
FLUSHDB ASYNC
FLUSHALL ASYNC

相关配置:

conf 复制代码
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes

作用:

text 复制代码
把大对象释放内存的工作交给后台线程,减少主线程阻塞。

53.6 合理使用 Pipeline

适合:

  1. 批量写入。
  2. 批量查询。
  3. 数据初始化。
  4. 大量无依赖命令。

注意:

text 复制代码
一次 Pipeline 不要塞太多命令。
否则可能导致客户端或 Redis 输出缓冲区过大。

53.7 慢查询排查

相关命令:

redis 复制代码
SLOWLOG GET
SLOWLOG LEN
SLOWLOG RESET

相关配置:

conf 复制代码
slowlog-log-slower-than 10000
slowlog-max-len 128

注意:

text 复制代码
slowlog-log-slower-than 单位是微秒。

53.8 内存监控

常用命令:

redis 复制代码
INFO memory
MEMORY USAGE key
CONFIG GET maxmemory
CONFIG GET maxmemory-policy

重点关注:

text 复制代码
used_memory
used_memory_rss
mem_fragmentation_ratio
maxmemory
evicted_keys
expired_keys

53.9 主从复制监控

常用命令:

redis 复制代码
INFO replication

重点关注:

text 复制代码
role
connected_slaves
master_link_status
master_repl_offset
slave_repl_offset
master_last_io_seconds_ago

53.10 持久化监控

常用命令:

redis 复制代码
INFO persistence

重点关注:

text 复制代码
rdb_last_bgsave_status
rdb_last_bgsave_time_sec
aof_enabled
aof_last_bgrewrite_status
aof_last_write_status

53.11 面试回答模板

text 复制代码
Redis 生产调优主要从几个方面入手。
第一,禁用 KEYS、FLUSHDB、FLUSHALL 等危险命令,避免误操作或阻塞。
第二,治理 BigKey 和 HotKey,BigKey 会导致网络阻塞和删除卡顿,HotKey 会导致单点压力过大。
第三,合理设置 TTL,并加入随机值,避免缓存雪崩。
第四,删除大 key 使用 UNLINK 或渐进式删除,并开启 lazy free。
第五,批量操作使用 Pipeline 减少 RTT,但一次不要发送过多命令。
第六,做好慢查询、内存、主从复制、持久化等监控。

54. Redis 常用排查命令

54.1 基础信息

redis 复制代码
INFO
INFO memory
INFO stats
INFO clients
INFO replication
INFO persistence
INFO commandstats

54.2 key 相关

redis 复制代码
TYPE key
TTL key
PTTL key
EXISTS key
OBJECT ENCODING key
MEMORY USAGE key

54.3 集合长度

redis 复制代码
STRLEN key
HLEN key
LLEN key
SCARD key
ZCARD key
XLEN stream

54.4 BigKey

bash 复制代码
redis-cli --bigkeys
redis-cli --bigkeys -i 0.1

54.5 慢查询

redis 复制代码
SLOWLOG GET
SLOWLOG LEN
SLOWLOG RESET

54.6 复制状态

redis 复制代码
INFO replication

54.7 集群状态

redis 复制代码
CLUSTER INFO
CLUSTER NODES
CLUSTER KEYSLOT key
CLUSTER COUNTKEYSINSLOT slot

55. Redis 面试高频题最终清单

55.1 基础概念类

1. Redis 是什么?

text 复制代码
Redis 是基于内存的高性能 Key-Value 数据库,支持多种数据结构,例如 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、GEO、Stream。
常用于缓存、分布式锁、计数器、排行榜、Session、消息队列、签到、UV 统计等场景。

2. Redis 和 MySQL 有什么区别?

text 复制代码
Redis 是内存型 NoSQL,访问速度快,适合缓存和高并发读写。
MySQL 是关系型数据库,适合事务、复杂查询和强一致持久化。
工程中通常 MySQL 做权威数据源,Redis 做缓存加速层。

3. Redis 为什么快?

text 复制代码
Redis 快主要因为基于内存操作,数据结构高效,命令执行主线程单线程避免锁竞争,使用 IO 多路复用处理大量连接,协议简单,并且支持 Pipeline 减少 RTT。
Redis 6/7 还引入多 IO 线程优化网络读写和协议解析。

4. Redis 是单线程吗?

text 复制代码
Redis 命令执行主流程是单线程,但整个 Redis 进程不是纯单线程。
持久化、AOF 重写、异步删除、主从复制等可能用子进程或后台线程。
Redis 6/7 引入多 IO 线程处理网络读写和协议解析,但命令执行仍然由主线程串行完成。

55.2 数据类型类

5. Redis 有哪些常见数据类型?

text 复制代码
传统五大类型:String、Hash、List、Set、ZSet。
扩展类型:Bitmap、HyperLogLog、GEO、Stream、Bitfield。

6. String 底层是什么?

text 复制代码
String 底层是 SDS,有 int、embstr、raw 三种编码。
整数用 int;短字符串用 embstr;长字符串用 raw。
SDS 相比 C 字符串支持 O(1) 获取长度、二进制安全、空间预分配、惰性释放。

7. Hash 底层是什么?

text 复制代码
Redis 6 中 Hash 底层是 ziplist 和 hashtable。
Redis 7 中是 listpack 和 hashtable。
元素少且 field/value 较短时用 listpack 节省内存,超过阈值后转成 hashtable 提高查询效率。

8. List 底层是什么?

text 复制代码
Redis List 底层主要是 quicklist。
quicklist 可以理解为多个 listpack 组成的双向链表,兼顾链表两端操作效率和 listpack 紧凑存储优势。

9. Set 底层是什么?

text 复制代码
Set 底层是 intset 和 hashtable。
如果元素都是整数且数量少,用 intset。
如果出现非整数或数量超过阈值,转成 hashtable。

10. ZSet 底层是什么?

text 复制代码
ZSet 小集合用 listpack,大集合用 skiplist + dict。
dict 用来根据 member O(1) 查 score。
skiplist 用来按照 score 排序和范围查询。

11. 跳表是什么?

text 复制代码
跳表是有序链表加多级索引。
它通过空间换时间,让查询复杂度从 O(N) 降到平均 O(logN)。
Redis ZSet 使用跳表,是因为它实现简单,范围查询方便,插入删除效率较高。

12. Bitmap 适合什么场景?

text 复制代码
Bitmap 适合二值状态统计,例如签到、用户是否活跃、广告是否点击。
一个状态只占 1 bit,非常节省内存。

13. HyperLogLog 适合什么场景?

text 复制代码
HyperLogLog 适合大规模基数统计,例如 UV。
它不保存具体元素,只估算不重复元素数量,每个 key 占用内存很小,但有约 0.81% 的误差。

14. Stream 和 Pub/Sub 区别?

text 复制代码
Pub/Sub 不持久化,没有 ACK,消费者不在线消息会丢。
Stream 支持消息持久化、消费组、ACK、Pending List,更适合做轻量级消息队列。
核心业务消息仍然建议使用 Kafka、RocketMQ 等专业 MQ。

55.3 过期、淘汰、持久化类

15. Redis key 过期后会立刻删除吗?

text 复制代码
不会。
Redis 使用惰性删除和定期删除。
访问 key 时发现过期会删除,这是惰性删除。
Redis 周期性随机抽样检查部分过期 key,这是定期删除。
内存不够时再通过淘汰策略兜底。

16. Redis 内存淘汰策略有哪些?

text 复制代码
noeviction、allkeys-lru、volatile-lru、allkeys-random、volatile-random、volatile-ttl、allkeys-lfu、volatile-lfu。
可以从淘汰范围和淘汰算法两个维度理解。

17. RDB 和 AOF 区别?

text 复制代码
RDB 是快照,保存某个时间点的全量数据,文件小、恢复快,但可能丢失两次快照之间的数据。
AOF 是追加写命令日志,数据更完整,但文件更大、恢复更慢。
生产中常用 AOF everysec 或 RDB+AOF 混合持久化。

18. AOF 重写原理?

text 复制代码
AOF 重写不是读取旧 AOF 压缩,而是根据当前内存数据重新生成最小命令集。
重写时 fork 子进程生成新 AOF,主进程继续处理请求,并把新写命令写入旧 AOF 和重写缓冲区。
子进程完成后,主进程把缓冲区内容追加到新 AOF,再替换旧文件。

55.4 缓存问题类

19. 缓存穿透是什么?怎么解决?

text 复制代码
缓存穿透是查询 Redis 和数据库都不存在的数据,导致每次请求都打到数据库。
解决方式包括空对象缓存、布隆过滤器、参数校验、限流和黑名单。

20. 布隆过滤器是什么?

text 复制代码
布隆过滤器由 bit 数组和多个 hash 函数组成。
判断不存在则一定不存在,判断存在则可能存在。
它有误判,普通布隆过滤器不支持删除。
常用于解决缓存穿透。

21. 缓存击穿是什么?怎么解决?

text 复制代码
缓存击穿是热点 key 突然失效,大量请求同时打到数据库。
解决方式包括热点 key 不设置物理过期时间、互斥锁重建缓存、逻辑过期、双缓存。

22. 缓存雪崩是什么?怎么解决?

text 复制代码
缓存雪崩是大量 key 同时失效,或者 Redis 整体不可用,导致请求大面积打到数据库。
解决方式包括 TTL 加随机值、缓存预热、Redis 高可用、多级缓存、限流、降级、熔断。

23. 缓存和数据库怎么保证一致性?

text 复制代码
强一致很难,通常保证最终一致。
主流方案是先更新数据库,再删除缓存。
删除失败时通过 MQ 重试、canal 监听 binlog、定时补偿和 TTL 兜底。
不推荐直接更新缓存,因为并发下容易旧值覆盖新值。

55.5 分布式锁类

24. Redis 分布式锁怎么实现?

text 复制代码
基础加锁使用 SET key uuid NX EX ttl,保证加锁和设置过期时间原子。
解锁使用 Lua 脚本,先判断 value 是不是自己的 uuid,如果是才删除,避免误删别人的锁。
生产中通常使用 Redisson,因为它支持可重入和自动续期。

25. 为什么不能用 SETNX + EXPIRE?

text 复制代码
SETNX 和 EXPIRE 是两条命令,不具备原子性。
如果 SETNX 成功后服务宕机,EXPIRE 没执行,就会死锁。
应该使用 SET key value NX EX ttl。

26. 为什么解锁要用 Lua?

text 复制代码
解锁需要先判断 value 是不是自己的,再删除 key。
如果 GET 和 DEL 分两条命令执行,中间可能发生锁过期和其他线程加锁,导致误删。
Lua 脚本可以保证判断和删除原子执行。

27. Redis 可重入锁怎么实现?

text 复制代码
可以用 Hash 实现。
key 是锁名,field 是 uuid:threadId,value 是重入次数。
加锁时如果锁不存在或锁属于当前线程,就 HINCRBY 加 1 并刷新过期时间。
解锁时 HINCRBY 减 1,减到 0 就删除锁。
整个逻辑用 Lua 保证原子性。

28. Redisson watchdog 是什么?

text 复制代码
Redisson 的 watchdog 是自动续期机制。
如果调用 lock.lock() 没有指定租约时间,默认锁过期时间是 30 秒,watchdog 每隔 10 秒检查一次。
如果当前线程仍然持有锁,就把过期时间刷新为 30 秒,避免业务未执行完锁提前过期。

29. RedLock 是什么?

text 复制代码
RedLock 是 Redis 作者提出的多 Redis master 节点分布式锁算法。
它要求客户端向多个独立 Redis 节点加锁,超过半数成功且总耗时小于锁有效期才算加锁成功。
但 RedLock 在极端网络分区、时钟漂移、GC 暂停场景下有争议。
强一致锁场景更适合 ZooKeeper 或 etcd。

55.6 高可用和集群类

30. Redis 主从复制流程?

text 复制代码
第一次同步通常是全量复制。
replica 连接 master 后,master 执行 BGSAVE 生成 RDB,把 RDB 发送给 replica,replica 加载后再执行复制期间的增量命令。
之后进入增量复制,master 持续把新写命令同步给 replica。
断线重连时,如果 backlog 中有对应 offset,可以部分重同步,否则重新全量复制。

31. Sentinel 是什么?

text 复制代码
Sentinel 是 Redis 高可用方案,负责监控 master/replica、判断故障、自动故障转移、选举新 master、通知客户端。
它能解决 master 宕机后的自动切换问题,但不能解决数据分片问题。

32. Sentinel 主观下线和客观下线区别?

text 复制代码
主观下线是单个 Sentinel 认为 master 不可用。
客观下线是多个 Sentinel 达到 quorum 后共同认为 master 不可用。
客观下线后才会进入故障转移流程。

33. Sentinel 如何选择新 master?

text 复制代码
优先选择 replica-priority 更高的从节点,数值越小优先级越高。
其次选择复制 offset 最大的从节点,因为它数据最新。
最后选择 runid 最小的从节点。

34. Redis Cluster 是什么?

text 复制代码
Redis Cluster 是官方集群方案,通过 16384 个 slot 实现数据分片。
key 通过 CRC16(key) % 16384 计算 slot。
每个 master 负责一部分 slot,每个 master 可以有 replica。
Cluster 支持水平扩容、缩容和故障转移。

35. Redis Cluster 为什么是 16384 个槽?

text 复制代码
因为 Redis Cluster 节点之间的心跳包需要携带 slot bitmap。
如果使用 65536 个槽,bitmap 需要 8KB;使用 16384 个槽只需要 2KB。
Redis Cluster 通常也不建议超过 1000 个 master,16384 个槽已经足够。
所以 16384 是槽位数量、网络开销和集群规模之间的折中。

36. Redis Cluster 支持强一致吗?

text 复制代码
不支持。
Redis Cluster 主从复制仍然是异步的。
如果 master 写成功后还没复制给 replica 就宕机,这部分写入可能丢失。
所以 Redis Cluster 不保证强一致。

37. Redis Cluster 多 key 操作为什么可能失败?

text 复制代码
因为多个 key 可能属于不同 slot,而不同 slot 可能在不同 master 上。
Redis Cluster 不支持跨 slot 的多 key 操作。
可以使用 hash tag,例如 user:{1001}:name 和 user:{1001}:age,让它们落到同一个 slot。

55.7 生产调优类

38. BigKey 有什么危害?

text 复制代码
BigKey 会导致内存倾斜、网络阻塞、删除阻塞、主从复制延迟、集群迁移困难。
如果直接 DEL 大 key,可能阻塞 Redis 主线程。

39. BigKey 怎么发现?

text 复制代码
可以使用 redis-cli --bigkeys、MEMORY USAGE、SCAN 配合类型长度命令。
例如 HLEN、LLEN、SCARD、ZCARD、STRLEN。

40. BigKey 怎么删除?

text 复制代码
String 可以用 UNLINK。
Hash 用 HSCAN + HDEL 分批删除。
Set 用 SSCAN + SREM 分批删除。
ZSet 用 ZSCAN + ZREM 或 ZREMRANGEBYRANK 分批删除。
List 用 LTRIM 分批裁剪。
核心思想是渐进式删除,避免阻塞主线程。

41. 为什么不能用 KEYS *?

text 复制代码
KEYS * 会一次性遍历所有 key,Redis 主线程会被阻塞。
如果线上 key 很多,可能造成 Redis 卡顿和业务超时。
生产应该使用 SCAN 渐进式遍历。

42. SCAN 有什么特点?

text 复制代码
SCAN 是基于游标的渐进式遍历。
它不会长时间阻塞 Redis。
但它可能返回重复 key,也不保证每次返回固定数量,需要客户端根据 cursor 是否为 0 判断结束。

43. Redis 生产中如何避免缓存雪崩?

text 复制代码
TTL 加随机值,避免集中失效。
热点数据预热。
Redis 使用主从、哨兵、Cluster 提升高可用。
服务侧加限流、熔断、降级。
必要时使用本地缓存兜底。

44. Redis 生产中如何调优?

text 复制代码
禁用 KEYS、FLUSHDB、FLUSHALL 等危险命令。
治理 BigKey 和 HotKey。
合理设置 TTL 并加随机值。
删除大 key 使用 UNLINK 或渐进式删除。
合理使用 Pipeline。
监控 slowlog、内存、复制延迟、持久化状态。
根据业务选择合适 maxmemory-policy。

56. Redis 面试总复习路线

建议按照下面顺序复习:

text 复制代码
1. Redis 是什么,和 MySQL 的关系。
2. Redis 为什么快,单线程与 IO 多路复用。
3. String、Hash、List、Set、ZSet 的使用场景和底层结构。
4. Bitmap、HyperLogLog、GEO、Stream 的典型场景。
5. 过期删除策略和内存淘汰策略。
6. RDB、AOF、混合持久化。
7. 事务、Pipeline、Lua、Pub/Sub。
8. 缓存穿透、击穿、雪崩。
9. 缓存和数据库双写一致性。
10. 分布式锁:SET NX EX、Lua 解锁、可重入、自动续期、Redisson、RedLock。
11. BigKey、MoreKey、SCAN、UNLINK。
12. 主从复制、哨兵 Sentinel、Redis Cluster。
13. 生产调优、监控、危险命令禁用。

57. 面试回答原则

57.1 先结论,后展开

不要一上来背概念。

例如 Redis 为什么快:

text 复制代码
先说:内存 + 高效数据结构 + IO 多路复用 + 单线程命令执行 + Pipeline。
再逐条展开。

57.2 必须体现工程风险

例如分布式锁不要只说 SETNX。

要说:

text 复制代码
SETNX + EXPIRE 非原子。
锁过期但业务没执行完。
解锁误删别人的锁。
不可重入。
需要 watchdog 自动续期。
Redis 主从异步复制可能导致锁丢失。

57.3 缓存问题要带场景

例如缓存击穿:

text 复制代码
热点商品 key 过期,大量请求同时打 MySQL。
解决:互斥锁、逻辑过期、热点 key 不过期。

57.4 高可用要说明不等于强一致

主从、哨兵、Cluster 都要强调:

text 复制代码
Redis 主从复制主要是异步复制。
高可用不代表零丢失。

57.5 生产题要讲权衡

比如持久化:

text 复制代码
RDB 恢复快但可能丢数据。
AOF 数据更完整但文件大、恢复慢。
混合持久化兼顾恢复速度和完整性。

58. 最终总结

Redis 面试的核心不是背命令,而是理解以下几条主线:

text 复制代码
1. Redis 为什么快:
   内存、高效数据结构、IO 多路复用、单线程命令执行、Pipeline、多 IO 线程。

2. Redis 怎么存:
   String、Hash、List、Set、ZSet,以及 SDS、dict、quicklist、listpack、intset、skiplist。

3. Redis 怎么保证可用:
   RDB、AOF、主从复制、哨兵、Cluster。

4. Redis 做缓存会有什么坑:
   穿透、击穿、雪崩、双写一致性、BigKey、HotKey。

5. Redis 做分布式锁要注意什么:
   SET NX EX、唯一 value、Lua 解锁、可重入、自动续期、Redisson、RedLock 争议。

6. Redis 生产怎么用:
   禁止危险命令、SCAN 替代 KEYS、UNLINK 删除大 key、合理 TTL、监控 slowlog 和内存、治理 BigKey/HotKey。

如果你能把这几条主线讲清楚,Redis 校招后端面试基本就能覆盖绝大多数问题。


Redis 面试讲义补丁:建议补充的增强点

1. HotKey 热点 Key

前面讲义讲了 BigKey,但 HotKey 可以再单独加强。

HotKey 是指某个 key 被极高频访问,例如秒杀商品详情、热搜榜第一、爆款文章详情。

危害:

  1. 单个 Redis 节点压力过大。
  2. 网络带宽集中。
  3. 请求排队,延迟升高。
  4. Redis Cluster 下即使整体节点很多,也可能因为一个热点 key 压垮单个分片。

解决:

  1. 本地缓存,例如 Caffeine。
  2. 热点 key 多副本复制,例如 key:1:copy1、key:1:copy2。
  3. 读请求随机打到多个副本。
  4. 热点探测,动态识别热点 key。
  5. 限流、降级。
  6. 对热点数据设置逻辑过期,异步刷新。

面试模板:

Redis HotKey 是指某个 key 被大量请求集中访问,导致单个 Redis 节点压力过高。

解决思路包括本地缓存、多副本缓存、热点探测、限流降级和逻辑过期。

如果是 Redis Cluster,HotKey 不会因为集群节点多就自动解决,因为一个 key 只能落到一个 slot,对应一个主节点。


2. 缓存预热、缓存降级、缓存熔断

缓存预热:

系统启动前或活动开始前,把热点数据提前加载到 Redis,避免冷启动请求全部打到数据库。

缓存降级:

Redis 不可用或数据库压力过高时,返回兜底数据、默认数据、静态数据,保证系统核心链路可用。

缓存熔断:

当 Redis 或下游数据库异常时,快速失败或走降级逻辑,避免请求堆积拖垮系统。

面试模板:

缓存体系不是只靠 Redis 本身,还要配合预热、限流、降级、熔断。

预热解决冷启动问题,限流保护 Redis 和数据库,降级保证异常时用户还能拿到可接受的兜底结果,熔断防止故障扩散。


3. Redis 限流

Redis 常见限流方式:

  1. 固定窗口计数器:INCR + EXPIRE。
  2. 滑动窗口:ZSet 记录请求时间戳。
  3. 令牌桶:按固定速率生成令牌。
  4. 漏桶:按固定速率处理请求。

固定窗口简单实现:

INCR rate:user:1001

EXPIRE rate:user:1001 60

问题:

固定窗口存在边界突刺问题,例如 59 秒和 60 秒附近可能瞬间通过两倍请求。

滑动窗口思路:

用 ZSet 存请求时间戳,每次请求删除窗口外数据,再统计窗口内请求数。

面试模板:

Redis 可以做限流,简单方案是 INCR + EXPIRE 实现固定窗口计数器。

如果要更平滑,可以用 ZSet 实现滑动窗口,把请求时间戳作为 score,统计最近一段时间内的请求数量。

更复杂的高并发限流可以使用令牌桶或漏桶算法。


4. Redis 事务、Lua、Pipeline 的区别再压缩

事务:

保证命令按顺序执行,执行期间不被插队,但不支持传统回滚。

Pipeline:

减少网络 RTT,不保证原子性。

Lua:

把多条命令封装到 Redis 服务端原子执行,适合分布式锁、限流、库存扣减。

面试模板:

事务解决命令顺序执行问题,Pipeline 解决网络 RTT 问题,Lua 解决多命令原子性问题。

三者目的不同,不能混为一谈。


5. Redis Cluster 的脑裂和数据丢失

Redis 主从复制是异步的,所以故障转移时可能丢失少量数据。

脑裂是指网络分区后,旧 master 没有及时下线,新 master 又被选出,短时间内可能出现两个 master 接收写请求。

缓解方式:

  1. 合理配置 min-replicas-to-write。
  2. 合理配置 min-replicas-max-lag。
  3. 客户端正确感知主从切换。
  4. 使用 Sentinel/Cluster 的官方高可用机制。
  5. 强一致场景不要把 Redis 当唯一权威存储。

面试模板:

Redis 高可用不等于强一致。

由于主从复制是异步的,master 写成功但还没同步给 replica 就宕机,数据可能丢失。

网络分区下还可能出现脑裂风险,可以通过 min-replicas-to-write 和 min-replicas-max-lag 降低风险,但不能彻底把 Redis 变成强一致系统。


6. Redis 客户端连接与连接池

Redis 虽然性能高,但连接数过多也会带来问题。

常见问题:

  1. 客户端连接泄漏。
  2. 连接池太小导致等待。
  3. 连接池太大导致 Redis 连接数过高。
  4. 大量慢请求造成连接堆积。
  5. Pipeline 使用不当导致输出缓冲区过大。

排查命令:

INFO clients

重点指标:

connected_clients

blocked_clients

client_recent_max_input_buffer

client_recent_max_output_buffer

面试模板:

Redis 客户端也需要连接池管理。

连接池太小会导致业务线程等待连接,连接池太大会增加 Redis 服务端连接压力。

生产中要结合 QPS、命令耗时、实例规格配置连接池,并监控 connected_clients、blocked_clients 和 slowlog。


相关推荐
小道仙971 小时前
Redisson源码解析,分布式锁解析
redis·分布式锁·redisson
加油20192 小时前
方法论:如何系统性的学习?
学习·学习方法·方法论
Amazing_Cacao2 小时前
CFCA精品可可饮品认证课程高级压力测试:在极端液态变量中,捍卫精品巧克力品质的稳定复现法则
笔记
小t说说2 小时前
科学素养培养:男孩女孩的不同“方程式”,真的有分性别学习平台?
学习
xian_wwq2 小时前
【学习笔记】变电保护、测控、安自、自动化系统概述
笔记·学习·保护
追梦开发者3 小时前
Redis 避坑指南①:从安装到连接,这 9 个坑 90% 的人都踩过
redis·缓存·database
lizhihai_993 小时前
股市学习心得—商业航天10大核心材料供应商
大数据·人工智能·学习
泰勒朗斯3 小时前
rootflight学习笔记
笔记·学习
知识分享小能手3 小时前
R语言入门学习教程,从入门到精通,R语言时间序列数据可视化(11)
学习·信息可视化·r语言