Redis相关
Redis 数据类型与场景
- 常用五大类型:String、Hash、List、Set、ZSet 扩展类型:Bitmap、HyperLogLog、Geo、Stream。
- Redis Bitmap
- String:二进制安全,可存 JSON、计数器。典型场景:缓存对象、分布式锁、限流计数。
- Hash:类似字典,适合存用户信息、配置项等结构化数据。
- List:双端链表,用于消息队列、任务队列(
LPUSH/BRPOP)。 - Set:无序去重集合,用于标签、好友关系、抽奖。
- ZSet:有序集合,成员分数排序,适合排行榜、延迟队列。
- Bitmap:位图,适合签到、活跃用户标记;1 bit 表示一个状态。
- HyperLogLog:估算基数,适合 UV 统计,误差约 0.81%。
- Geo:地理位置,查询附近地点。
- Stream:消息队列,支持消费组、可靠消费。
⚠️注意事项
- 选择数据结构时考虑内存占用与操作复杂度,避免误用 ZSet 做大宽表。
- Hash 内部字段过多会与 String 体积相差无几,需要设置
hash-max-ziplist-entries/values。 - Stream 需要定期
XDEL/XTRIM避免无限增长。
📚【面试题目1:Redis的核心数据结构有哪些?各自的底层实现与使用场景是什么?】
回答要点关键字
(1) 核心结构:String、Hash、List、Set、Sorted Set(ZSet)
(2) 底层实现:SDS/哈希表/双向链表/跳表/整数集合/压缩列表
(3) 使用场景:缓存/计数/队列/排行榜/去重/消息通知
(4) 关键特性:String动态扩容、ZSet有序性、Hash紧凑存储、List双向操作
打开详情
🍺基础回答:
Redis 最常用的就是五种核心数据结构,日常开发基本都围绕它们。String 就是键值对,存字符串、数字都行,比如缓存用户昵称、统计文章阅读量;Hash 适合存对象,比如用户信息(id 对应 name、age、phone),操作单个字段方便;List 是有序列表,能做消息队列或最新消息排行;Set 是无序去重集合,比如用户标签、好友列表去重;Sorted Set 是有序的,能按分数排序,适合做排行榜(比如游戏积分排名)。
🎉高级扩展版:
每种数据结构的底层实现有优化,不是单一结构:1. String:底层是简单动态字符串(SDS),不是C语言原生字符串,支持动态扩容(预分配空间减少扩容次数),还能存储字符串、整数、浮点数,支持原子操作(incr/decr);2. Hash:底层是哈希表(字典),当数据量小时用压缩列表(ziplist)节省空间(键值对少且字段小时),数据量大时转为哈希表;3. List:底层是双向链表,Redis 3.2 后改为 quicklist(双向链表+压缩列表组合),兼顾内存和操作效率,支持首尾插入/删除(O(1))、中间查询(O(n));4. Set:底层是哈希表(无值,仅存键)或整数集合(当元素全是整数且数量少时),支持交集、并集、差集操作;5. Sorted Set(ZSet):底层是跳表(skiplist)+ 哈希表,跳表保证有序性和快速查询(O(logn)),哈希表映射成员到分数,支持按分数范围查询、排名获取。
📌 加分项:
对比 SDS 与 C 字符串的优势(SDS 记录长度,避免 strlen 遍历、防止缓冲区溢出、二进制安全);说明 ZSet 跳表的结构(多层索引,减少查询时的比较次数);提到 Redis 4.0+ 的新结构(如 HyperLogLog 用于基数统计、Geo 用于地理位置查询);结合实际场景优化,比如 Hash 适合存储小对象(字段≤512 且值≤64B 时用 ziplist,内存更紧凑),List 做队列时用 lpush + rpop 或 brpop 阻塞获取。
⚠️注意事项:
String value 最大容量是 512MB,避免存储过大数据(如大文件二进制数据);Hash 不适合存储大量字段(字段过多会转为哈希表,内存占用增加);List 中间插入/删除效率低(O(n)),避免用于随机访问场景;ZSet 的分数是双精度浮点数,可能有精度丢失(如存金额建议放大 100 倍存整数);Set 交集/并集操作数据量大时会阻塞 Redis,需控制数据规模。
📚【面试题目2:Redis的持久化机制有哪些?RDB与AOF的区别与选型建议?】
回答要点关键字
(1) 核心机制:RDB(快照持久化)、AOF(日志持久化)
(2) 实现原理:RDB 定时生成内存快照、AOF 记录写操作日志(append-only)
(3) 核心区别:性能(RDB 优)、数据完整性(AOF 优)、文件大小(RDB 小)
(4) 选型建议:单实例选混合持久化、高可用场景配合主从复制、备份策略组合
打开详情
🍺基础回答:
Redis 持久化就是把内存里的数据存到磁盘,防止重启丢失,主要有 RDB 和 AOF 两种。RDB 是定时拍快照,比如每隔 5 分钟或满足“1000 次写操作”就把当前内存数据生成一个 .rdb 文件,恢复时直接加载这个文件,速度快但可能丢数据(比如快照后的数据没来得及备份)。AOF 是记录所有写操作(比如 set、hmset),以日志形式追加到文件,恢复时重新执行日志里的命令,数据完整性高(默认每秒刷盘),但日志文件大,恢复速度慢。
🎉高级扩展版:
- RDB 详解:- 触发方式:手动触发(save 阻塞 Redis、bgsave 异步生成快照,fork 子进程写文件,主进程不阻塞)、自动触发(配置 save "900 1" "300 10" "60 10000",满足任一条件触发 bgsave);- 优点:文件小(二进制压缩存储)、恢复速度快(直接加载内存)、对性能影响小(异步生成);- 缺点:数据丢失风险(丢失最后一次快照后的修改)、fork 子进程开销大(内存大时可能阻塞短时间)。2. AOF 详解:- 实现流程:命令写入(append 到 aof_buf)→ 日志刷盘(配置 appendfsync:always 每次写刷盘、everysec 每秒刷盘、no 操作系统控制)→ 文件重写(避免日志过大,fork 子进程重写日志,保留最终状态命令);- 优点:数据完整性高(everysec 最多丢 1 秒数据,always 不丢数据)、日志可读(文本格式,可手动修改恢复);- 缺点:文件大(未重写时)、恢复速度慢(需重新执行所有命令)、写操作性能略低(刷盘开销)。3. 混合持久化(Redis 4.0+):RDB 快照 + AOF 增量日志,恢复时先加载 RDB,再执行 AOF 日志,兼顾 RDB 恢复速度和 AOF 数据完整性。
📌 加分项:
解释 RDB 的 fork 子进程与写时复制(COW)机制(子进程生成快照时,主进程修改数据会复制页面,不影响快照);AOF 重写的触发条件(自动:aof_current_size > 64MB 且 aof_current_size/aof_base_size > 1.5;手动:bgrewriteaof);对比不同刷盘策略的性能(always 最安全但性能最差,everysec 平衡性能与安全,生产常用);结合高可用架构,主从复制中主库禁用持久化,从库开启 RDB,避免主库性能损耗。
⚠️注意事项:
不要同时禁用 RDB 和 AOF,否则 Redis 重启后数据全丢失;RDB 适合冷备份(定期拷贝 .rdb 文件),AOF 适合热备份;AOF 日志文件需定期重写,避免占用过多磁盘空间;混合持久化需开启配置(aof-use-rdb-preamble yes);Redis 恢复时优先加载 AOF(若开启),再加载 RDB;大内存 Redis(如 10GB+)不建议用 save 命令(阻塞时间过长),仅用 bgsave。
📚【面试题目3:Redis的主从复制原理是什么?如何实现高可用?哨兵(Sentinel)的核心作用?】
回答要点关键字
(1) 主从复制:主库同步数据到从库,基于 RDB 全量同步 + 命令传播增量同步
(2) 复制流程:连接建立→全量同步→增量同步→心跳检测
(3) 高可用方案:主从复制 + 哨兵(自动故障转移)、Redis Cluster(集群分片)
(4) 哨兵核心:监控主从节点、自动故障转移、配置同步
打开详情
🍺基础回答:
Redis 主从复制就是让从库跟着主库走,主库写数据,从库读数据,实现读写分离。流程很简单:从库启动时连接主库,主库生成 RDB 快照发给从库,从库加载快照完成全量同步,之后主库有新的写操作,会实时把命令发给从库(增量同步),从库执行命令保持数据一致。高可用靠主从+哨兵,哨兵就是个监控进程,时刻盯着主从节点,主库挂了就自动从从库里选一个新主库,把其他从库切换到新主库,不用人工干预。
🎉高级扩展版:
- 主从复制详细流程:- 阶段一:连接建立(从库执行 slaveof 主库IP:端口,发送 SYNC 命令,主库创建复制连接);- 阶段二:全量同步(主库执行 bgsave 生成 RDB,发送 RDB 给从库,同时缓存期间的写命令;从库清空内存,加载 RDB,再执行缓存的写命令);- 阶段三:增量同步(主库每执行一个写命令,就通过复制连接发给从库,从库即时执行,保持数据一致);- 阶段四:心跳检测(主从库互相发送 ping 命令,检测连接状态,主库记录从库偏移量,从库反馈已同步偏移量)。2. 增量同步核心:基于复制偏移量(offset)和复制积压缓冲区(主库维护的环形缓冲区,存储最近写命令),从库断线重连后,若偏移量在缓冲区范围内,仅同步缺失命令(增量),否则触发全量同步。3. 哨兵(Sentinel)机制:- 核心作用:监控(检查主从节点是否在线)、故障转移(主库下线后,选举新主库,更新从库和客户端配置)、通知(通过 API 通知客户端主库变更);- 哨兵集群:建议部署 3 个及以上哨兵节点(避免单点故障),哨兵之间通过 gossip 协议交换节点状态,通过投票机制决定故障转移(超过半数哨兵认为主库下线才触发);- 故障转移流程:发现主库下线→选举领头哨兵→从从库中选新主库(优先选偏移量最大、配置优先级最高的从库)→ 通知其他从库切换主库→ 通知客户端。
📌 加分项:
提到主从复制的优化(从库开启 replica-read-only 只读,避免误写;主库关闭持久化,从库开启,减少主库性能损耗);解释复制积压缓冲区的大小配置(repl-backlog-size,默认 1MB,大内存 Redis 建议调大,减少断线后全量同步概率);对比 Redis Cluster 与主从+哨兵的区别(主从+哨兵是单分片,Cluster 是多分片,支持水平扩容);哨兵的主观下线(SDOWN,单个哨兵认为主库下线)与客观下线(ODOWN,半数以上哨兵认为主库下线)机制。
⚠️注意事项:
主从复制不支持写操作负载均衡(仅从库读,主库写),写压力大需配合 Cluster;从库数量不宜过多(建议≤3 个,否则主库复制压力大);哨兵集群必须部署奇数个节点(避免投票平局);主库下线后,哨兵故障转移需要时间(秒级),客户端需支持自动重连新主库;复制连接是 TCP 连接,需保证主从节点网络通畅,避免网络延迟导致数据同步滞后。
📚【面试题目4:Redis的缓存问题(缓存穿透、缓存击穿、缓存雪崩)如何解决?】
回答要点关键字
(1) 核心问题:缓存穿透(查不存在数据)、缓存击穿(热点key失效)、缓存雪崩(大量key同时失效)
(2) 解决方案:穿透(布隆过滤器/空值缓存)、击穿(互斥锁/永不过期)、雪崩(随机过期时间/集群容错)
(3) 关键思路:拦截无效请求、保护热点数据、分散过期压力
(4) 实现细节:布隆过滤器误判率、互斥锁粒度、过期时间范围设计
打开详情
🍺基础回答:
Redis 作为缓存常用,但会遇到三个经典问题。缓存穿透是有人故意查不存在的数据(比如查 id=-1 的用户),缓存和数据库都查不到,一直打数据库;解决的话可以用布隆过滤器提前拦截,或者缓存空值(比如缓存 id=-1 的值为 null,设置短过期时间)。缓存击穿是热点 key 突然失效(比如秒杀商品的缓存过期),大量请求同时打数据库;解决可以给热点 key 加互斥锁(比如 Redis 的 setnx),只有一个线程去数据库查,其他人等结果,或者让热点 key 永不过期。缓存雪崩是大量 key 同一时间过期(比如都设了 24 小时过期),导致数据库压力骤增;解决就是给 key 加随机过期时间(比如 24 小时±1 小时),分散过期时间,还可以搞 Redis 集群,避免单点故障。
🎉高级扩展版:
- 缓存穿透深度解决方案:- 方案一:布隆过滤器(Bloom Filter):在缓存前拦截无效 key,布隆过滤器存储所有有效 key 的哈希值,查询时先查布隆过滤器,不存在则直接返回,存在再查缓存和数据库;注意布隆过滤器有误判率(可通过调整哈希函数个数和位数降低),需定期同步有效 key;- 方案二:空值缓存+短期过期:数据库查询不到数据时,缓存空值(如 "" 或 null),设置 5-10 分钟过期,避免同一无效 key 反复查询数据库;- 方案三:接口层参数校验:过滤明显无效的参数(如 id≤0、手机号格式错误),从源头拦截请求。2. 缓存击穿深度解决方案:- 方案一:互斥锁(分布式锁):热点 key 失效时,客户端通过 Redis 的 setnx(set if not exists)获取锁,获取成功则查询数据库并更新缓存,失败则休眠 100ms 后重试;锁需设置过期时间,避免死锁;- 方案二:热点 key 永不过期:不设置过期时间,通过后台线程定期更新缓存(如每小时更新一次),适合热点数据变动不频繁的场景;- 方案三:缓存预热+过期时间顺延:系统启动时提前加载热点数据到缓存,查询时若发现 key 快过期(如剩余 5 分钟),自动顺延过期时间,避免集中失效。3. 缓存雪崩深度解决方案:- 方案一:随机过期时间:给缓存 key 设置基础过期时间 + 随机偏移(如 24h + 0-3600s),分散过期时间点,避免同时失效;- 方案二:Redis 集群高可用:部署主从+哨兵或 Redis Cluster,避免单个 Redis 节点宕机导致缓存整体不可用;- 方案三:限流降级:缓存失效时,通过网关或接口层限流(如每秒只允许 100 个请求访问数据库),同时降级返回默认数据(如“系统繁忙,请稍后重试”),保护数据库;- 方案四:分层缓存:核心热点数据存在本地缓存(如 Caffeine)+ Redis 缓存,本地缓存无需网络开销,进一步减轻 Redis 和数据库压力。
📌 加分项:
对比布隆过滤器与空值缓存的适用场景(数据量大用布隆过滤器,数据量小用空值缓存);互斥锁的优化(用 Redisson 的 RLock,支持自动续期,避免锁过期);缓存雪崩的另一种场景(Redis 节点宕机),解决方案是 Redis 集群+异地多活;结合实际业务,如秒杀场景用“互斥锁+热点 key 永不过期”,普通业务用“随机过期时间+空值缓存”;提到缓存更新策略(Cache-Aside 模式:读缓存→缓存 miss 读数据库→更新缓存;Write-Through 模式:写数据库→更新缓存)。
⚠️注意事项:
布隆过滤器不能删除 key(删除会影响其他 key 的判断),适合不常删除的场景;互斥锁会增加接口响应时间(重试等待),需控制锁的粒度和过期时间(如锁过期时间 3-5 秒,避免数据库查询超时导致死锁);空值缓存需设置短期过期,避免无效空值占用缓存空间;随机过期时间的偏移范围不宜过大(如 0-1 小时),否则可能导致缓存数据不一致;限流降级需合理设置阈值,避免过度降级影响用户体验。
📚【面试题目5:Redis的分布式锁实现原理是什么?如何保证锁的安全性(防死锁、防误删)?】
回答要点关键字
(1) 实现方式:基于 SET NX EX 命令、Redisson 框架(Lua 脚本+自动续期)
(2) 核心要求:互斥性、安全性(防死锁/防误删)、可用性(集群环境可用)
(3) 关键机制:过期时间(防死锁)、唯一标识(防误删)、Lua 脚本(原子操作)、自动续期(锁续命)
(4) 进阶方案:Redlock 算法(多节点锁)、集群环境适配
打开详情
🍺基础回答:
Redis 分布式锁就是用 Redis 实现跨服务的锁,让多个服务在并发时只能有一个线程操作资源。核心是用 SET NX EX 命令:SET 是设值,NX 表示只有 key 不存在时才成功(互斥),EX 是设过期时间(防死锁,比如 30 秒)。为了防误删,存的 value 要设唯一标识(比如 UUID+线程 ID),释放锁时先判断 value 是不是自己的,再删除。不过手动实现麻烦,实际开发常用 Redisson 框架,它能自动续期(锁快过期还没释放就自动延长时间),还支持集群环境,安全性更高。
🎉高级扩展版:
- 分布式锁核心实现原理:- 基础实现(SET NX EX):命令
SET lock_key unique_value NX EX 30,其中 unique_value 是客户端唯一标识(如 UUID+ThreadId),NX 保证互斥(只有一个客户端能成功),EX 保证锁自动过期(防死锁,避免客户端崩溃后锁一直存在);- 释放锁(Lua 脚本):不能直接用 DEL 命令(可能误删别人的锁),需用 Lua 脚本原子执行“判断 value+删除”:if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end,Lua 脚本能保证操作原子性(Redis 单线程执行);- 进阶实现(Redisson):封装了分布式锁,核心特性: - 自动续期:锁持有线程启动定时任务(默认每 10 秒),将锁过期时间延长到 30 秒(看门狗机制),避免业务执行时间过长导致锁过期; - 支持多种锁类型:可重入锁(RLock)、公平锁、读写锁、红锁(Redlock); - 集群适配:支持 Redis 主从、哨兵、Cluster 集群,主库宕机后哨兵切换新主库,锁仍可用。2. 锁的安全性保障:- 防死锁:设置合理的过期时间(根据业务执行时间预估,如 30-60 秒),Redisson 自动续期;- 防误删:用唯一 value 标识客户端,释放锁时先校验再删除,Lua 脚本保证原子性;- 互斥性:NX 命令保证同一时间只有一个客户端获取锁;- 可用性:集群环境下,主从复制+哨兵故障转移,避免单点 Redis 宕机导致锁不可用;- 防重入:Redisson 可重入锁通过存储锁的持有线程和重入次数实现(如 hash 结构,key 是 lock_key,field 是 unique_value,value 是重入次数)。3. Redlock 算法(多节点锁):为解决单节点 Redis 宕机导致锁丢失的问题,Redlock 要求在多个独立的 Redis 节点(至少 3 个)上获取锁,只有超过半数节点获取成功,才认为锁获取成功,释放锁时需释放所有节点的锁,安全性更高,但性能略低。
📌 加分项:
对比基础实现与 Redisson 的差异(基础实现无自动续期、不支持重入,Redisson 功能完善);解释 Lua 脚本的必要性(避免“判断 value”和“删除 key”的非原子操作,防止中间被其他线程修改);提到分布式锁的性能优化(锁的粒度要细,避免大锁占用时间过长;用 Redis Cluster 分片,分散锁的存储节点);结合实际业务场景,如库存扣减、订单创建用 Redisson 可重入锁,公平锁适合对顺序有要求的场景(如抢票);对比 Redis 分布式锁与 ZooKeeper 分布式锁(Redis 性能高,ZooKeeper 可靠性高,适合不同场景)。
⚠️注意事项:
锁的过期时间要大于业务最大执行时间(如业务最多执行 20 秒,锁过期设 30 秒),避免业务没执行完锁就过期;unique_value 必须唯一,否则可能导致误删别人的锁;避免长时间持有锁(如锁内执行耗时操作,如数据库慢查询),否则会阻塞其他线程;Redisson 自动续期需确保客户端正常运行(客户端崩溃后不会续期,锁会过期释放);Redis 主从复制是异步的,主库宕机后从库未同步锁数据,可能导致多个客户端获取锁(可通过 Redlock 算法解决);分布式锁不能解决所有并发问题(如极端情况下的网络延迟),需结合业务层幂等性设计(如订单号唯一)。
📚【面试题目6:Redis的过期键删除策略是什么?内存淘汰机制有哪些?】
回答要点关键字
(1) 过期删除:惰性删除+定期删除(混合策略)
(2) 内存淘汰:volatile-lru/volatile-ttl/allkeys-lru/volatile-random/allkeys-random/noeviction
(3) 核心目标:平衡内存占用与 CPU 开销,避免内存溢出(OOM)
(4) 实现细节:定期删除的执行频率、LRU 近似实现、淘汰策略选型
打开详情
🍺基础回答:
Redis 过期键删除不是简单的到期就删,而是用“惰性删除+定期删除”的混合策略。惰性删除是只有访问 key 时才判断是否过期,过期就删除,这样不浪费 CPU,但可能占用内存;定期删除是每隔一段时间(比如 100ms)随机抽一批过期 key 检查删除,平衡 CPU 和内存。当 Redis 内存达到最大限制(maxmemory),就会触发内存淘汰机制,按配置的策略删 key 腾空间。常用的淘汰策略是 volatile-lru(删过期 key 中最近最少用的)和 allkeys-lru(删所有 key 中最近最少用的),noeviction 是不淘汰,内存满了就拒绝写操作。
🎉高级扩展版:
- 过期键删除策略详解:- 惰性删除:核心是“延迟删除”,只有当客户端访问过期 key 时,Redis 才检查过期时间,若过期则删除并返回 null;优点是 CPU 开销低(只在访问时处理),缺点是内存浪费(过期 key 未被访问会一直占用内存,称为“内存泄漏”);- 定期删除:Redis 每隔一段时间(配置 hz,默认 10,即每秒 10 次)执行一次过期扫描,流程:随机抽取一定数量(默认 20)的过期 key,删除其中已过期的;若删除的 key 比例超过 25%,则重复扫描,直到比例低于 25% 或扫描次数达到上限(避免阻塞);优点是能主动释放内存,缺点是 CPU 开销随过期 key 数量增加而增大;- 混合策略:结合两者优势,惰性删除避免 CPU 空耗,定期删除避免内存过度占用,是 Redis 的默认策略。2. 内存淘汰机制(maxmemory-policy):- 按淘汰范围分类: - 只淘汰过期 key:volatile-lru(最近最少使用)、volatile-ttl(剩余过期时间最短)、volatile-random(随机淘汰); - 淘汰所有 key:allkeys-lru(最近最少使用)、allkeys-random(随机淘汰); - 不淘汰:noeviction(默认,内存满后拒绝所有写操作,返回 OOM 错误);- 常用策略说明: - volatile-lru:生产环境常用,只淘汰过期 key 中的 LRU 键,不影响未过期 key,平衡内存和业务; - allkeys-lru:适合所有 key 都是缓存场景(无持久化需求),优先淘汰不常用 key; - volatile-ttl:适合希望过期时间短的 key 先被淘汰的场景; - noeviction:适合数据不能丢失的场景(如持久化存储),内存满后拒绝写操作,避免数据丢失。3. LRU 实现:Redis 没有用传统的 LRU 链表(维护成本高),而是用“近似 LRU”:每个 key 维护一个 lru 字段(记录最后访问时间戳),淘汰时随机抽取一批 key(默认 5 个),删除其中 lru 最小的(最近最少使用),通过调整抽样数量(maxmemory-samples)提高近似度(抽样越多越接近真实 LRU,CPU 开销越高)。
📌 加分项:
对比 Redis 4.0+ 的 LFU 淘汰策略(volatile-lfu/allkeys-lfu,基于访问频率,比 LRU 更精准,适合热点数据场景);解释 hz 配置的影响(hz 越大,定期删除越频繁,CPU 开销越高,内存释放越快);内存淘汰与持久化的关系(淘汰的 key 不会同步到 AOF 或 RDB,恢复后不会出现);结合实际配置建议(生产环境设置 maxmemory 为物理内存的 70%-80%,淘汰策略选 volatile-lru,maxmemory-samples 设为 10);提到过期键删除对主从复制的影响(主库删除过期 key 后,会发送 DEL 命令给从库,保证主从数据一致)。
⚠️注意事项:
避免设置 hz 过大(如超过 100),否则会占用过多 CPU 资源;maxmemory 需合理配置(不宜超过物理内存 80%,避免 Redis 占用过多内存导致系统 swap);noeviction 策略需谨慎使用(内存满后拒绝写操作,会影响业务);近似 LRU 可能淘汰非最久未使用的 key,对业务敏感的场景可调整抽样数量;过期 key 若长期不被访问,仍会占用内存,需配合定期清理脚本(如 scan 遍历过期 key 手动删除);LFU 策略需开启(Redis 4.0+),且需调整 lfu-log-factor(访问频率衰减因子)和 lfu-decay-time(衰减时间)适配业务。
📚【面试题目7:Redis Cluster集群的核心原理是什么?分片、槽位分配与故障转移机制?】
回答要点关键字
(1) 核心架构:去中心化集群、主从分片、16384个哈希槽(slot)
(2) 槽位机制:槽位分配(主节点均分)、key哈希映射(CRC16(key)%16384)、槽位迁移
(3) 故障转移:主节点下线检测、从节点选举、槽位重新分配
(4) 关键特性:水平扩容、高可用、客户端路由(智能路由/重定向)
打开详情
🍺基础回答:
Redis Cluster 是 Redis 的分布式集群方案,核心是去中心化,把数据分散到多个节点存储,解决单节点内存和性能瓶颈。它把所有数据分成 16384 个槽位,每个主节点负责一部分槽位(比如 3 个主节点就各分 5000 多个槽)。存数据时,用 CRC16 算法对 key 哈希后取模 16384,得到对应的槽位,再把数据存到负责该槽位的主节点。每个主节点都有从节点,主节点挂了,从节点就顶上(故障转移),保证高可用。客户端访问时,要么直接知道槽位对应的节点(智能路由),要么被节点重定向到正确节点。
🎉高级扩展版:
- 集群核心架构:- 节点类型:主节点(Master)负责槽位和数据存储,从节点(Slave)仅复制主节点数据,不负责槽位(除非故障转移为新主);- 集群规模:至少 3 个主节点(推荐 3 主 3 从),每个主节点可配置 1-多个从节点;- 通信机制:节点间通过 Gossip 协议交换信息(如节点状态、槽位分配),默认端口 16379(数据端口)和 16380(集群总线端口)。2. 槽位分配与数据路由:- 槽位分配:集群创建后需手动分配槽位(如
cluster addslots),主节点均分 16384 个槽,只有主节点有槽位所有权;- 数据映射:key → CRC16(key) → 模 16384 → 槽位 → 对应主节点;批量操作(如 MSET、DEL 多个 key)需保证所有 key 在同一槽位(可通过 hash tag 实现,如{user_id}:order_id,仅对 user_id 哈希);- 客户端路由:- 智能路由:客户端首次连接时获取槽位分配表,后续直接访问对应节点;- 重定向:若客户端访问错误节点(如槽位已迁移),节点返回MOVED或ASK指令,客户端重新发起请求。3. 故障转移机制:- 故障检测:节点通过 ping/pong 消息检测其他节点状态,若主节点超时未响应,标记为疑似下线(PFAIL);超过半数主节点标记该主节点为下线(FAIL),触发故障转移;- 从节点选举:下线主节点的所有从节点参与选举,通过cluster failover机制,优先选择复制偏移量最大(数据最完整)的从节点升级为新主节点;- 槽位迁移:新主节点接管原主节点的所有槽位,节点间通过 Gossip 协议同步新的槽位分配表,客户端更新本地路由表。
📌 加分项:
解释 hash tag 的作用(强制多个 key 映射到同一槽位,支持批量操作);对比 Redis Cluster 与主从+哨兵的区别(主从+哨兵是单分片,Cluster 是多分片,支持水平扩容);槽位迁移的实现(cluster migrate 命令,迁移过程中数据可读写,采用“先迁移后切换”策略);集群扩容流程(新增主节点→迁移槽位→新增从节点→复制数据);提到集群的一致性保证(最终一致性,主节点故障转移期间可能出现短暂数据不一致)。
⚠️注意事项:
集群中所有节点必须开启 cluster-enabled yes,否则无法加入集群;槽位必须全部分配(16384 个槽位都有主节点负责),否则集群状态为 fail;批量操作的 key 若不在同一槽位,会报错(需用 hash tag 或拆分操作);从节点仅复制数据,不提供读服务(需手动配置 replica-read-only no,但不推荐,可能导致数据不一致);故障转移期间会有短暂的写不可用(秒级),需业务层适配(如重试机制);集群不支持多数据库(默认仅 db0),与单机 Redis 不同;扩容时迁移槽位会占用网络和 CPU 资源,建议在低峰期操作。
📚【面试题目8:Redis与数据库(MySQL)如何保证数据一致性?常见的缓存更新策略有哪些?】
回答要点关键字
(1) 核心策略:Cache-Aside(旁路缓存)、Write-Through(写透)、Write-Behind(写回)
(2) 一致性保障:先更数据库再删缓存、延迟双删、防缓存穿透/击穿/雪崩
(3) 适用场景:读多写少(Cache-Aside)、数据一致性要求高(Write-Through)
(4) 关键问题:缓存与数据库更新顺序、并发更新冲突、延迟一致性
打开详情
🍺基础回答:
Redis 作为缓存,和 MySQL 保证数据一致性的核心是“更新数据库和缓存的顺序要对”,常用的是 Cache-Aside 策略(旁路缓存)。读数据时先查 Redis,有就直接返回,没有就查 MySQL,然后把数据写到 Redis 再返回;写数据时先更 MySQL,成功后再删 Redis(不是更新 Redis),这样下次读就会从 MySQL 加载最新数据到 Redis。还有延迟双删策略,就是写数据后先删一次 Redis,过几百毫秒再删一次,防止并发场景下缓存和数据库不一致。另外,还要配合之前说的缓存穿透、击穿、雪崩的解决方案,避免异常情况导致的数据问题。
🎉高级扩展版:
- 三大缓存更新策略详解:- Cache-Aside(旁路缓存,最常用):- 读流程:Redis 命中→返回;Redis 未命中→查 MySQL→写入 Redis→返回;- 写流程:更新 MySQL→删除 Redis(而非更新 Redis,避免脏写);- 优点:实现简单、性能高(无需缓存和数据库强绑定);- 缺点:并发写可能导致不一致(如两个线程同时写,A 更 MySQL→B 更 MySQL→B 删 Redis→A 删 Redis,无问题;但 A 更 MySQL→A 删 Redis→B 查 Redis 未命中→查 MySQL(A 更后的数据)→写 Redis→B 更 MySQL→B 删 Redis,也无问题,核心是“先更库再删缓存”)。- Write-Through(写透):- 流程:写操作→更新缓存→缓存同步更新 MySQL;- 优点:数据一致性高(缓存和数据库同步);- 缺点:性能低(每次写都要操作两次,缓存和数据库)、实现复杂(需缓存支持同步写库);- 适用场景:数据一致性要求极高、写操作少的场景。- Write-Behind(写回):- 流程:写操作→更新缓存→缓存异步批量更新 MySQL;- 优点:写性能高(仅操作缓存,异步刷库);- 缺点:数据一致性差(缓存未刷库时宕机,数据丢失)、实现复杂(需缓存支持异步刷库和失败重试);- 适用场景:写操作频繁、对数据一致性要求不高的场景(如日志存储)。2. 并发场景一致性保障:- 延迟双删:解决“写库删缓存”后,有线程已读旧缓存的问题,流程:更新 MySQL→删除 Redis→休眠 500ms(根据业务调整)→再次删除 Redis;休眠时间需大于缓存最大过期时间或业务最大查询耗时,确保旧缓存已被读取的线程完成操作。- 防并发更新冲突:用分布式锁(如 Redis 锁)包裹“更新 MySQL+删除 Redis”操作,确保同一时间只有一个线程更新数据。- 缓存过期时间:给所有缓存 key 设置过期时间,即使出现不一致,过期后也会自动加载最新数据,兜底保障。3. 特殊场景处理:- 缓存穿透:布隆过滤器+空值缓存;- 缓存击穿:互斥锁+热点 key 永不过期;- 缓存雪崩:随机过期时间+集群高可用。
📌 加分项:
对比“先删缓存再更库”与“先更库再删缓存”的差异(先删缓存可能导致脏读:A 删缓存→B 查 MySQL→写缓存→A 更 MySQL,B 读的是旧数据;先更库再删缓存无此问题);提到 Redis 缓存更新的异步方案(如 MySQL binlog 同步到 Redis,用 Canal 监听 binlog,触发 Redis 缓存更新/删除,适合高并发场景);结合实际业务,如电商商品详情用 Cache-Aside+延迟双删,金融交易用 Write-Through+分布式锁;解释缓存过期时间的兜底作用(即使一致性策略失效,过期后也能恢复一致);提到读写分离场景的一致性(主库写→删 Redis,从库读→查 Redis,需确保主从同步延迟小于缓存过期时间)。
⚠️注意事项:
“先更库再删缓存”是核心原则,切勿颠倒顺序;延迟双删的休眠时间需合理配置(过短可能无效,过长影响性能);binlog 同步方案需处理 binlog 延迟和重复消费问题;缓存和数据库的更新操作需保证原子性(如用事务包裹 MySQL 更新,分布式锁包裹整体操作);避免缓存和数据库强耦合(如不要在 MySQL 触发器中操作 Redis,维护成本高);高并发场景下,即使采用一致性策略,也可能出现短暂的不一致(如缓存删除后、新数据写入前的间隙),需业务层支持幂等性(如订单号唯一、数据版本号)。
38. 【面试题目】缓存击穿、缓存雪崩、缓存穿透的成因和解决方案?
回答要点关键字
(1) 核心差异:击穿(热点key失效)、雪崩(大量key同时失效)、穿透(查询不存在key)
(2) 成因:key过期策略不当、缓存集群故障、查询无意义key
(3) 解决方案:击穿(互斥锁/永不过期)、雪崩(过期时间随机化/集群容灾)、穿透(布隆过滤器/空值缓存)
(4) 核心原则:预防为主、兜底为辅、平衡性能与一致性
打开详情
🍺基础回答:
这三个都是缓存常见问题,成因和解决办法不一样。缓存击穿是某个热点key突然过期了,大量请求直接打数据库,比如秒杀商品的缓存过期。解决的话,要么给热点key设置永不过期,要么用互斥锁,第一个请求去查数据库时加锁,其他请求等着,查完更新缓存再释放锁。缓存雪崩是很多key在同一时间过期,或者缓存集群挂了,所有请求都冲数据库。解决要给key的过期时间加随机值,避免同时过期,还要给缓存集群做高可用(比如Redis哨兵/集群),再加降级限流。缓存穿透是查的key根本不存在,缓存和数据库都没数据,请求一直打数据库,比如恶意刷不存在的用户ID。解决可以用布隆过滤器提前过滤不存在的key,或者缓存空值(比如缓存一个空字符串,设置短过期时间)。
🎉高级扩展版:
-
详细成因与解决方案:
一、缓存击穿
- 成因:热点key(高并发查询的key)过期/被删除,大量请求瞬间穿透到数据库,导致数据库压力突增。
- 进阶解决方案:
- 热点key永不过期:业务层面标记热点key,缓存中不设置过期时间,通过后台异步线程定期更新缓存(避免缓存脏数据);
- 互斥锁优化:用Redis的setnx加锁(设置过期时间防死锁),第一个请求获取锁后查库更新缓存,其他请求自旋等待(超时直接返回降级结果);
- 熔断降级:热点key失效时,直接返回默认数据(如“当前商品火爆,请稍后重试”),避免数据库压垮。
二、缓存雪崩
- 成因:1. 大量key集中设置相同过期时间(如凌晨12点过期);2. 缓存集群故障(如Redis主从切换、集群宕机);3. 缓存服务过载崩溃。
- 进阶解决方案:
- 过期时间随机化:给每个key的过期时间加5-30分钟随机值,打散过期峰值;
- 缓存集群高可用:Redis哨兵模式(主从切换)、Redis Cluster(分片存储),避免单点故障;
- 多级缓存:本地缓存(Caffeine)+ 分布式缓存(Redis),缓存集群故障时本地缓存兜底;
- 限流降级:通过Sentinel限制穿透到数据库的请求QPS,核心接口优先保障。
三、缓存穿透
- 成因:1. 恶意攻击(如刷不存在的用户ID、商品ID);2. 业务逻辑误查(如查询已删除的数据),导致请求穿透缓存直接查库,且数据库也无数据。
- 进阶解决方案:
- 布隆过滤器:将所有存在的key(如商品ID、用户ID)提前存入布隆过滤器,请求先过过滤器,不存在的key直接拦截;
- 空值缓存优化:数据库查询为空时,缓存空值(如""、null)并设置短过期时间(5-10分钟),避免重复穿透;
- 接口校验:业务层增加参数校验(如用户ID格式校验、商品ID范围校验),拦截无效请求;
- 限流黑名单:识别恶意IP/用户,加入黑名单,限制其请求频率。
📌 加分项:
- 组合方案:布隆过滤器(防穿透)+ 随机过期时间(防雪崩)+ 互斥锁(防击穿)+ 多级缓存(兜底),覆盖所有场景;
- 监控告警:通过Prometheus监控缓存命中率、穿透率、数据库QPS突增,异常时及时告警;
- 业务适配:核心业务(支付、下单)的热点key用“永不过期+异步更新”,非核心业务(评论、推荐)用“空值缓存+短过期”,平衡性能与一致性。
⚠️注意事项:
- 互斥锁可能导致并发性能下降,需控制锁超时时间(如1-3秒),避免死锁;
- 布隆过滤器存在误判率,需合理设置bit数组大小和哈希函数个数,避免拦截有效key;
- 空值缓存需设置短过期时间,避免业务数据新增后(如新增商品)缓存空值导致查询失败;
- 缓存雪崩时的降级方案需提前演练,避免降级后影响核心业务可用性。
39. 【面试题目】在设计 Redis 的 Key 和 Value 时,有哪些需要注意的原则?
回答要点关键字
(1) Key 设计:命名规范、唯一性、过期时间、粒度适中
(2) Value 设计:序列化选型、大小限制、结构优化、避免冗余
(3) 性能原则:减少网络传输、提升缓存命中率、降低内存占用
(4) 运维原则:可读性强、便于监控、兼容扩容(如Redis Cluster)
打开详情
🍺基础回答:
设计Redis的Key和Value,核心是让缓存好用又高效。Key方面,要命名规范,比如用“业务:模块:id”的格式(如ecommerce:product:1001),一眼能看懂;要保证唯一,不能重复;过期时间根据业务设,热点数据可以永不过期,普通数据设合理时间,避免占内存。Value方面,别存太大的对象(比如超过100KB),不然序列化和传输都慢;尽量用紧凑的序列化方式(比如Protobuf比JSON省空间);结构别太复杂,能拆分就拆分,比如用户信息别存在一个大Hash里,按需拆分小Hash或String。
🎉高级扩展版:
- Key 设计核心原则:
(1) 命名规范:统一前缀+模块+唯一标识,用冒号分隔(如“user:center:info:10086”),避免中文/特殊字符(增加编码开销);
(2) 唯一性保障:结合业务主键(如用户ID、订单号),避免不同业务复用Key(如“product:1001”和“order:1001”无冲突);
(3) 过期时间合理:- 热点数据(如秒杀商品):永不过期,后台异步更新;
- 普通数据(如用户会话):设置短期过期(30分钟-24小时);
- 低频数据(如历史订单):设置长期过期(7-30天)或惰性删除;
(4) 粒度适中:避免过细(如“user:10086:name”“user:10086:age”,大量小Key浪费内存),避免过粗(如“user:all”存储所有用户信息,更新困难)。
- Value 设计核心原则:
(1) 序列化选型:- 高性能场景:Protobuf/Kryo(二进制序列化,空间小、速度快);
- 可读性场景:JSON(文本序列化,便于调试,适合非高频场景);
- 避免Java默认序列化(Serializable,空间大、兼容性差);
(2) 大小限制:单Value建议≤100KB,超过1MB需拆分(如大文件拆分成多个小Value,用“key:part1”“key:part2”命名);
(3) 结构优化: - 复杂对象用Hash存储(如用户信息:hmset user:10086 name "张三" age 20),支持部分字段更新;
- 列表数据用List/ZSet(如商品评论、排行榜),避免用String存储JSON数组(更新麻烦);
- 避免嵌套过深(如JSON嵌套多层对象),增加序列化/反序列化开销;
(4) 避免冗余:Value仅存储必要字段(如用户缓存无需存储历史订单详情),关联数据通过Key关联(如“order:1001”关联“user:10086”)。
- 性能与运维优化:
- 减少Key数量:用Hash合并同类小Key(如“user:10086”的所有字段存一个Hash,而非多个String);
- 兼容Redis Cluster:Key的哈希标签(如“{user:10086}:info”)确保相关Key落在同一分片,避免跨分片查询;
- 便于监控:Key命名包含业务标识,便于通过工具(如RedisInsight)统计各业务缓存占用。
📌 加分项:
- 缓存命中率优化:Key的粒度与查询场景匹配(如查询商品详情时,Key为“product:1001:detail”,包含所有所需字段,避免多次查询);
- 内存优化:Value使用压缩算法(如Redis的LZ4压缩),减少大Value的内存占用;小Value用String存储(比Hash更省内存,Hash有字典 overhead);
- 兼容性设计:Key和Value避免使用Redis关键字(如“KEYS”“MGET”),序列化方式确保跨语言兼容(如Protobuf定义统一协议)。
⚠️注意事项:
- 避免大量小Key(如百万级“user:xxx:name”):Redis存储小Key的元数据开销大,优先用Hash合并;
- 过期时间设置过短会导致缓存命中率低,过长会导致内存浪费和数据脏读,需结合业务更新频率调整;
- 大Value(>1MB)会导致Redis阻塞(序列化/传输耗时),影响并发性能,需拆分或避免存储;
- 不要用Redis存储敏感数据(如密码、token),若必须存储需加密(如AES加密Value),避免泄露。
40 .📚 MySQL 主从读写分离:主从延迟与一致性问题解决方案
1. 核心问题本质
- 主从延迟:主库执行写操作(增删改)后,binlog 同步到从库并应用的时间差(通常毫秒级,极端情况秒级/分钟级)。
- 一致性问题:读写分离下,写主库后立即读从库,因延迟导致读取到旧数据(脏读),或从库未同步完成时主从数据不一致。
2. 主从延迟根源
| 延迟原因 | 常见场景 |
|---|---|
| 网络传输延迟 | 主从跨机房、网络带宽瓶颈导致 binlog 传输慢 |
| 从库压力过大 | 从库承担过多读请求、SQL 慢查询堆积 |
| 大事务/批量操作 | 主库执行大批量插入/更新,binlog 体积大 |
| 从库 SQL 线程单线程(5.6前) | 单线程应用 binlog,无法并行处理 |
3. 一致性解决方案(按优先级排序)
方案1:强制读主(核心场景兜底)
- 逻辑:对强一致性要求的读操作(如刚下单后查订单状态、支付后查余额),直接路由到主库。
- 实现:
- 代码层面:通过注解/ThreadLocal 标记强一致性请求,路由规则跳过从库。
- 中间件层面:Sharding-JDBC/MyCat 配置「读写分离策略」,指定某些表/语句强制读主。
- 适用场景:用户个人中心、订单详情、支付结果查询等核心场景(占比低,不影响主库压力)。
方案2:延时读取(非核心场景妥协)
- 逻辑:写主库后,延迟一段时间再读从库,预留同步时间。
- 实现:
- 业务层面:非核心场景(如商品列表、历史订单列表),写操作后延迟 50~500ms 再查询(根据实际延迟调整)。
- 技术层面:通过缓存暂存最新数据,延迟后失效缓存,引导读从库。
- 适用场景:对实时性要求低的场景,避免强制读主导致主库压力上升。
方案3:主从同步状态校验(精准控制)
- 逻辑:读从库前,先校验主从同步进度,确认从库已同步目标写操作后再读取。
- 实现方式:
- binlog 位点校验:
- 主库写操作后记录当前 binlog 文件名+位点(
show master status)。 - 读从库前,查询从库同步位点(
show slave status中的Master_Log_File/Read_Master_Log_Pos)。 - 若从库位点 ≥ 主库位点,允许读从库;否则等待重试或降级读主。
- 主库写操作后记录当前 binlog 文件名+位点(
- GTID 校验(5.7+推荐):
- 主库写操作后记录当前 GTID(
select @@GLOBAL.gtid_executed)。 - 从库查询已执行 GTID(
select @@GLOBAL.gtid_executed),确认包含目标 GTID 后再读取。
- 主库写操作后记录当前 GTID(
- binlog 位点校验:
- 适用场景:实时性要求中等,且不想过度占用主库资源的场景(如商品库存查询)。
方案4:优化主从同步性能(减少延迟)
- 缩短延迟时长,从根源降低一致性问题概率:
- 主库优化:
- 避免大事务,拆分批量操作(如分批次插入 1000 条数据)。
- 开启 binlog 并行写入(
sync_binlog=1结合innodb_flush_log_at_trx_commit=1,平衡一致性与性能)。
- 从库优化:
- 5.6+ 开启并行复制(
slave_parallel_workers=N,N=CPU 核心数),提升 binlog 应用效率。 - 从库禁用 binlog(仅作为读库),减少 IO 开销;配置更大的
innodb_buffer_pool_size。
- 5.6+ 开启并行复制(
- 架构优化:
- 主从同机房部署,减少网络延迟;必要时使用专线。
- 避免从库承担非查询任务(如备份、统计分析),单独部署从库用于专项任务。
- 主库优化:
方案5:读写分离中间件增强(自动化控制)
- 利用中间件自动处理一致性,减少业务代码侵入:
- Sharding-JDBC:支持「Hint 强制路由」「延迟路由」「主从同步状态感知」。
- MyCat:配置「主从延迟阈值」,超过阈值自动路由到主库。
- ProxySQL:实时监控主从延迟,延迟超标时自动切主读。
- 优势:业务代码无需关注一致性逻辑,由中间件统一管控。
方案6:最终一致性方案(高并发场景)
- 若业务允许「最终一致」,采用「写主库+缓存+异步同步」架构:
- 写操作:写主库 + 更新缓存(设置短期过期时间)。
- 读操作:先读缓存,缓存命中则返回;缓存未命中时,读从库并更新缓存。
- 兜底:缓存过期后自动从从库加载最新数据,确保最终一致性。
- 适用场景:高并发读场景(如商品详情页、首页推荐),允许短暂数据不一致。
4. 生产环境最佳实践组合
- 核心场景:强制读主(如订单、支付、用户中心)→ 保证强一致性。
- 中等实时性场景:主从同步状态校验(GTID 方式)→ 平衡一致性与性能。
- 低实时性场景:延时读取 + 缓存 → 降低主库压力。
- 基础保障:优化主从同步性能(并行复制、同机房部署)→ 减少延迟发生概率。
- 架构支撑:使用 Sharding-JDBC/ProxySQL 中间件 → 自动化管控,减少研发成本。
5. 避坑指南
- 避免过度依赖「延时读取」:延迟时间难以精准控制,极端延迟下仍会出现不一致。
- 大事务是主从延迟的重灾区:必须拆分,否则同步延迟可能达分钟级。
- 从库并行复制需合理配置:
slave_parallel_workers并非越大越好,建议等于 CPU 核心数(避免上下文切换开销)。 - 跨机房主从需谨慎:网络延迟不可控,建议优先同机房,跨机房仅作为灾备。