Redis面试常见问题详解

以下是 Redis 技术场景压力面问题的详细解答,覆盖核心原理、策略对比与场景适配要点:

1. Redis 单线程支撑高并发的原因及单线程模型细节

  • 单线程支撑高并发的核心原因:

    1. 基于内存操作:所有数据存于内存,读写速度远快于磁盘 IO,减少了线程阻塞的场景。

    2. IO 多路复用 :采用epoll/kqueue等 IO 多路复用技术,单线程可同时处理多个客户端连接的网络 IO,避免了多线程的上下文切换开销。

    3. 命令执行高效:Redis 命令大多是简单的内存操作,执行耗时极短,单线程能快速处理大量命令。

  • "单线程" 是否只用一个 CPU 核心:

    ❌ 并非如此。Redis 的核心命令执行线程是单线程,但持久化(RDB/AOF)、异步删除、集群同步等操作由独立的后台线程处理,会占用其他 CPU 核心;Redis 6.0 后还引入了IO 多线程,专门处理网络读写,进一步利用多核 CPU。

2. Redis 数据结构及底层实现

数据结构 底层实现 关键细节
String int (整数值)、embstr (短字符串,<44 字节)、raw(长字符串,≥44 字节) embstr 是连续内存分配,减少内存碎片;raw 是分开的对象头和字符串存储
Hash ziplist (压缩列表,元素少且小)、hashtable(哈希表,元素多或大) ziplist 切换为 hashtable 的条件:哈希元素数 > 512 或 单个元素值 > 64 字节(可配置)
List ziplist (元素少且小)、quicklist(Redis 3.2 后,ziplist 组成的双向链表) quicklist 结合了 ziplist 的内存紧凑和链表的高效增删特性
Set intset (整数集合,元素为整数且数量少)、hashtable(哈希表,非整数或数量多) intset 切换为 hashtable 的条件:元素数 > 512 或 出现非整数值(可配置)
Zset ziplist (元素少且小)、skiplist+hashtable(跳表 + 哈希表,元素多或大) ziplist 存储时,member 和 score 紧挨存放;跳表保证范围查询高效,哈希表保证元素查找高效
Bitmap 基于 String 的位操作 按位存储,节省内存,适用于布尔型数据统计(如用户签到)
HyperLogLog 基于基数估算算法 用极小内存统计海量数据的基数,存在一定误差(约 0.81%)
Geo 基于 Zset 的 score 存储经纬度(geohash 编码) 利用 Zset 的有序性实现地理位置的距离计算、范围查询

3. Redis 过期键删除策略及内存淘汰策略区别

  • 过期键删除策略:

    1. 定时删除:创建定时器,键过期时立即删除。优点是内存释放及时;缺点是定时器占用大量 CPU 资源,影响 Redis 性能。

    2. 惰性删除:键过期后不主动删除,只有访问时才检查并删除。优点是 CPU 开销小;缺点是过期键长期不访问会导致内存泄漏。

    3. 定期删除 :Redis 每隔一段时间(默认 100ms)随机抽取部分过期键删除,并限制删除操作的耗时。平衡了定时删除和惰性删除的优缺点,是 Redis 的默认策略

  • 不用单一策略的原因:定时删除耗 CPU,惰性删除漏内存,定期删除能在 CPU 和内存之间取平衡。

  • 内存淘汰策略 vs 过期删除策略:

    • 过期删除策略:针对已设置过期时间的键,处理其过期后的删除逻辑。

    • 内存淘汰策略:当 Redis 内存达到maxmemory时,从 ** 所有键(含未过期键)** 中选择部分键删除,释放内存,核心是解决内存溢出问题(如volatile-lruallkeys-lru等)。

4. Redis 持久化方式(RDB vs AOF)

