Redis的基础,经典,高级问题解答篇

目录

一,基础

二,经典

缓存雪崩:

[1. Redis事务的原子性](#1. Redis事务的原子性)

[2. 与MySQL事务的区别](#2. 与MySQL事务的区别)

[1. 主从复制原理](#1. 主从复制原理)

[2. 哨兵模式故障转移流程](#2. 哨兵模式故障转移流程)

[3. 客户端感知故障转移](#3. 客户端感知故障转移)

三,高级


一,基础

  1. Redis的5种基础数据类型及使用场景?Zset底层实现原理?

    1. String(字符串)

      • 场景 :缓存简单键值对(如用户会话信息、计数器)、分布式锁(如SETNX)、二进制数据存储(如图片Base64)。

      • 特点 :支持原子操作(如INCRDECR),最大存储512MB。

    2. List(列表)

      • 场景 :消息队列(LPUSH/RPOP实现生产者-消费者)、最新消息排行(如微博时间线)、阻塞式任务调度。

      • 特点:双向链表,支持按索引操作(但时间复杂度高)。

    3. Hash(哈希表)

      • 场景 :存储对象(如用户信息字段:HSET user:1 name "Alice")、聚合数据(如购物车商品信息)。

      • 特点:支持单字段读写,内存优化(底层为ziplist或hashtable)。

    4. Set(集合)

      • 场景 :去重数据(如用户标签)、共同好友(SINTER求交集)、随机抽奖(SRANDMEMBER)。

      • 特点:无序、自动去重,支持集合运算(并/交/差)。

    5. Zset(有序集合)

      • 场景:排行榜(按分数排序)、延迟队列(以时间戳为score)、带权重的任务调度。

      • 特点 :元素唯一但score可重复,支持范围查询(ZRANGEBYSCORE)。

    6. Zset的底层由 跳跃表(Skip List)字典(Dict) 组成,或在小数据量时使用 listpack(替代旧版的ziplist)

      • 跳跃表

        • 多层链表结构,高层链表作为"快速通道",支持O(log N)的查询、插入、删除。

        • 每个节点包含scorevalue,按score排序,支持高效范围操作(如ZRANGE)。

      • 字典

        • 维护value -> score的映射,实现O(1)的单元素查询。
      • 内存优化

        • 元素数≤zset-max-listpack-entries且元素大小≤zset-max-listpack-value时,使用listpack存储。
  2. Redis持久化机制RDB和AOF的区别?如何选择?

    特性 RDB AOF
    原理 定时生成全量数据快照(二进制文件) 记录所有写命令(追加日志文件)
    文件体积 小(压缩二进制) 大(文本命令,需重写优化)
    恢复速度 快(直接加载内存) 慢(重放命令)
    数据安全 可能丢失最后一次快照后的数据 可配置fsync策略(无/秒级/每次写)
    资源消耗 高(fork子进程内存开销) 低(追加日志,重写时才有fork开销)
    1. 选择策略

      1. 高数据安全 :选择AOF(appendfsync everysec),容忍秒级丢失。

      2. 快速恢复/备份:选择RDB(如每日备份)。

      3. 混合模式 :Redis 4.0+支持RDB+AOF,AOF记录增量,RDB做全量备份。

  3. 如何用Redis实现分布式锁?需要注意哪些问题?

    1. 实现步骤

    java 复制代码
    // 加锁:SET key unique_value NX PX 30000
    String result = jedis.set("lock_key", "client1", "NX", "PX", 30000);
    if ("OK".equals(result)) {
        // 执行业务逻辑
    } 
    
    // 解锁:Lua脚本保证原子性
    String script = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "   return redis.call('del', KEYS[1]) " +
        "else " +
        "   return 0 " +
        "end";
    Object unlockResult = jedis.eval(script, Collections.singletonList("lock_key"), Collections.singletonList("client1"));

    注意事项

    1. 锁过期时间:需预估业务耗时,建议设置自动续期(如Redisson的看门狗)。

    2. 唯一标识:value使用唯一值(如UUID+线程ID),避免误删其他客户端的锁。

    3. 优化方案

    4. 使用Redisson库,内置看门狗、可重入锁、红锁(RedLock)实现。

    5. 对锁操作添加重试机制(如指数退避)。

    6. 原子性操作 :加锁(SET NX PX)和释放锁(Lua脚本)必须原子化。

    7. 集群问题

      • 主从异步复制:主节点宕机可能导致锁丢失,可考虑RedLock算法(需部署多实例)。

      • RedLock争议:依赖系统时钟一致性,需权衡CAP。

二,经典

  1. 什么是缓存穿透/雪崩/击穿?分别给出解决方案

    1. 缓存穿透:

      1. 问题 :大量请求查询数据库中不存在的数据 (如非法ID),绕过缓存直接压垮数据库。
        解决方案

        1. 布隆过滤器(Bloom Filter):在缓存层前加布隆过滤器,预存所有合法键的哈希值,拦截非法请求。

          • 特点:存在一定误判率(可能将非法请求误判为合法),需权衡内存占用。
        2. 缓存空值 :对查询结果为null的键,缓存空值并设置短过期时间(如30秒)。

          • 注意:需定期清理无效空值,避免内存浪费。
    缓存雪崩:
    1. 问题 :大量缓存同时过期 或缓存服务宕机,导致请求全部涌向数据库。
      解决方案

      1. 随机过期时间 :为缓存设置基础过期时间 + 随机偏移值(如 TTL = 24h + random(0, 1h))。

      2. 永不过期 + 异步更新

        • 缓存不设过期时间,通过后台线程定期更新。

        • 结合双缓存策略:主缓存永不过期,备份缓存设置过期时间作为兜底。

      3. 熔断降级:使用Hystrix等工具,在数据库压力过大时直接返回默认值或限流。

    2. 缓存击穿:

      1. 问题热点数据过期 时,高并发请求瞬间击穿缓存,直接访问数据库。
        解决方案

      2. 互斥锁(Mutex Lock)

        • 当缓存失效时,通过分布式锁(如Redis的SETNX)控制只有一个线程重建缓存,其他线程等待。

        • 示例代码:

        java 复制代码
        public String getData(String key) {
            String value = redis.get(key);
            if (value == null) {
                if (redis.setnx("lock_" + key, "1", 10)) { // 获取锁
                    value = db.query(key);
                    redis.setex(key, 3600, value);
                    redis.del("lock_" + key); // 释放锁
                } else {
                    Thread.sleep(100); // 等待后重试
                    return getData(key);
                }
            }
            return value;
        }
        • 逻辑过期:缓存永不过期,但存储的数据中增加逻辑过期时间字段,由业务代码判断是否需要异步更新。
  2. Redis事务的原子性如何理解?与MySQL事务的区别?
    1.

    1. Redis事务的原子性
      • 实现方式 :通过MULTI(开启事务)、EXEC(提交事务)、DISCARD(取消事务)命令实现。

      • 原子性含义

        • 命令队列的原子执行:事务中的命令会被序列化并按顺序执行,不会被其他客户端命令打断。

        • 不支持回滚:若事务中某条命令执行失败(如语法错误),其他命令仍会继续执行,无回滚机制。

        • 示例:

        java 复制代码
        MULTI
        SET key1 "A"  # 入队
        INCR key2     # 入队(若key2非数值类型,执行时报错)
        EXEC          # 提交事务,第二条命令执行失败,但第一条仍生效
      2. 与MySQL事务的区别
      特性 Redis MySQL
      原子性 仅保证命令队列的批量执行,无回滚 支持ACID,失败时自动回滚
      隔离性 无隔离级别,事务执行期间可能被其他客户端修改数据 支持多隔离级别(如读已提交、可重复读)
      持久性 依赖持久化机制(RDB/AOF) 依赖Redo Log和Binlog保证持久性
      使用场景 简单批量操作(如批量SET) 复杂业务逻辑(如转账、订单处理)
  3. Redis主从复制原理?哨兵模式如何实现故障转移?
    1.

    1. 主从复制原理
    1. 核心流程

      1. 全量同步(首次连接)

        • 从节点发送PSYNC ? -1命令请求同步。

        • 主节点执行BGSAVE生成RDB文件并发送给从节点,同时缓存期间的写命令到复制缓冲区。

        • 从节点加载RDB后,主节点发送缓冲区中的写命令使其追上最新状态。

      2. 增量同步(断线重连)

        • 从节点发送PSYNC <runid> <offset>,主节点根据offset从复制缓冲区发送增量数据。

        • 若复制缓冲区数据丢失(如缓冲区溢出),触发全量同步。

    2. 关键配置

      • repl-backlog-size:调整复制缓冲区大小,避免频繁全量同步。
    2. 哨兵模式故障转移流程

    哨兵(Sentinel)是Redis的高可用解决方案,负责监控、通知和自动故障转移:
    5. 监控

    • 每个哨兵节点定期向主节点、从节点和其他哨兵发送PING命令检测存活状态。
    1. 主观下线(SDOWN)

      • 若哨兵在down-after-milliseconds时间内未收到主节点的有效响应,标记其为主观下线。
    2. 客观下线(ODOWN)

      • 当超过半数哨兵认为主节点主观下线,则标记为客观下线。
    3. 选举Leader哨兵

      • 通过Raft算法选举一个Leader哨兵来执行故障转移。
    4. 故障转移

      • Leader哨兵从从节点列表中选出一个新的主节点(基于优先级、复制偏移量等)。

      • 向其他从节点发送SLAVEOF命令,使其复制新主节点。

      • 更新客户端配置,通知其连接新主节点。

    复制代码
    ##### **3. 客户端感知故障转移**
    1. 客户端通过订阅哨兵的+switch-master事件获取新主节点地址,或通过哨兵API动态查询。

三,高级

  1. Redis Cluster集群模式的数据分片原理?如何实现动态扩容?

    1. 数据分片原理

      Redis Cluster采用哈希槽(Hash Slot)机制进行数据分片,共有16384个槽位。每个键通过CRC16(key)计算哈希值,再对16384取模确定所属槽位。集群中的每个节点负责一部分槽,数据分布由槽分配决定。客户端请求时,若连接节点不负责该槽,会返回MOVED重定向错误,引导客户端访问正确节点。节点间通过Gossip协议交换集群状态,维护槽分配信息的一致性。

      动态扩容实现

    2. 添加新节点 :使用CLUSTER MEET将新节点加入集群。

    3. 迁移槽数据

      • 使用redis-cli --cluster reshard触发槽重新分片。

      • 源节点将槽内键逐个迁移到目标节点,期间对正在迁移的键的请求,源节点返回ASK重定向,客户端需重试到目标节点。

    4. 更新槽分配:迁移完成后,槽归属信息通过Gossip协议同步到整个集群。

    5. 副本扩展 :新增副本节点通过CLUSTER REPLICATE同步主节点数据。

  2. Redis内存淘汰策略有哪些?如何设计大Key热Key的解决方案?

    1. 内存淘汰策略 (通过maxmemory-policy配置)

    2. noeviction:拒绝写入新数据(默认)。

    3. volatile-ttl:淘汰过期时间最近的键。

    4. LRU/LFU策略

      • allkeys-lru/volatile-lru:基于最近最少使用。

      • allkeys-lfu/volatile-lfu:基于访问频率(4.0+)。

    5. 随机淘汰allkeys-random/volatile-random

    6. 大Key解决方案

    7. 拆分:如将大Hash拆分为多个小Hash,通过键名后缀分片。

    8. 异步删除 :使用UNLINK代替DEL,非阻塞释放内存。

    9. 压缩:对Value进行压缩(如GZIP),但会增加CPU开销。

    10. 监控 :通过redis-cli --bigkeys定期扫描大Key。

    11. 热Key解决方案

    12. 多级缓存:本地缓存(如Guava)减少Redis访问。

    13. 读写分离:通过副本节点分散读压力。

    14. 分片打散 :使用Hash Tag(如{user1000}.profile)强制热Key分布到同一节点,并增加副本。

    15. Proxy支持:如Twemproxy或Codis自动分散请求。

  3. Redis多线程模型演进(从单线程到IO多线程)?管道技术原理?

    1. 多线程演进

    2. 单线程模型(6.0前):单线程处理所有网络I/O、命令解析和执行,避免锁竞争,但无法利用多核CPU。

    3. I/O多线程(6.0+)

      • 多线程网络I/O :主线程负责接收连接,I/O线程处理读写(配置io-threads启用)。

      • 单线程命令执行:命令执行仍为单线程,保证原子性。

    4. 性能提升:高并发场景下,网络I/O瓶颈缓解,吞吐量显著提升。

    5. 管道技术(Pipeline)原理

    6. 批量发送:客户端将多个命令打包发送,减少网络往返次数(RTT)。

    7. 服务端顺序执行:服务器按顺序依次执行命令,全部完成后一次性返回结果。

    8. 优势:适用于批量操作(如批量写入),提升吞吐量。

    9. 注意点:不保证原子性,且返回结果需客户端按序解析。

相关推荐
Cloud_.24 分钟前
Spring Boot整合Redis
java·spring boot·redis·后端·缓存
martian6655 小时前
分布式并发控制实战手册:从Redis锁到ZK选主的架构之道
java·开发语言·redis·分布式·架构
天上掉下来个程小白7 小时前
Redis-06.Redis常用命令-列表操作命令
java·redis·后端·springboot·苍穹外卖
陈平安Java and C9 小时前
Redis的Java客户端
redis
阿黄学技术13 小时前
Redis场景问题2:缓存击穿
java·数据库·redis·缓存
昔我往昔13 小时前
Redis的缓存雪崩和缓存穿透的理解和如何避免
数据库·redis·缓存
敲键盘的小夜猫15 小时前
Redisson延迟队列实战:分布式系统中的“时间管理者“
java·redis·分布式
晴天Y2820 小时前
redis部署架构
数据库·redis·架构
Foyo Designer21 小时前
【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的缓存技术:使用 Redis 提升性能
java·spring boot·redis·spring·缓存
JIU_WW1 天前
Redis大key问题
数据库·redis