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调用),减少网络开销。

完。

相关推荐
Lee川7 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
倔强的石头_7 小时前
kingbase备份与恢复实战(二)—— sys_dump库级逻辑备份与恢复(Windows详细步骤)
数据库
Lee川10 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i12 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有13 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有13 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫14 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫14 小时前
Handler基本概念
面试
Wect14 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼15 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试