特性 RDB AOF
核心原理 定时将内存中的数据快照写入二进制文件 记录 Redis 的每一条写命令,重启时重放命令恢复数据
优点 文件体积小,恢复速度快;对性能影响小(子进程执行) 数据安全性更高,可配置不同刷盘策略;日志文件易读懂
缺点 数据安全性低,两次快照间的数据易丢失;大数据集下,fork 子进程会阻塞主线程 文件体积大,恢复速度慢;写命令频繁时,AOF 重写会有性能开销
适用场景 适合做冷备份、数据恢复要求不高的场景;集群主从同步的基础 适合对数据安全性要求高(如金融场景)、需最小化数据丢失的场景
  • 持久化方案选择:

    1. 追求性能和恢复速度:仅用 RDB。

    2. 追求数据安全性:仅用 AOF(配置everysec刷盘)。

    3. 兼顾性能与安全性:开启 RDB+AOF(Redis 4.0 后支持混合持久化,AOF 文件中包含 RDB 快照和增量命令,兼顾体积与恢复速度)。

5. AOF 刷盘策略及重写机制

  • AOF 三种刷盘策略的性能与安全性权衡:

    策略 刷盘时机 数据安全性 性能
    always 每执行一条写命令,立即刷盘到磁盘 最高,仅丢失故障瞬间的命令 最低,磁盘 IO 频繁,性能损耗大
    everysec 每秒刷盘一次 较高,最多丢失 1 秒的数据 适中,平衡安全性与性能,是默认策略
    no 由操作系统决定刷盘时机 最低,可能丢失大量数据 最高,Redis 仅将命令写入缓冲区,不干预磁盘 IO
  • AOF 重写的触发条件:

    1. 手动触发 :执行bgrewriteaof命令。

    2. 自动触发:满足两个条件:

      ① 当前 AOF 文件大小超过

      基准大小(初始为 AOF 创建时的大小)的指定百分比(默认 100%);

      ② 当前 AOF 文件大小超过

      最小尺寸(默认 64MB)(可通过

      复制代码
      auto-aof-rewrite-percentage

      复制代码
      auto-aof-rewrite-min-size

      配置)。

  • AOF 重写的核心逻辑:

    Redis 创建子进程,遍历当前内存中的数据,将其转化为对应的写命令(如用

    复制代码
    hmset

    替代多条

    复制代码
    hset

    ),生成新的 AOF 文件;新文件写入完成后,将重写期间的增量命令追加到新文件,最后替换旧 AOF 文件。重写后的 AOF 文件体积更小,命令更简洁,恢复速度更快。

6. Redis 启动加载持久化文件的逻辑

  • 加载流程:

    1. Redis 启动时,先检查是否有 AOF 文件,若有则优先加载 AOF 文件恢复数据;

    2. 若没有 AOF 文件,再加载 RDB 文件恢复数据;

    3. 加载过程中,Redis 会阻塞客户端请求,直到数据恢复完成。

  • 优先级AOF 优先于 RDB,因为 AOF 的数据完整性更高。

  • 加载失败的影响:

    1. 若 RDB/AOF 文件损坏,Redis 启动失败并打印错误日志,需修复或删除损坏的持久化文件;

    2. 若文件存在但数据格式异常,可能导致部分数据丢失,需结合备份文件恢复。

7. Redis 内存限制配置及淘汰策略

  • maxmemory 的作用:限制 Redis 使用的最大内存,防止 Redis 耗尽服务器内存导致系统 OOM 或 Redis 进程被杀死。

  • 达到内存上限后的淘汰策略执行逻辑:

    策略类型 核心逻辑 适用场景
    LRU(最近最少使用) volatile-lru:从设置过期时间的键中淘汰最近最少使用的键;allkeys-lru:从所有键中淘汰最近最少使用的键 绝大多数常规场景,兼顾热点数据留存
    LFU(最不经常使用) volatile-lfu/allkeys-lfu:淘汰访问频率最低的键,比 LRU 更精准识别冷数据 访问频率差异大的场景(如电商商品详情缓存)
    TTL(剩余过期时间) volatile-ttl:从设置过期时间的键中淘汰剩余 TTL 最短的键 对数据过期时间敏感的场景(如限时优惠缓存)
    随机淘汰 volatile-random/allkeys-random:随机淘汰键 无明显热点数据的场景,仅作兜底
    不淘汰(默认) noeviction:拒绝新的写命令,返回错误 对数据完整性要求极高的场景(如金融交易)

