Redis相关八股
Redis为什么快
1. 基于内存存储(In-Memory)
- Redis 将所有数据存储在 内存 中,读写操作直接访问内存,避免了磁盘 I/O 的高延迟。
- 内存访问速度比磁盘快几个数量级(纳秒 vs 毫秒)。
2. 单线程模型(Single-Threaded)
- Redis 核心服务采用单线程处理所有客户端请求(从 6.0 开始,网络 I/O 和部分操作引入了多线程,但命令执行仍是单线程)。
- 避免了多线程的上下文切换和锁竞争开销。
- 基于 I/O 多路复用(如 epoll、kqueue)实现高并发,一个线程可处理成千上万个连接。
3. 高效的数据结构
- Redis 底层使用 C 语言实现,并针对每种数据类型优化了数据结构:
String:动态字符串(SDS)List:双端链表或压缩列表(ziplist)Hash:哈希表或压缩列表Set:整数集合或哈希表ZSet:跳跃表(Skip List)+ 哈希表
- 这些结构在时间复杂度和内存使用上都做了极致优化。
4. 非阻塞 I/O 与事件驱动
- 使用 Reactor 模式,通过事件循环处理连接、读写、定时任务等。
- 所有操作都是非阻塞的,保证了高吞吐和低延迟。
5. 避免复杂操作
- Redis 命令设计简单,大多数操作时间复杂度为 O(1) 或 O(log N)。
- 不支持表连接(JOIN)等复杂查询,保持核心逻辑轻量。
✅ 总结:内存 + 单线程 + 高效数据结构 + I/O 多路复用 = 极致性能
Redis的八种数据结构分别讲一下
Redis 不是只有“八种”数据结构,实际上它有 5 种基础数据结构,以及基于这些基础结构实现的 3 种高级数据结构,合起来常被说成“8 种”。
| 类型 | 特点 | 底层结构 | 典型用途 |
|---|---|---|---|
| String | 字符串/数字 | SDS | 缓存、计数 |
| Hash | 字段-值映射 | ziplist / hashtable | 对象存储 |
| List | 有序可重复 | ziplist / linkedlist | 消息队列 |
| Set | 无序不重复 | intset / dict | 去重、集合运算 |
| ZSet | 有序不重复 | skiplist + dict | 排行榜 |
| Bitmap | 位操作 | String | 签到、统计 |
| HyperLogLog | 基数估算 | - | UV统计 |
| Geospatial | 地理位置 | ZSet | 附近的人 |
店铺营业状态设置(day05)-CSDN博客
什么是布隆过滤器
布隆过滤器(Bloom Filter) 是一种基于概率的数据结构,用于高效判断一个元素是否可能存在于一个集合中。
核心特点是:空间效率高、查询速度快,但存在一定的误判率(False Positive),且不支持删除操作(标准版本)。底层实现原理
布隆过滤器由两个核心组件构成:
1. 一个长度为 m 的位数组(Bit Array)
- 初始时所有位都置为 0。
2. k 个独立的哈希函数
- 每个哈希函数能将输入元素映射到位数组的一个位置(0 到 m-1)。
1. 添加元素
- 对元素使用 k 个哈希函数,得到 k 个位置。
- 将位数组中这 k 个位置都置为 1。
2. 查询元素
- 同样计算出 k 个位置。
- 检查这些位置是否全部为 1:
- 如果任意一位为 0 → 元素一定不存在。
- 如果全部为 1 → 元素可能存在(可能因哈希冲突导致误判)。
典型应用场景
- ✅ 防止缓存穿透:在缓存前加布隆过滤器,过滤掉查询不存在 key 的请求。
- ✅ 网页爬虫去重:快速判断 URL 是否已抓取。
- ✅ 垃圾邮件过滤:判断邮件地址是否在黑名单中。
- ✅ 数据库查询优化:如 HBase、Cassandra 用其跳过不含目标数据的文件。
Redis 中 set和zset区别是什么?
| 特性 | Set(集合) | ZSet(有序集合,Sorted Set) |
|---|---|---|
| 是否有序 | ❌ 无序 | ✅ 有序(按分数 score 排序) |
| 元素是否唯一 | ✅ 唯一(不允许重复) | ✅ 唯一(不允许重复) |
| 存储结构 | 哈希表(或整数集合) | 跳跃表(Skip List) + 哈希表 |
| 核心字段 | 只有元素值 | 元素值(member) + 分数(score) |
| 时间复杂度(插入/删除/查询) | O(1) | O(log N) |
| 内存占用 | 较低 | 较高(需存储 score 和维护跳跃表) |
谈谈Redis的持久化机制RDB和AOF的区别及优缺点。
什么是RDB和AOF_rdb aof-CSDN博客
如何保证 redis 和 mysql 数据缓存一致性问题?
1、对于【读数据】,选择旁路缓存策略,如果 cache 不命中,会从 db 加载数据到 cache。
读:1. 先读 Redis
2. 如果命中,返回数据
3. 如果未命中,查 MySQL,写入 Redis,再返回写流程:1. 更新 MySQL
2. 删除 Redis 中对应的 key(不是更新!)2、先删缓存,再更新 DB(不推荐)
1. 删除 Redis
2. 更新 MySQL3、延迟双删
用于防止 “先删缓存,后更新 DB” 导致的并发问题。
public void updateOrder(Order order) {redis.delete("order:" + id); // 第一次删除mysql.update(order); // 更新数据库Thread.sleep(100); // 延迟 100msredis.delete("order:" + id); // 第二次删除 }
- 高并发写场景,防止其他线程在更新 DB 期间读到旧缓存。
4、基于 MySQL Binlog 的异步更新(如 Canal)
利用 MySQL 的 binlog 实现缓存自动更新。
MySQL → Binlog → Canal → 消费者 → 删除/更新 Redis
流程:业务更新 MySQL---->Canal 监听 binlog,捕获变更-->消费者删除对应 Redis key。
Redis如何实现高可用?
Redis 实现高可用(High Availability)的核心目标是:在部分节点故障时,系统仍能正常提供服务,避免单点故障。主要通过以下三种机制实现:
1. Redis 主从复制(Replication)
原理
- 一个 主节点(Master) 接收写请求。
- 多个 从节点(Slave/Replica) 异步复制主节点的数据。
- 从节点可处理读请求,实现读写分离。
2. Redis Sentinel(哨兵)
原理
- Sentinel 是一个分布式监控系统,通常部署 3~5 个节点。
- 监控主从节点的健康状态。
- 当主节点宕机时,自动选举一个从节点升级为新主节点,并通知客户端。
3. Redis Cluster(集群)
原理
- Redis 官方提供的分布式解决方案。
- 数据自动分片(Sharding) 到 16384 个哈希槽(hash slots),分布在多个节点上。
- 每个节点负责一部分槽位。
- 每个主节点可配置一个或多个从节点,实现高可用。
高可用机制
- 当某个主节点宕机:
- 它的从节点会自动被选举为新主节点。
- 集群继续对外提供服务(仅该主节点负责的槽位短暂受影响)。
- 客户端直接连接集群,通过
MOVED或ASK重定向请求。
方案 是否自动故障转移 是否支持分片 适用场景 主从复制 ❌ 否 ❌ 否 基础备份、读写分离 Sentinel ✅ 是 ❌ 否 中小规模,高可用需求 Cluster ✅ 是 ✅ 是 大规模、高并发、分布式
Redis的持久化机制有哪两种?AOF和RDB同时开启时,重启会加载哪个文件?为什么?
Redis 提供了两种核心的持久化方式:
RDB(Redis Database)
- 原理:在指定时间间隔内,生成内存数据的快照(snapshot),保存为二进制文件(如
dump.rdb)。- 优点:文件小、恢复快、性能影响小。
- 缺点:可能丢失最后一次快照之后的数据。
AOF(Append-Only File)
- 原理:记录每一条写操作命令,以文本形式追加到日志文件(
appendonly.aof)。- 优点:数据安全性高(最多丢 1 秒数据),可读性强。
- 缺点:文件大、恢复慢、影响写性能
Redis 在启动时,会根据持久化文件的存在情况决定加载策略:
情况 加载顺序 原因 仅开启 RDB 加载 dump.rdb有快照就恢复 仅开启 AOF 加载 appendonly.aof有日志就重放 ✅ AOF 和 RDB 同时开启 优先加载 AOF 文件 AOF 数据更完整,更接近宕机前的状态 核心原因:
- RDB 是定时快照,比如每 5 分钟保存一次,那么最后一次快照之后的 5 分钟数据会丢失。
- AOF 记录了每一笔写操作(默认每秒同步),最多只丢失 1 秒数据,数据完整性更高。
- 因此,Redis 选择优先使用数据更完整的 AOF 文件来恢复,确保数据不丢失。
用Redis做缓存时,缓存穿透、缓存击穿、缓存雪崩怎么解决?说具体方案,别只说概念。
缓存穿透、缓存击穿、缓存雪崩分别是什么-CSDN博客
Redis的哨兵模式和Cluster模式有什么区别?分别适合什么场景?
哨兵模式(Sentinel)详解
1. 架构组成
- 主从复制:一个 Master,多个 Replica。
- Sentinel 集群:3~5 个 Sentinel 节点,监控 Master 和 Replica 的健康状态。
2. 工作流程
- Sentinel 持续监控 Master。
- 当 Master 宕机,Sentinel 集群通过投票选出一个 Replica 升级为新 Master。
- 通知客户端更新连接地址。
3. 优点
- ✅ 实现自动故障转移,避免单点故障。
- ✅ 支持读写分离,提升读性能。
- ✅ 架构相对简单,易于理解。
4. 缺点
- ❌ 不支持数据分片,单节点存储上限受限。
- ❌ 写操作集中在 Master,写性能无法扩展。
- ❌ 故障转移期间有短暂不可用(几秒)。
5. 适用场景
✅ 中小规模应用,如:
- 数据量不大(< 10GB)
- 对高可用有要求,但无需水平扩展
- 读多写少的缓存系统
Cluster 模式(Cluster)详解
1. 架构组成
- 多主多从:至少 6 个节点(3 主 3 从)。
- 数据分片:16384 个哈希槽(hash slots),均匀分布在主节点上。
- Key 通过
CRC16(key) % 16384计算所属槽位。- 每个主节点负责一部分槽位,从节点复制主节点数据。
2. 工作流程
- 客户端请求 Key,计算其槽位。
- 如果节点负责该槽位,直接处理。
- 否则返回
MOVED重定向到正确节点。- 主节点宕机,其从节点自动升级为新主。
3. 优点
- ✅ 真正的分布式架构,支持水平扩展。
- ✅ 读写均可扩展,性能随节点增加而提升。
- ✅ 高可用 + 自动故障转移。
- ✅ 官方原生支持,无需额外组件。
4. 缺点
- ❌ 不支持跨 slot 的多键操作(如
MGET、事务、Lua 脚本)。- ❌ 运维复杂,需管理槽位、节点状态。
- ❌ 客户端必须支持 Cluster 协议。
5. 适用场景
✅ 大规模、高并发、高可用系统,如:
- 用户画像系统
- 实时排行榜
- 大型电商平台的购物车、库存缓存
Redis Cluster最多能有多少个节点?槽位怎么分配的?客户端怎么知道数据存在哪个节点?
| 问题 | 回答 |
|---|---|
| 最多多少节点 | 理论 1000,推荐 ≤1000,避免 Gossip 开销过大 |
| 槽位怎么分配 | 16384 个槽,slot = CRC16(key) % 16384,主节点分片负责 |
| 客户端如何定位 | 通过 MOVED / ASK 重定向,客户端缓存槽位映射,实现智能路由 |
理论上最多支持 1000 个节点,但实际推荐不超过 1000 个。
槽位(Hash Slot)是怎么分配的?
Redis Cluster 将整个键空间划分为 16384 个哈希槽(hash slots),范围是
0到16383。1. 槽位分配原理
- 每个 主节点(master) 负责一部分槽位。
- 所有槽位必须被分配完,且每个槽位只能由一个主节点负责。
- 从节点(replica)不负责槽位,只复制主节点的数据。
2. Key 到槽位的映射
- 对每个 key,通过以下公式计算其所属槽位:
slot = CRC16(key) % 16384CRC16是一个标准的 16 位循环冗余校验算法,输出范围是 0~65535。- 取模 16384 后,得到 0~16383 的槽位号。
3. 槽位分配示例
假设 3 个主节点:
node1负责槽位0 ~ 5500node2负责槽位5501 ~ 11000node3负责槽位11001 ~ 16383✅ 这样,数据就被均匀分散到多个节点上,实现水平分片(Sharding)。
4. 为什么是 16384 个槽位?
- 足够多:能支持几百个节点的均匀分布。
- 足够小:每个节点只需维护 16384 bit(约 2KB)的位图来记录槽位归属,Gossip 消息不会太大。
- 历史原因:CRC16 输出 65536,16384 是 2 的幂,便于计算。
客户端怎么知道数据存在哪个节点?
客户端通过 “重定向机制” 来定位数据,主要依赖以下两种响应:
1. MOVED 重定向(永久迁移)
- 当客户端请求一个 key,但当前节点不负责该 key 的槽位时,会返回:
MOVED <slot> <node-ip:port>
- 客户端收到后,应将该槽位映射缓存到本地,后续请求直接发往目标节点。
🌰 示例:
- 客户端向
node1发送GET user:1001node1计算CRC16("user:1001") % 16384 = 12000- 发现 12000 不归自己管,返回:
MOVED 12000 192.168.1.3:6379- 客户端缓存
slot 12000 → node3,下次直接请求node32. ASK 重定向(临时迁移)
- 在 集群重新分片(resharding) 过程中,某个槽位正在从
nodeA迁移到nodeB。- 如果 key 还在
nodeA,但槽位已部分迁移到nodeB,nodeA会返回:ASK <slot> <target-node-ip:port>
- 客户端需先向目标节点发送
ASKING命令,再发送请求。⚠️
ASK是临时的,不影响客户端本地的槽位映射缓存。3. 客户端行为(智能客户端)
- 现代 Redis 客户端(如 Jedis、Lettuce、redis-py-cluster)都是 “智能客户端”。
- 它们会:
- 维护一个 本地槽位映射表(slot → node)。
- 收到
MOVED时更新映射。- 自动重定向请求,对应用透明。
Redis的过期键删除策略是什么?
Redis 的过期键删除策略采用了 两种机制结合的方式:惰性删除(Lazy Deletion) + 定期删除(Active Expire),目的是在内存占用和CPU 消耗之间取得平衡。
惰性删除(Lazy Deletion)
- 只有在访问某个 key 时,才检查它是否已过期。
- 如果过期,则删除该 key,并返回
nil。- 如果未过期,则正常返回值。
定期删除(Active Expire / Active Expire)
- Redis 会周期性地随机抽取一部分设置了过期时间的 key,检查并删除其中已过期的 key。
- 该任务由定时任务(
serverCron)执行,默认每秒运行 10 次(每 100ms 一次)。具体流程(Redis 7.0+)
- 从设置了过期时间的 key 字典中,随机抽取 20 个 key(
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)。- 检查这些 key 是否过期,删除过期的。
- 如果超过 25% 的抽样 key 过期,则重复步骤 1,继续清理。
- 每次运行时间不超过 25ms,避免阻塞主线程。
过期删除策略和内存淘汰策略有什么区别?
- 内存淘汰策略是在内存满了的时候,redis 会触发内存淘汰策略,来淘汰一些不必要的内存资源,出空间,来保存新的内容
- 过期键删除策略是将已过期的键值对进行删除,Redis 采用的删除策略是惰性删除+定期删除。
为什么对于过期键不立即删除?
在过期 key 比较多的情况下,删除过期 key 可能会占用相当一部分 CPU 时间,在内存不紧张但 CPU 时间紧张的情况下,将 CPU 时间用于删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。所以,定时删除策略对 CPU 不友好。
介绍一下Redis 内存淘汰策略
Redis 提供了 8 种淘汰策略,分为 三类:基于 LRU、LFU、TTL 和随机。
第一类:针对所有 key(allkeys)
策略 说明 allkeys-lru从所有 key 中淘汰最近最少使用(Least Recently Used)的。 allkeys-lfu从所有 key 中淘汰最不经常使用(Least Frequently Used)的。(Redis 4.0+) allkeys-random从所有 key 中随机淘汰。 第二类:仅针对设置了过期时间的 key(volatile)
策略 说明 volatile-lru从设置了 TTL 的 key 中淘汰最近最少使用的。 volatile-lfu从设置了 TTL 的 key 中淘汰最不经常使用的。 volatile-random从设置了 TTL 的 key 中随机淘汰。 volatile-ttl淘汰剩余生存时间(TTL)最短的 key。 第三类:不淘汰
noeviction默认策略。达到内存上限后,新写入操作返回错误(如 SET返回OOM)。只允许读操作。
LRU和LFU工作原理对比
1. LRU(最近最少使用)
- 维护一个“访问时间线”:每次访问某个 key,就把它移到“最近使用”位置。
- 淘汰时:选择最久未被访问的 key。
- 类比:像翻书,最近翻过的页在最上面,很久没翻的在最下面。
🌰 示例:
- 访问顺序:A → B → C → A
- 当前顺序:[A, C, B](A 最近用过)
- 淘汰时:淘汰 B(最久未访问)
2. LFU(最不经常使用)
- 为每个 key 维护一个“访问计数器”:每访问一次,计数 +1。
- 淘汰时:选择访问次数最少的 key。
- 高级实现:计数器会随时间衰减,避免“历史热门”长期霸占缓存。
🌰 示例:
- 访问记录:A(5次), B(2次), C(8次)
- 淘汰时:淘汰 B(访问最少)
LRU和LFU有什么区别?
| 算法 | 全称 | 中文含义 | 核心思想 |
|---|---|---|---|
| LRU | Least Recently Used | 最近最少使用 | 看“时间”:优先淘汰最久没被访问的 |
| LFU | Least Frequently Used | 最不经常使用 | 看“频率”:优先淘汰访问次数最少的 |
| 对比维度 | LRU | LFU |
|---|---|---|
| 判断依据 | 最后一次访问时间 | 历史访问频率 |
| 对突发流量的反应 | ⚠️ 敏感 (新 key 会排到前面,旧热点可能被淘汰) | ✅ 不敏感 (需要多次访问才能成为“热点”) |
| 缓存污染问题 | ❌ 存在 (一次性大量扫描数据会挤掉热点) | ✅ 较好解决 (单次访问不会大幅提升计数) |
| 内存开销 | 较小 (只需维护访问顺序) | 较大 (每个 key 要存一个计数器) |
| 实现复杂度 | 简单 (可用双向链表 + 哈希表) | 复杂 (需计数器 + 衰减机制) |
| 适合场景 | 访问模式局部性强 (如会话缓存、页面缓存) | 访问模式稳定 (如商品详情、配置缓存) |
Redis 6.0的多线程和单线程相比,性能提升多少?
Redis 6.0 引入的多线程是一个重大架构升级,但它并非将整个 Redis 变成多线程,而是在网络 I/O 层面引入多线程,核心命令执行仍为单线程。
redis 6.0 多线程的本质:多线程只用于网络 I/O
- 主线程:仍负责接收连接、解析命令、执行命令、返回结果。
- IO 线程:只负责并行地读取客户端请求数据和写回响应数据。
🔑 命令执行(Command Execution)依然是单线程的,保证了数据一致性和无锁设计。
在高并发、网络 I/O 密集的场景下,性能可提升 3~4 倍。
为什么能提升这么多?
根本原因:网络 I/O 成为瓶颈
- 在单线程模型下,处理大量小请求时,网络读写(socket read/write)占用了大量 CPU 时间。
- 尤其在 10G 网卡、高并发(10万+ QPS)场景下,单线程无法充分利用多核 CPU。
- 多线程 I/O 将这部分工作并行化,释放了主线程压力。
Redis 6.0多线程为什么只处理网络IO不处理命令执行?
Redis 6.0 引入多线程时,只将网络 I/O 部分多线程化,而命令执行仍然保留在单线程中,
根本原因:保持 Redis 的“简单性”和“无锁设计”
原因 说明 ✅ 保持无锁设计 避免锁竞争,保证高性能和简单性 ✅ 解决真实瓶颈 网络 I/O 是瓶颈,命令执行不是 ✅ 保证数据一致性 单线程天然支持原子性、事务、Lua 脚本 ✅ 避免复杂性 多线程执行会引入竞态、死锁等问题 ✅ 兼容生态 不破坏现有客户端和模块假设
Redis的主从复制是同步还是异步?网络中断后重连,会全量同步还是增量同步?怎么判断?
问题 回答 主从复制是同步还是异步? ✅ 异步复制,主节点不等待从节点确认 网络中断后重连? ⚖️ 取决于偏移量是否在 backlog 中:
- 在:增量同步
- 不在:全量同步如何判断? 🔍 看日志:
-Partial resynchronization→ 增量
-Full resync→ 全量Redis 主从复制本质上是异步的
- 主节点(Master) 在接收到写命令后:
- 先执行该命令(如
SET key value)。- 然后将该命令追加到复制积压缓冲区(replication backlog)。
- 立即返回结果给客户端,不等待从节点确认。
- 从节点(Replica) 通过
PSYNC命令持续拉取主节点的写命令,并在本地重放。网络中断后重连,会全量同步还是增量同步?
取决于 从节点的偏移量(offset)是否还在主节点的复制积压缓冲区中。
两种情况:
情况 同步方式 说明 ✅ 增量同步(Partial Resynchronization) 从节点断开时间短,其复制偏移量仍在主节点的 backlog中主节点只发送缺失的部分命令 ❌ 全量同步(Full Resynchronization) 从节点断开时间长,偏移量已从 backlog中移除主节点生成 RDB 快照并传输,从节点清空数据重新加载 怎么判断是全量还是增量同步?
1. 通过日志判断(最直接)
查看 Redis 日志:
增量同步日志:
Replica asks for synchronization Partial resynchronization request from replica accepted.
- 全量同步日志:
Replica asks for synchronization Full resync from master, sending 123456789 bytes of bulk data
为什么Redis单线程还能这么快?遇到CPU密集型命令(如KEYS)会有什么问题?怎么处理?
| 问题 | 回答 |
|---|---|
| 单线程为何快? | 内存操作 + I/O 多路复用 + 无锁 + 高效数据结构 |
| CPU 密集型命令问题? | 阻塞主线程,导致服务不可用 |
| 如何处理? | 1. 禁用 KEYS2. 用 SCAN 替代3. 开启慢日志 4. 多线程 I/O(仅提升网络) 5. 从节点执行 |
单线程为何快?
1. 基于内存操作
- Redis 数据存储在 内存 中,读写速度是纳秒级。
- 相比之下,数据库操作磁盘是毫秒级,相差百万倍。
- 内存操作几乎不成为瓶颈。
📊 示例:一次
GET key就是一次哈希表查找,O(1) 时间复杂度。2. 采用 I/O 多路复用(I/O Multiplexing)
- Redis 使用 epoll(Linux)、kqueue(BSD)等机制,一个线程可监听成千上万个客户端连接。
- 不需要为每个连接创建线程,避免了线程切换和锁竞争开销。
3. 纯 C 语言实现,高效数据结构
- Redis 用 C 编写,贴近硬件,性能极高。
- 内置如 跳表(zset)、压缩列表(ziplist)、哈希表 等优化数据结构,查询效率高。
4. 避免多线程开销
- 多线程的代价:
- 线程创建/销毁开销
- 上下文切换(context switch)
- 锁竞争(lock contention)
- Redis 单线程天然无锁,避免了这些开销。
遇到 CPU 密集型命令(如 KEYS)会有什么问题?
虽然 Redis 快,但某些命令会阻塞主线程,导致整个服务不可用。
典型问题命令:
命令 问题 KEYS *扫描所有 key,O(N) 时间,大数据量时卡住几秒甚至更久 SMEMBERS big_set获取大集合所有元素,耗 CPU 和带宽 HGETALL huge_hash类似,可能返回 GB 级数据 后果:
- 主线程阻塞:期间无法处理其他请求。
- 超时:客户端请求超时(如
read timeout)。- 雪崩:大量请求堆积,系统崩溃。
怎么处理这类问题?
方案 1:禁用危险命令(推荐)
在
redis.conf中重命名或禁用:rename-command KEYS "" rename-command FLUSHDB "" rename-command FLUSHALL ""方案 2:使用替代命令
危险命令 安全替代 KEYS *SCAN 0 MATCH user:* COUNT 100SMEMBERSSSCANHGETALLHSCAN# 安全分页扫描 SCAN 0 MATCH session:* COUNT 100 # 返回新的游标,下次继续🔑
SCAN是 O(1) 每次调用,适合在生产环境使用。方案 3:Redis 6.0+ 的多线程 I/O
Redis 6.0 引入了多线程 I/O,但命令执行仍是单线程。
- 多线程负责:网络读写(socket read/write)。
- 主线程负责:命令解析和执行。
效果:提升网络吞吐,但不能解决
KEYS这类命令的阻塞问题。方案 4:监控与告警
- 监控
slowlog:SLOWLOG GET 5- 设置慢查询阈值:
configslowlog-log-slower-than 10000 # 超过 10ms 记录- 告警:发现
KEYS、FLUSHALL等命令立即报警。方案 5:隔离部署
- 将管理类、分析类请求放到独立的从节点执行。
- 主节点只处理核心业务流量
什么是 BigKey?
Redis大key问题指的是某个key对应的value值所占的内存空间比较大,,导致Redis的性能下降、内存不足、数据不均衡以及主从同步延迟等问题。
BigKey 并没有一个绝对的标准,但通常从两个维度衡量:
Key 对应的 Value 体积过大:例如,一个 String 类型的 Value 超过 10 KB,或者一个 Hash、List、Set 等集合类型的元素数量超过 5000(这个阈值可根据实际情况调整)。
Key 本身占用内存过大:虽然不常见,但如果一个 Key 的 Value 是包含了数百万个元素的集合,它就是一个 BigKey。
Redis的BigKey有什么危害?
- 阻塞主线程(最严重): Redis是单线程处理命令的。删除或读取一个BigKey(如包含数百万个元素的Hash/List)会消耗大量CPU时间,导致其他命令被阻塞,造成服务延迟甚至超时。
- 内存占用过高: 创建或读取BigKey时,需要一次性申请或传输大量内存,可能引发内存碎片或网络带宽打满。
- 主从同步延迟: BigKey的同步会占用大量网络带宽和从节点处理时间,导致主从数据不一致时间变长。
- 集群模式下槽位倾斜: 在Redis Cluster中,BigKey会导致其所在节点的内存和负载远高于其他节点,破坏负载均衡。
怎么检测和删除BigKey?
- 使用官方
redis-cli --bigkeys命令: Redis自带的命令,通过扫描(SCAN)方式统计不同数据类型中元素数量最多的Key,适合离线或低峰期使用。使用
MEMORY USAGE命令:对于已知的、怀疑是 BigKey 的 Key,可以直接查询其精确的内存占用(字节数)。
删除BigKey时会阻塞服务吗?
直接使用
DEL命令删除一个 BigKey(例如,一个包含百万元素的 Hash),Redis 需要同步释放大量内存,这个过程会长时间占用主线程,导致服务阻塞。
UNLINK命令: 不会阻塞(或阻塞极短)。UNLINK只将Key标记为待删除并创建后台任务,立即返回。内存释放由后台线程完成,对主线程影响极小。
UNLINK:异步删除。它先将 Key 从 keyspace 中移除,真正的内存释放操作在后台线程中执行。
Redis大key如何解决?
解决 Redis 大 Key 问题是一个系统工程,需要从预防、监控、治理三个层面入手。
预防
数据分片(Sharding):
- 按范围分片: 将一个大的集合拆分成多个小的Key。例如,将一个包含百万用户信息的Hash
user:all:info拆分为user:0-9999:info,user:10000-19999:info等。- 按哈希分片: 对ID进行哈希取模,如
user:profile:{id%100},将数据分散到100个Key中。- 时间维度分片: 如日志类数据,按天/小时存储,
log:20251023,log:20251024。选择合适的数据结构:
- 避免使用一个String存储超大JSON或序列化对象,考虑是否可拆解。
- 如果List过长且只关心最新N条,使用
LPUSH + LTRIM限制长度。- 考虑用Sorted Set替代List实现排行榜,利用其范围查询能力。
设置合理的过期时间(TTL):为可能膨胀的Key设置TTL,防止无限增长。
业务逻辑优化:评估是否真的需要在Redis中存储如此大的数据。有时数据库或文件系统更合适。
已存在BigKey的治理
安全删除:必须使用
UNLINK命令 替代DEL。UNLINK异步释放内存,避免阻塞主线程。分批处理:
- List: 使用
LPOP/RPOP或LTRIM分批弹出或截断。- Hash: 使用
HSCAN遍历,配合HDEL分批删除字段。- Set/ZSet: 使用
SSCAN/ZSCAN配合SREM/ZREM分批删除成员。迁移或归档:将不常访问的BigKey数据迁移到成本更低的存储(如数据库、对象存储),Redis中只保留热点数据。
监控与发现
- 定期执行
redis-cli --bigkeys进行扫描。- 配置慢查询告警,分析长时间运行的命令。
- 集成监控系统,对内存突增、延迟升高进行预警。
什么是热key?
热Key(Hot Key) 是指在短时间内被极高频次访问的Redis Key。
核心特征
- 高QPS: 该Key的读/写请求量在极短时间内暴增,可能达到每秒数万甚至数十万次。
- 集中访问: 访问集中在单个或少数几个Key上。
- 突发性: 通常由特定事件触发,具有突发性。
常见场景
- 突发热点事件: 某明星官宣、突发新闻,导致相关资讯的缓存Key被疯狂访问。
- 热门商品: 电商大促时,秒杀或爆款商品的商品详情、库存信息Key被大量用户请求。
- 热门文章/视频: 平台上的爆款内容,其内容缓存、点赞数、评论数等Key访问量激增。
- 公共配置: 全局开关、活动配置等被所有服务实例频繁读取的Key。
危害
- 单机过载(最严重): Redis通常是主从或集群部署。一个热Key会集中在某一台Redis实例上,导致该实例的CPU、内存带宽或网络带宽被打满,响应变慢甚至崩溃,而其他实例却很空闲。
- 请求倾斜: 在集群模式下,热Key所在的分片(shard)负载远高于其他分片,破坏负载均衡。
- 缓存击穿风险: 如果热Key恰好在此时过期或被删除,大量请求会直接穿透到数据库,造成数据库压力剧增,甚至宕机。
- 响应延迟: 单实例处理不过来,导致所有访问该实例的请求延迟升高。
如何解决热key问题?
本地缓存(Local Cache):
- 在应用服务本地(如使用
Caffeine、Guava Cache)缓存热Key的数据。- 大部分请求在本地缓存命中,极大减少对Redis的访问压力。
- 需注意本地缓存的一致性问题(如设置较短TTL或通过消息队列更新)。
Redis集群优化:
- Key分片: 将一个热Key拆分成多个子Key(如
hotkey:1,hotkey:2),访问时随机选择一个,分散压力。- 副本读取: 利用Redis主从架构,将读请求分散到多个从节点(Slave),写请求仍走主节点。
限流与降级:
- 在服务层对访问热Key的请求进行限流,防止打垮下游。
- 在极端情况下,可返回默认值或降级页面,保证系统可用性。
提前发现与预案:
- 建立热Key探测机制(如监控、采样分析),提前发现潜在热Key。
- 对已知的热Key(如大促商品)提前做好预案(如预热、本地缓存)。
用Redis存储1000万条用户会话数据,选什么数据结构?内存占用大概多少?怎么优化内存?
推荐使用 String 结构,以 session:<session_id> 为 key,序列化后的会话数据为 value。
SET session:abc123 "{\"user_id":1001,"login_time":1730000000,"ip":"192.168.1.100"}"
EXPIRE session:abc123 3600 # 1小时过期
| 数据结构 | 是否合适 | 原因 |
|---|---|---|
| ✅ String | ✔️ 推荐 | 简单、直接、内存紧凑,适合存储整条会话数据 |
| ❌ Hash | ⚠️ 不推荐 | 虽然可存字段,但每个 field 都有额外开销,小数据更耗内存 |
原则:数据结构越简单,内存占用越小,性能越高。
用Redis做排行榜,怎么实现实时更新和分页查询?ZSet的score更新会影响性能吗?
????
Spring Boot02(数据库、Redis)---java八股-CSDN博客
Spring Boot02(数据库、Redis)02---java八股-CSDN博客
