目录
[1. Redis事务的原子性](#1. Redis事务的原子性)
[2. 与MySQL事务的区别](#2. 与MySQL事务的区别)
[1. 主从复制原理](#1. 主从复制原理)
[2. 哨兵模式故障转移流程](#2. 哨兵模式故障转移流程)
[3. 客户端感知故障转移](#3. 客户端感知故障转移)
一,基础
-
Redis的5种基础数据类型及使用场景?Zset底层实现原理?
-
String(字符串)
-
场景 :缓存简单键值对(如用户会话信息、计数器)、分布式锁(如
SETNX
)、二进制数据存储(如图片Base64)。 -
特点 :支持原子操作(如
INCR
、DECR
),最大存储512MB。
-
-
List(列表)
-
场景 :消息队列(
LPUSH/RPOP
实现生产者-消费者)、最新消息排行(如微博时间线)、阻塞式任务调度。 -
特点:双向链表,支持按索引操作(但时间复杂度高)。
-
-
Hash(哈希表)
-
场景 :存储对象(如用户信息字段:
HSET user:1 name "Alice"
)、聚合数据(如购物车商品信息)。 -
特点:支持单字段读写,内存优化(底层为ziplist或hashtable)。
-
-
Set(集合)
-
场景 :去重数据(如用户标签)、共同好友(
SINTER
求交集)、随机抽奖(SRANDMEMBER
)。 -
特点:无序、自动去重,支持集合运算(并/交/差)。
-
-
Zset(有序集合)
-
场景:排行榜(按分数排序)、延迟队列(以时间戳为score)、带权重的任务调度。
-
特点 :元素唯一但score可重复,支持范围查询(
ZRANGEBYSCORE
)。
-
-
Zset的底层由 跳跃表(Skip List) 和 字典(Dict) 组成,或在小数据量时使用 listpack(替代旧版的ziplist):
-
跳跃表:
-
多层链表结构,高层链表作为"快速通道",支持O(log N)的查询、插入、删除。
-
每个节点包含
score
和value
,按score排序,支持高效范围操作(如ZRANGE
)。
-
-
字典:
- 维护
value -> score
的映射,实现O(1)的单元素查询。
- 维护
-
内存优化:
- 元素数≤
zset-max-listpack-entries
且元素大小≤zset-max-listpack-value
时,使用listpack存储。
- 元素数≤
-
-
-
Redis持久化机制RDB和AOF的区别?如何选择?
特性 RDB AOF 原理 定时生成全量数据快照(二进制文件) 记录所有写命令(追加日志文件) 文件体积 小(压缩二进制) 大(文本命令,需重写优化) 恢复速度 快(直接加载内存) 慢(重放命令) 数据安全 可能丢失最后一次快照后的数据 可配置 fsync
策略(无/秒级/每次写)资源消耗 高( fork
子进程内存开销)低(追加日志,重写时才有 fork
开销)-
选择策略:
-
高数据安全 :选择AOF(
appendfsync everysec
),容忍秒级丢失。 -
快速恢复/备份:选择RDB(如每日备份)。
-
混合模式 :Redis 4.0+支持
RDB+AOF
,AOF记录增量,RDB做全量备份。
-
-
-
如何用Redis实现分布式锁?需要注意哪些问题?
-
实现步骤:
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"));
注意事项:
-
锁过期时间:需预估业务耗时,建议设置自动续期(如Redisson的看门狗)。
-
唯一标识:value使用唯一值(如UUID+线程ID),避免误删其他客户端的锁。
-
优化方案:
-
使用Redisson库,内置看门狗、可重入锁、红锁(RedLock)实现。
-
对锁操作添加重试机制(如指数退避)。
-
原子性操作 :加锁(
SET NX PX
)和释放锁(Lua脚本)必须原子化。 -
集群问题:
-
主从异步复制:主节点宕机可能导致锁丢失,可考虑RedLock算法(需部署多实例)。
-
RedLock争议:依赖系统时钟一致性,需权衡CAP。
-
-
二,经典
-
什么是缓存穿透/雪崩/击穿?分别给出解决方案
-
缓存穿透:
-
问题 :大量请求查询数据库中不存在的数据 (如非法ID),绕过缓存直接压垮数据库。
解决方案:-
布隆过滤器(Bloom Filter):在缓存层前加布隆过滤器,预存所有合法键的哈希值,拦截非法请求。
- 特点:存在一定误判率(可能将非法请求误判为合法),需权衡内存占用。
-
缓存空值 :对查询结果为
null
的键,缓存空值并设置短过期时间(如30秒)。- 注意:需定期清理无效空值,避免内存浪费。
-
-
缓存雪崩:
-
问题 :大量缓存同时过期 或缓存服务宕机,导致请求全部涌向数据库。
解决方案:-
随机过期时间 :为缓存设置基础过期时间 + 随机偏移值(如
TTL = 24h + random(0, 1h)
)。 -
永不过期 + 异步更新:
-
缓存不设过期时间,通过后台线程定期更新。
-
结合双缓存策略:主缓存永不过期,备份缓存设置过期时间作为兜底。
-
-
熔断降级:使用Hystrix等工具,在数据库压力过大时直接返回默认值或限流。
-
-
缓存击穿:
-
问题 :热点数据过期 时,高并发请求瞬间击穿缓存,直接访问数据库。
解决方案: -
互斥锁(Mutex Lock):
-
当缓存失效时,通过分布式锁(如Redis的
SETNX
)控制只有一个线程重建缓存,其他线程等待。 -
示例代码:
javapublic 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; }
- 逻辑过期:缓存永不过期,但存储的数据中增加逻辑过期时间字段,由业务代码判断是否需要异步更新。
-
-
-
-
Redis事务的原子性如何理解?与MySQL事务的区别?
1.1. Redis事务的原子性
-
-
实现方式 :通过
MULTI
(开启事务)、EXEC
(提交事务)、DISCARD
(取消事务)命令实现。 -
原子性含义:
-
命令队列的原子执行:事务中的命令会被序列化并按顺序执行,不会被其他客户端命令打断。
-
不支持回滚:若事务中某条命令执行失败(如语法错误),其他命令仍会继续执行,无回滚机制。
-
示例:
javaMULTI SET key1 "A" # 入队 INCR key2 # 入队(若key2非数值类型,执行时报错) EXEC # 提交事务,第二条命令执行失败,但第一条仍生效
-
2. 与MySQL事务的区别
特性 Redis MySQL 原子性 仅保证命令队列的批量执行,无回滚 支持ACID,失败时自动回滚 隔离性 无隔离级别,事务执行期间可能被其他客户端修改数据 支持多隔离级别(如读已提交、可重复读) 持久性 依赖持久化机制(RDB/AOF) 依赖Redo Log和Binlog保证持久性 使用场景 简单批量操作(如批量SET) 复杂业务逻辑(如转账、订单处理) -
-
-
Redis主从复制原理?哨兵模式如何实现故障转移?
1.1. 主从复制原理
-
核心流程:
-
全量同步(首次连接):
-
从节点发送
PSYNC ? -1
命令请求同步。 -
主节点执行
BGSAVE
生成RDB文件并发送给从节点,同时缓存期间的写命令到复制缓冲区。 -
从节点加载RDB后,主节点发送缓冲区中的写命令使其追上最新状态。
-
-
增量同步(断线重连):
-
从节点发送
PSYNC <runid> <offset>
,主节点根据offset
从复制缓冲区发送增量数据。 -
若复制缓冲区数据丢失(如缓冲区溢出),触发全量同步。
-
-
-
关键配置:
repl-backlog-size
:调整复制缓冲区大小,避免频繁全量同步。
2. 哨兵模式故障转移流程
哨兵(Sentinel)是Redis的高可用解决方案,负责监控、通知和自动故障转移:
5. 监控:- 每个哨兵节点定期向主节点、从节点和其他哨兵发送
PING
命令检测存活状态。
-
主观下线(SDOWN):
- 若哨兵在
down-after-milliseconds
时间内未收到主节点的有效响应,标记其为主观下线。
- 若哨兵在
-
客观下线(ODOWN):
- 当超过半数哨兵认为主节点主观下线,则标记为客观下线。
-
选举Leader哨兵:
- 通过Raft算法选举一个Leader哨兵来执行故障转移。
-
故障转移:
-
Leader哨兵从从节点列表中选出一个新的主节点(基于优先级、复制偏移量等)。
-
向其他从节点发送
SLAVEOF
命令,使其复制新主节点。 -
更新客户端配置,通知其连接新主节点。
-
##### **3. 客户端感知故障转移**
- 客户端通过订阅哨兵的
+switch-master
事件获取新主节点地址,或通过哨兵API动态查询。
-
三,高级
-
Redis Cluster集群模式的数据分片原理?如何实现动态扩容?
-
数据分片原理
Redis Cluster采用哈希槽(Hash Slot)机制进行数据分片,共有16384个槽位。每个键通过
CRC16(key)
计算哈希值,再对16384取模确定所属槽位。集群中的每个节点负责一部分槽,数据分布由槽分配决定。客户端请求时,若连接节点不负责该槽,会返回MOVED
重定向错误,引导客户端访问正确节点。节点间通过Gossip协议交换集群状态,维护槽分配信息的一致性。动态扩容实现
-
添加新节点 :使用
CLUSTER MEET
将新节点加入集群。 -
迁移槽数据:
-
使用
redis-cli --cluster reshard
触发槽重新分片。 -
源节点将槽内键逐个迁移到目标节点,期间对正在迁移的键的请求,源节点返回
ASK
重定向,客户端需重试到目标节点。
-
-
更新槽分配:迁移完成后,槽归属信息通过Gossip协议同步到整个集群。
-
副本扩展 :新增副本节点通过
CLUSTER REPLICATE
同步主节点数据。
-
-
Redis内存淘汰策略有哪些?如何设计大Key热Key的解决方案?
-
内存淘汰策略 (通过
maxmemory-policy
配置) -
noeviction:拒绝写入新数据(默认)。
-
volatile-ttl:淘汰过期时间最近的键。
-
LRU/LFU策略:
-
allkeys-lru
/volatile-lru
:基于最近最少使用。 -
allkeys-lfu
/volatile-lfu
:基于访问频率(4.0+)。
-
-
随机淘汰 :
allkeys-random
/volatile-random
。 -
大Key解决方案
-
拆分:如将大Hash拆分为多个小Hash,通过键名后缀分片。
-
异步删除 :使用
UNLINK
代替DEL
,非阻塞释放内存。 -
压缩:对Value进行压缩(如GZIP),但会增加CPU开销。
-
监控 :通过
redis-cli --bigkeys
定期扫描大Key。 -
热Key解决方案
-
多级缓存:本地缓存(如Guava)减少Redis访问。
-
读写分离:通过副本节点分散读压力。
-
分片打散 :使用Hash Tag(如
{user1000}.profile
)强制热Key分布到同一节点,并增加副本。 -
Proxy支持:如Twemproxy或Codis自动分散请求。
-
-
Redis多线程模型演进(从单线程到IO多线程)?管道技术原理?
-
多线程演进
-
单线程模型(6.0前):单线程处理所有网络I/O、命令解析和执行,避免锁竞争,但无法利用多核CPU。
-
I/O多线程(6.0+):
-
多线程网络I/O :主线程负责接收连接,I/O线程处理读写(配置
io-threads
启用)。 -
单线程命令执行:命令执行仍为单线程,保证原子性。
-
-
性能提升:高并发场景下,网络I/O瓶颈缓解,吞吐量显著提升。
-
管道技术(Pipeline)原理
-
批量发送:客户端将多个命令打包发送,减少网络往返次数(RTT)。
-
服务端顺序执行:服务器按顺序依次执行命令,全部完成后一次性返回结果。
-
优势:适用于批量操作(如批量写入),提升吞吐量。
-
注意点:不保证原子性,且返回结果需客户端按序解析。
-