8. Redis 缓存穿透、击穿、雪崩的场景与解决方案

问题类型 场景描述 核心解决方案
缓存穿透 请求不存在的 key,缓存和数据库均无数据,请求直击数据库 1. 缓存空值(设置短期过期时间);2. 布隆过滤器提前拦截不存在的 key;3. 接口层增加参数校验
缓存击穿 热点 key 突然过期,大量请求同时击中数据库 1. 热点 key 设置永不过期;2. 互斥锁(如 Redis 的 setnx),保证只有一个请求更新缓存;3. 热点数据预热
缓存雪崩 大量 key 同时过期,或 Redis 集群宕机,请求全部涌入数据库 1. 过期时间添加随机值,避免集中过期;2. Redis 集群做高可用(主从 + 哨兵 / 集群);3. 服务熔断 / 降级 / 限流;4. 多级缓存(本地缓存 + Redis)

9. Redis 实现分布式锁

  • 核心实现方式:

    1. 基础命令SET key value EX seconds NX(原子性设置 key,不存在则创建,同时设置过期时间),替代setnx + expire(非原子操作,易出现死锁)。

    2. 释放锁:用 Lua 脚本原子性校验 value 并删除(防止误删其他客户端的锁),脚本逻辑:

      复制代码
      if redis.call('get', KEYS[1]) == ARGV[1] then
          return redis.call('del', KEYS[1])
      else
          return 0
      end
  • 核心命令作用:

    • setnx:key 不存在时设置,实现 "加锁",保证互斥性;

    • set ex nx:原子性完成 "加锁 + 过期时间设置",避免死锁。

  • 需考虑的问题:

    1. 死锁:设置过期时间兜底,业务执行超时则自动释放锁;

    2. 锁超时:若业务执行时间超过锁过期时间,可通过 "续期"(如 Redisson 的 watchdog 机制)延长锁时间;

    3. 主从切换:主节点加锁后宕机,从节点未同步锁数据,导致锁失效,可采用 Redlock 算法或 ZooKeeper 实现分布式锁。

10. Redlock 算法

  • 核心逻辑:

    1. 向多个独立的 Redis 节点(至少 3 个)发送加锁请求(SET key value EX nx);

    2. 计算加锁成功的节点数,若成功数≥N/2+1(N 为节点数)且总耗时≤锁过期时间,判定加锁成功;

    3. 释放锁时,向所有节点发送释放请求。

  • 解决的问题 :传统单 Redis 节点的分布式锁存在主从切换导致的锁失效问题,Redlock 通过多节点投票实现高可用。

  • 生产是否推荐使用:

    不推荐。原因:

    1. Redis 的异步复制特性仍可能导致锁数据不一致;

    2. 算法对时钟同步要求高,节点时钟偏移会导致锁判断错误;

    3. 实现复杂,性能开销大,不如 ZooKeeper 的分布式锁可靠。

11. Redis 实现限流的方案

  • 常见限流方案:

    1. 固定窗口计数 :用 Redis 的incr统计单位时间内的请求数,超过阈值则限流;

    2. 滑动窗口计数:用 Redis 的 Zset 存储请求时间戳,统计窗口内的请求数,剔除过期时间戳;

    3. 漏桶算法:用 Redis 模拟漏桶,控制请求的流出速率,平滑突发流量;

    4. 令牌桶算法:用 Redis 生成令牌,请求需获取令牌才能执行,支持突发流量。

  • 漏桶 vs 令牌桶:

    特性 漏桶算法 令牌桶算法
    流量控制 严格控制流出速率,无法处理突发流量 可根据令牌生成速率和桶容量,处理合理的突发流量
    核心逻辑 请求先进入桶中,桶按固定速率放水,桶满则拒绝请求 按固定速率生成令牌放入桶中,请求需获取令牌才能执行
  • Redis + Lua 实现高性能限流:将限流逻辑(如令牌桶 / 滑动窗口)写入 Lua 脚本,Redis 原子性执行脚本,减少网络往返,示例(固定窗口限流):

    复制代码
    local key = KEYS[1]
    local limit = tonumber(ARGV[1])
    local expire = tonumber(ARGV[2])
    local current = tonumber(redis.call('incr', key))
    if current == 1 then
        redis.call('expire', key, expire)
    end
    if current > limit then
        return 0
    else
        return 1
    end

