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. 注意点:不保证原子性,且返回结果需客户端按序解析。

相关推荐
考虑考虑17 小时前
Redis8中新特性:TopK获取最高排名的数据
redis·后端
北执南念18 小时前
企业级 Spring Boot + WebSocket + Redis 分布式消息推送方案
spring boot·redis·websocket
Chan1618 小时前
【 Java八股文面试 | Redis篇 缓存问题、持久化、分布式锁 】
java·数据库·redis·后端·spring·缓存·面试
百***628521 小时前
Spring Boot 3.X:Unable to connect to Redis错误记录
spring boot·redis·后端
hoiii1871 天前
设置Redis在CentOS7上的自启动配置
数据库·redis·缓存
爬山算法1 天前
Redis(122)Redis的版本更新如何进行?
数据库·redis·bootstrap
thekenofdis1 天前
Lua脚本执行多个redis命令提示“CROSSSLOT Keys in request don‘t hash to the same slot“问题
redis·lua·哈希算法
oneslide1 天前
Kubernetes环境部署Redis集群
redis·容器·kubernetes
是垚不是土2 天前
构建高可用Redis:哨兵模式深度解析与Nacos微服务适配实践
redis·微服务·bootstrap