Java面试-08-分布式缓存Redis

Redis 面试题汇总

目录

  • 一、基础与数据类型
    • [1. Redis有哪些数据类型?底层实现和应用场景是什么?](#1. Redis有哪些数据类型?底层实现和应用场景是什么?)
    • [2. Redis常用的命令有哪些?](#2. Redis常用的命令有哪些?)
  • 二、持久化
    • [1. Redis提供了哪几种持久化方式?如何选择?](#1. Redis提供了哪几种持久化方式?如何选择?)
  • 三、高性能与线程模型
    • [1. Redis为什么快?](#1. Redis为什么快?)
    • [2. Redis为什么是单线程的?现在还是单线程吗?](#2. Redis为什么是单线程的?现在还是单线程吗?)
  • 四、Redis事务
    • [1. Redis事务的ACID特性](#1. Redis事务的ACID特性)
    • [2. Redis事务的常用命令](#2. Redis事务的常用命令)
    • [3. Redis事务的实现原理](#3. Redis事务的实现原理)
    • [4. Redis事务与数据库事务的区别](#4. Redis事务与数据库事务的区别)
    • [5. Redis事务的适用场景](#5. Redis事务的适用场景)
  • 五、高可用与集群
    • [1. Redis集群方案有哪些?](#1. Redis集群方案有哪些?)
    • [2. 哨兵模式工作流程](#2. 哨兵模式工作流程)
    • [3. Redis Cluster哈希槽原理](#3. Redis Cluster哈希槽原理)
  • 六、缓存常见问题
    • [1. 缓存穿透、击穿、雪崩的区别与解决方案](#1. 缓存穿透、击穿、雪崩的区别与解决方案)
    • [2. 布隆过滤器误判怎么办?](#2. 布隆过滤器误判怎么办?)
    • [3. 互斥锁方案下,其他线程等待多久?](#3. 互斥锁方案下,其他线程等待多久?)
  • 七、数据一致性
    • [1. Redis和MySQL数据一致性如何保证?](#1. Redis和MySQL数据一致性如何保证?)
    • [2. 项目实战:先更新DB还是先删缓存?](#2. 项目实战:先更新DB还是先删缓存?)
    • [3. 最终一致还是强一致?](#3. 最终一致还是强一致?)
  • 八、分布式锁与项目实战
    • [1. Redis分布式锁实现原理](#1. Redis分布式锁实现原理)
    • [2. 可能出现的问题及解决方案](#2. 可能出现的问题及解决方案)
    • [3. RedLock算法的争议](#3. RedLock算法的争议)
    • [4. 可重入锁实现](#4. 可重入锁实现)
    • [5. 看门狗机制](#5. 看门狗机制)
    • [6. 项目实战:军用多模态检索系统数据更新](#6. 项目实战:军用多模态检索系统数据更新)

一、基础与数据类型

1. Redis有哪些数据类型?底层实现和应用场景是什么?

数据类型 底层实现 项目实战场景
String 简单动态字符串(SDS)。 - embstr (短字符串):SDS和RedisObject连续存储,省内存。 - raw(长字符串):两者分开存储。 项目实战 : 1. 分布式锁set article:1001:lock uuid nx ex 10 2. 计数器 :文章浏览量 incr article:9527:view 3. Token存储:用户登录态
List - ziplist (元素少且值小):连续内存,节省空间。 - linkedlist(元素多):双向链表,方便两端操作。 项目实战 : 1. 消息队列LPUSH + BRPOP 实现生产者-消费者。 2. 最新动态LPUSH user:1001:news + LTRIM 保留前100条。
Hash - ziplist (字段少且值小)。 - hashtable(字段多):哈希表,链地址法解决冲突。 项目实战 : 1. 对象缓存 :存储用户信息 hmset user:1001 name "张三" age 25 2. 购物车hincrby cart:1001 article:2001 1
Set - intset (整数且少):有序无重复整数数组,二分查找。 - hashtable(非整数或多):元素作键,值为NULL。 项目实战 : 1. 共同好友sinter user:1001:friends user:1002:friends 2. 抽奖去重sadd lottery:2024 userid
ZSet - ziplist (元素少且值小)。 - skiplist + hashtable:跳跃表保证有序,哈希表快速查分值。 项目实战 : 1. 排行榜zadd rank:day:20240301 100 user1 2. 延迟队列 :时间戳作为score,zrangebyscore 拉取到期任务

2. Redis常用的命令有哪些?

  • 通用keys(生产禁用)、scan(推荐)、expirettldel
  • Stringsetgetincrsetnxmset
  • Hashhsethgethgetallhincrby
  • Listlpushrpopbrpoplrange
  • Setsaddsmemberssintersunion
  • ZSetzaddzrangezrevrangezscore

二、持久化

1. Redis提供了哪几种持久化方式?如何选择?

RDB(快照)
  • 原理:每隔一段时间,将内存中的数据集写到磁盘Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作
  • 保存策略save(同步阻塞)、bgsave(异步)、配置文件自动触发。save x x 几秒内有几个key的变化就触发
  • 优点:文件紧凑,恢复快,对主进程性能影响小。
  • 缺点:可能丢失最后一次快照后的数据。
AOF(追加文件)
  • 原理:以日志形式记录每个写操作,重启时重放。
  • 刷盘策略
    • always:每次写命令都同步,最安全,性能最差
    • everysec:每秒同步,最多丢1秒数据(默认推荐)
    • no:由操作系统决定,性能最好,最不安全
  • 优点:数据安全性高。
  • 缺点:文件大,恢复慢,IO压力大。
混合持久化
  • 原理:RDB做全量快照 + AOF做增量日志。
  • 优势:结合两者优点,重启加载RDB快,数据丢失少。
生产环境选择策略
  • 方案一(推荐)RDB + AOF混合持久化。RDB做冷备和快速恢复,AOF保证数据可靠性。
  • 方案二(缓存场景):完全关闭持久化,只做内存缓存,性能最高。
  • 方案三(主从架构):主库关闭持久化(避免fork影响性能),从库开启AOF。

三、高性能与线程模型

1. Redis为什么快?

  • 内存操作:基于内存操作,读写快。
  • 单线程模型:避免多线程的锁竞争,保证操作原子性。
  • 数据结构简单:对数据操作也简单。使用底层模型不同:
  • 构建了自己的VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
  • I/O 多路复用:使用I/O多路复用模型,非阻塞IO

2. Redis为什么是单线程的?现在还是单线程吗?

核心澄清

  • Redis的命令执行仍然是单线程的。
  • Redis 6.0+ 引入了多线程IO,用于网络数据的读写和解析,但命令执行依然是单线程。

为什么命令执行保持单线程?

  • 避免锁竞争,简化数据结构和逻辑。
  • 瓶颈通常不在CPU,而在内存和网络IO。
  • 多线程IO解决了网络吞吐的瓶颈。

多线程IO配置

conf 复制代码
io-threads 4          # 开启4个IO线程
io-threads-do-reads yes  # 开启读线程

四、Redis事务

1. Redis事务的ACID特性

Redis事务的ACID特性与传统数据库事务的区别

核心结论 :Redis事务不是严格意义上的ACID事务 ,它更像是一个批量执行命令的机制

2. Redis事务的实现原理

事务队列

  • 执行MULTI后,Redis会将后续的命令放入一个队列中,不立即执行。
  • 命令入队时,Redis会检查语法错误,如果有语法错误,会立即返回错误。
  • 语法正确的命令会被放入队列,等待EXEC执行。

命令执行

  • 执行EXEC时,Redis会按顺序执行队列中的所有命令。
  • 执行过程中,如果某个命令执行失败(如对非字符串类型执行INCR),后续命令仍会继续执行。
  • EXEC返回所有命令的执行结果,包括错误信息。

WATCH机制的实现

  • WATCH命令会将key标记为"被监视"。
  • 在EXEC执行前,Redis会检查被监视的key是否被修改。
  • 如果被修改,EXEC会返回nil,事务执行失败。
  • 如果未被修改,EXEC正常执行,并清除所有监视标记。

5. Redis事务的适用场景

五、高可用与集群

1. Redis集群方案有哪些?

方案 原理 优点 缺点
主从复制 一主多从,从库同步主库数据。 读写分离,读扩展,数据备份。 手动故障转移,写单点。
哨兵模式 在主从基础上加监控、通知、自动故障转移。 高可用,自动主从切换。 写单点,数据量受单机内存限制。
Redis Cluster 去中心化,数据分片(16384个槽),每个节点负责一部分槽。 分布式,线性扩展,自动故障转移。 架构复杂,跨节点操作受限。

2. 哨兵模式工作流程

  1. 主观下线:单个哨兵发现节点无响应。
  2. 客观下线:多个哨兵(>=quorum)都认为主节点下线。
  3. 选举领头哨兵:基于Raft算法选出一个执行故障转移。
  4. 故障转移
  • 从从库中选一个新主库(优先级 > 复制偏移量 > runid)
  • 其他从库指向新主库
  • 通知客户端

3. Redis Cluster哈希槽原理

  • 集群有 16384个哈希槽
  • key 通过 CRC16(key) % 16384 计算属于哪个槽。
  • 每个节点负责一段连续的槽范围(如 0-5000)。
  • 槽是数据迁移和负载均衡的基本单位。

为什么不使用一致性哈希?

哈希槽简化了数据分布和迁移的实现,节点增减只需迁移部分槽,不用全部重新哈希。


六、缓存常见问题

1. 缓存穿透、击穿、雪崩的区别与解决方案

问题 现象 原因 解决方案
缓存穿透 请求数据在缓存和DB都不存在,每次都打到DB。 恶意攻击(查不存在的ID)。 1. 布隆过滤器 :拦截不存在请求。 2. 缓存空值:key-null,短过期时间。
缓存击穿 热点key过期瞬间,大量请求打到DB。 热点key过期 + 高并发。 1. 永不过期 :后台更新。 2. 互斥锁:只让一个线程查DB更新缓存。
缓存雪崩 大量key同时过期,或Redis宕机,DB被打垮。 集中过期/缓存故障。 1. 过期时间加随机值 。 2. 集群高可用 。 3. 限流降级

2. 布隆过滤器误判怎么办?

背景:布隆过滤器判断"不存在"一定准确,判断"存在"可能误判。

解决方案

  1. 容忍:业务允许少量误判(如推荐系统)。
  2. 兜底:即使布隆说过"存在",查DB没数据,按穿透处理(缓存空值)。
  3. 定期重建:定时从DB重新构建布隆过滤器。

七、数据一致性

1. Redis和MySQL数据一致性如何保证?

常见方案对比

方案 原理 优点 缺点
旁路缓存 读:先查缓存,没有查DB并回写。 写:先更新DB,再删缓存。 简单,适合读多写少。 删除缓存可能失败,需重试。
读写穿透 缓存层封装DB操作,应用只操作缓存。 应用逻辑简单。 缓存层复杂,实现难度大。
异步消息 更新DB后发消息,消费者更新缓存。 解耦,最终一致。 消息可能丢失或重复。
Canal监听binlog 监听MySQL binlog变更,异步更新缓存。 与业务代码解耦,可靠。 引入Canal组件,复杂度高。

2. 项目实战:先更新DB还是先删缓存?

经典问题:先删缓存再更新DB,可能导致脏数据。

缓存更新策略:

  1. 旁路缓存:读时先查 Redis,无则查 MySQL 并更新 Redis;写时先更新 MySQL,再删 Redis缓存。
  • 延迟双删策略,更新完数据库后删除缓存,休眠一小会儿-几百毫秒,再删一次缓存。(原因:线程1更新数据库的瞬间,线程2查询到了旧数据,然后线程1删除缓存,此时网络延迟线程2把查询到的旧数据写到缓存里面了,导致读取到旧数据)
  1. 读写穿透:读、写操作由缓存处理,缓存负责和数据库同步。异步缓存写入:写操作直接更新 Redis,Redis 异步更新到 MySQL。
  2. 消息队列:更新 MySQL 时发消息到队列,消费者据此更新 Redis,实现最终一致。
  3. 分布式事务:如两阶段提交,协调者让参与者准备和提交;补偿事务(TCC)分尝试、确认、取消阶段。
  4. 定期同步:用定时任务定期从 MySQL 取数据更新到 Redis

八、分布式锁与项目实战

1. Redis分布式锁实现原理

使用场景:

  1. 定时任务操作数据库,是多实例的,所以需要使用到分布式锁

  2. 大模型问答的时候,同一个用户问答流式回答过程中,不能开启两个页面去问答,使用到了分布式锁

  3. 使用流程

    • 用set nx ex 原子命令加锁,key是锁的唯一键(可以使用业务:方法:唯一值),value是线程id,执行成功说明获取到了锁,可以执行业务
    • 在finally里面使用lua脚本校验线程id是否一致,然后再释放锁
  • 问题1:死锁问题:业务代码崩溃,长时间不释放锁,导致死锁
    • 解决:加上过期时间
  • 问题2:锁误释放问题:A线程获取到了锁,但是执行时间比较长,导致锁过期了,B线程获取到了锁,此时A线程执行完了要释放锁,却把B的锁释放了
    • 解决:给每个锁设置唯一标识,在finally里面使用lua脚本校验线程id是否一致,然后再释放锁
  • 问题3:锁过期导致并发问题:业务还没执行完,锁被释放,导致并发问题
    • 解决:使用redission的看门狗机制,锁会自动续期,30秒
  • 问题4:集群下数据一致性问题:主从复制延迟,导致锁失效
    • 解决:使用redission的红锁算法,客户端向一半以上的节点获取锁,获取到了说明成功,释放的时候向所有节点释放锁

2. 看门狗机制

作用:业务执行时间可能超过锁过期时间,自动续期。

实现思路

  • 获取锁成功后,启动一个守护线程。
  • 每隔 1/3 过期时间(如过期10秒,每3秒检查一次),如果任务还在执行,就续期。
  • Redisson框架已实现此机制。

3. 项目实战:分布式锁使用场景(问答)