12. Redis 中的 Lua 脚本

  • 作用:

    1. 实现复杂的原子性操作,避免多命令执行的竞态条件;

    2. 减少客户端与 Redis 的网络交互次数,提升性能;

    3. 自定义扩展 Redis 的命令逻辑。

  • 保证原子性的原因:Redis 执行 Lua 脚本时,会阻塞其他命令的执行,直到脚本运行完成,确保脚本内的所有命令作为一个整体执行,无中间中断。

  • 性能注意事项:

    1. 避免编写耗时过长的脚本(如循环遍历大量数据),会阻塞 Redis 主线程;

    2. 脚本体积不宜过大,减少网络传输和解析开销;

    3. 避免在脚本中执行keys *等耗时命令;

    4. 利用 Redis 的脚本缓存(script load),避免重复编译脚本。

13. Redis实现延迟队列:List+Brpoplpush、Sorted Set+Zrangebyscore

方案 核心实现 优点 缺点 适用场景
List + Brpoplpush 将延迟任务存入List,用brpoplpush阻塞读取;需额外线程定时将到期任务移入消费List 实现简单、原生命令支持、阻塞读取无轮询空耗 无法精准按时间排序、不支持重复任务、单List易堆积 延迟时间固定、任务量小、对时间精度要求低的场景(如简单重试)
Sorted Set + Zrangebyscore 时间戳score,任务ID/内容为member;定时用zrangebyscore获取到期任务并删除 时间精度高、支持按时间排序、可去重、易扩展 需定时轮询(可能有空轮询)、高并发下轮询易有性能损耗 对时间精度要求高、任务量中大型、需灵活设置延迟时间的场景(如订单超时、定时通知)

14. Redis缓存与数据库一致性保证

  • 先更数据库,再删缓存

    • 问题:更新数据库后,若删缓存失败,会导致缓存存旧数据,数据库存新数据,数据不一致。

    • 优化:增加缓存删除重试机制(如消息队列)、设置缓存过期时间兜底。

  • 先删缓存,再更数据库

    • 问题:高并发下,删缓存后数据库更新完成前,其他请求会从数据库加载旧数据回写缓存,导致不一致。

    • 优化:延迟双删(先删缓存→更新数据库→延迟1~5秒再删一次缓存)、加分布式锁保证更新与查询的互斥。

  • 终极方案:分布式事务(如Seata)、Canal监听数据库binlog异步更新缓存。

15. 高并发下Redis缓存预热核心思路

  • 核心思路:在系统上线/重启前,提前将热点数据从数据库加载到Redis,避免高并发时大量请求直击数据库。

  • 避免压垮数据库/Redis的措施

    1. 分批加载:按数据热度分批次、分时段预热,避免一次性全量加载。

    2. 限流控速:控制预热请求的QPS,防止数据库连接池耗尽。

    3. 降级兜底:预热中若数据库/Redis压力过大,触发降级(如返回默认值)。

    4. 主从分离:用数据库从库加载数据,避免影响主库业务。

    5. 本地缓存辅助:预热过程中,用应用本地缓存暂存部分数据,减少Redis压力。

16. Redis缓存降级

  • 触发条件:Redis集群宕机/超时、缓存穿透/击穿/雪崩、Redis达到最大内存限制、数据库压力过高。

  • 降级策略分类

    1. 熔断:短期切断Redis请求,直接返回默认值/空数据,适用于Redis突发故障。

    2. 限流:限制Redis的请求QPS,超出部分走降级逻辑,适用于Redis高负载。

    3. 静态化:将热点数据预生成静态文件(如JSON),请求直接读取静态文件,适用于Redis长期故障。

    4. 降级查询:简化查询逻辑(如只查部分字段),减少Redis/数据库压力。

  • 降级开关设计

    1. 基于配置中心(如Nacos/Apollo)做动态开关,支持一键降级/恢复。

    2. 按业务模块分粒度设计开关(如商品详情、订单列表单独降级)。

    3. 结合监控指标自动触发降级(如Redis超时率>50%自动熔断)。

    4. 增加降级日志与告警,便于追踪降级原因与恢复。

17. Redis实现分布式Session

  • 实现方式

    1. 将Session数据序列化后存入Redis,以sessionId为key,设置过期时间。

    2. 客户端(浏览器)用Cookie存储sessionId,每次请求携带该ID从Redis获取Session。

  • 需考虑的问题

    1. Session过期:设置合理的Redis key过期时间,支持客户端刷新Session时重置过期时间。

    2. 多集群共享:Redis采用主从+哨兵/集群模式,保证Session数据跨节点可用;跨机房场景用Redis集群同步。

    3. 安全性sessionId设置复杂随机值,防止伪造;Cookie开启HttpOnlySecure属性,防止XSS和CSRF;Session数据加密存储。

  • 与Spring Session结合的核心逻辑 : Spring Session通过重写HttpSession接口,将Session的创建、读取、更新、销毁操作委托给Redis;通过过滤器(SpringSessionRepositoryFilter)拦截请求,从Redis中获取/存储Session数据,实现分布式Session的透明化使用。

18. Redis Pipeline

  • 作用:批量发送多个Redis命令,减少客户端与Redis的网络往返次数,提升批量操作的性能。

  • 核心原理:客户端将多个命令缓存到本地缓冲区,一次性发送给Redis;Redis依次执行命令后,将所有结果批量返回给客户端。

  • 与事务(Multi/Exec)的区别

    特性 Pipeline 事务(Multi/Exec)
    执行逻辑 命令按顺序执行,无原子性保证 命令入队后原子性执行(全部执行或全部不执行)
    响应时机 批量发送后批量返回结果 Exec后才执行并返回所有结果
    依赖关系 不支持命令间的依赖(如后一条用前一条的结果) 支持命令入队时的依赖判断
  • 最佳使用场景

    1. 批量执行无依赖 的读/写命令(如批量设置set、批量获取get)。

    2. 高并发下的批量数据插入/查询(如数据迁移、统计报表生成)。

    3. 网络延迟较高的场景(如跨机房Redis调用),减少网络开销。

完。

相关推荐
奋进的芋圆42 分钟前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
小熊officer1 小时前
Python字符串
开发语言·数据库·python
渐暖°1 小时前
JDBC直连ORACLE进行查询
数据库·oracle
萧曵 丶2 小时前
Next-Key Lock、记录锁、间隙锁浅谈
数据库·sql·mysql·mvcc·可重复读·幻读
han_2 小时前
从一道前端面试题,谈 JS 对象存储特点和运算符执行顺序
前端·javascript·面试
做cv的小昊2 小时前
【TJU】信息检索与分析课程笔记和练习(7)数据库检索—Ei
数据库·笔记·学习·全文检索
zgl_200537792 小时前
ZGLanguage 解析SQL数据血缘 之 标识提取SQL语句中的目标表
java·大数据·数据库·数据仓库·hadoop·sql·源代码管理
莳花微语3 小时前
记录一次OGG进程abended,报错OGG-01431、OGG-01003、OGG-01151、OGG-01296问题的处理
数据库·sql·mysql
尋有緣3 小时前
力扣1355-活动参与者
大数据·数据库·leetcode·oracle·数据库开发
萧曵 丶3 小时前
MySQL三大日志系统浅谈
数据库·sql·mysql