Redis 面试题
1. Redis是什么?
Redis(Remote Dictionary Server)是一个使用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的
2. Redis优缺点?
Redis 的核心优势在于极致性能和灵活的数据结构,但同时受限于内存成本和数据一致性设计,存在明显使用边界。
Redis 的核心优势(优点)
- 超高性能:数据存储在内存中,读写速度极快,单机 QPS (每秒请求次数) 可轻松达到 10 万级别,响应延迟通常在微秒级。
- 丰富数据结构:原生支持字符串、哈希、列表、集合、有序集合等,无需额外封装就能满足复杂业务需求(如排行榜、计数器)。
- 高可用与可扩展:支持主从复制、哨兵模式和集群部署,能实现故障自动切换和水平扩容,保障服务稳定性。
- 功能全面:自带持久化(RDB/AOF)、过期淘汰、发布订阅、事务等功能,可覆盖缓存、消息队列、会话存储等多场景。
Redis 的主要局限(缺点)
- 内存成本高:数据依赖内存存储,大规模数据场景下硬件成本远高于磁盘数据库(如 MySQL)。
- 数据容量受限:单实例数据量受物理内存大小限制,即使集群部署,也需合理规划分片以控制单节点内存占用。
- 强一致性支持弱:默认采用异步复制,主从节点间可能存在数据延迟,无法完全满足金融级等强一致性要求的场景。
- 持久化有代价:RDB 可能丢失短时间数据,AOF 日志文件会随时间增大,需定期重写,过程中会占用额外资源。
3. Redis为什么这么快?
Redis 之所以性能极高(单机 QPS 可达 10 万 +,响应延迟微秒级),核心源于其底层设计对 “高性能” 的极致优化,具体可从以下几个层面拆解:
1. 基于内存存储,避开磁盘 I/O 瓶颈
Redis 的数据主要存储在内存中,而内存的读写速度(微秒级)远高于磁盘(毫秒级,差距约 10 万倍)。传统数据库(如 MySQL)需频繁读写磁盘,而 Redis 几乎所有操作都在内存中完成,天然规避了磁盘 I/O 这个性能瓶颈。
2. 单线程模型,避免多线程切换开销
Redis 采用单线程处理核心请求(网络 I/O 和数据操作由一个主线程完成),这看似 “反常识”,却带来两大优势:
- 无线程切换成本:多线程需频繁进行 CPU 上下文切换(保存 / 恢复线程状态),而单线程无需处理这些开销,减少了性能损耗。
- 无锁竞争:单线程操作共享数据时,无需加锁(如互斥锁),避免了锁竞争带来的阻塞和性能波动。
注:Redis 并非完全单线程,后台持久化(RDB/AOF)、集群同步等操作由额外线程处理,不影响主线程的请求处理。
3. 高效的数据结构设计
Redis 针对每种数据结构(字符串、哈希、列表、集合等)都做了极致优化,底层实现并非简单的数组或链表,而是结合场景选择最高效的结构:
- 字符串(String):基于动态字符串(SDS)实现,预分配空间减少内存重分配次数,支持直接修改部分内容(无需整体复制)。
- 哈希(Hash):小规模数据用压缩列表(ziplist)存储(节省内存,连续内存访问更快),数据量大时自动转为哈希表,兼顾读写效率。
- 列表(List):底层是 “双向链表 + 压缩列表”,插入 / 删除首尾元素复杂度为 O (1),且压缩列表适合存储短数据,减少内存碎片。
- 有序集合(Sorted Set):基于跳表(skiplist)实现,支持 O (logN) 的插入、查询和排序,比平衡树实现更简单,性能更稳定。
4. I/O 多路复用技术,高效处理并发连接
Redis 采用 I/O 多路复用模型(如 Linux 的 epoll、Windows 的 IOCP),通过一个线程同时监听多个网络连接,批量处理 I/O 事件:
- 传统阻塞 I/O 需为每个连接创建线程,线程数过多会导致资源耗尽;而多路复用可在单线程内高效处理数万甚至数十万连接,大幅降低资源消耗。
epoll等机制采用 “事件驱动” 模式,仅当连接有数据可读 / 可写时才触发处理,避免无效的轮询开销。
总结
Redis 的高性能是内存存储 + 单线程模型 + 高效数据结构 + I/O 多路复用四大核心因素共同作用的结果,每一层设计都围绕 “减少开销、提升效率” 展开,最终实现了远超传统数据库的响应速度。
4. 既然Redis那么快,为什么不用它做主数据库,只用它做缓存?
Redis 不做主数据库,核心是它的优势(快)恰好对应了主数据库的核心需求(可靠存海量数据、支持复杂操作)的短板,具体可简化为三点:
- 存海量数据成本太高:Redis 靠内存存数据,成本远高于磁盘数据库,存全量业务数据不划算。
- 数据可靠性不足:持久化机制(RDB/AOF)可能丢数据,核心业务(如订单、交易)无法接受。
- 不支持复杂需求:没有 SQL 那样的复杂查询能力,事务功能也弱,满足不了主数据库的业务场景。
所以它更适合做缓存,专门加速高频访问的热点数据,而非替代主数据库存全量核心数据。
5. 讲讲Redis的线程模型?
Redis 的线程模型可以概括为 “单线程处理核心请求 + 多线程处理辅助任务”,这种设计既保证了核心操作的高效性,又避免了单线程在辅助任务上的性能瓶颈。
1. 核心:单线程处理网络 I/O 和数据操作
Redis 的主线程是单线程,负责处理所有客户端的网络请求(连接建立、数据读写)和核心数据操作(如 set、get、哈希 / 列表操作等)。
为什么用单线程?
因为 Redis 的数据都在内存中,操作速度极快(微秒级),而多线程的上下文切换、锁竞争等开销反而会降低效率。单线程无需处理这些问题,能最大化利用 CPU 资源。
如何处理并发连接?
依赖I/O 多路复用技术(如 Linux 的 epoll):主线程通过一个 “事件循环” 监听多个客户端连接,批量处理就绪的 I/O 事件(如 “客户端发送数据”“连接断开”),无需为每个连接创建线程,高效支撑数万并发连接。
2. 辅助:多线程处理非核心任务
Redis 并非完全单线程,从 4.0 版本开始引入多线程处理非核心任务,避免阻塞主线程:
- 后台持久化:RDB 快照生成、AOF 日志重写由专门的后台线程执行,不影响主线程处理请求。
- 异步删除:删除大键(如包含百万元素的集合)时,用
UNLINK命令触发异步删除,由后台线程逐步清理,避免主线程长时间阻塞。 - 集群同步:主从节点间的数据同步、集群槽位迁移等操作,由独立线程处理。
总结
Redis 线程模型的核心是:用单线程高效处理核心的网络 I/O 和数据操作(避免多线程开销),用多线程处理耗时的辅助任务(避免阻塞主线程)。这种设计既发挥了单线程的高效性,又通过多线程弥补了其在复杂任务上的短板,最终实现了超高的性能。
6. Redis应用场景有哪些?
Redis 凭借高性能、多数据结构和灵活特性,适合以下核心应用场景:
- 缓存
- 存储高频访问数据(如商品详情、用户信息),减少数据库压力,提升响应速度。
- 例:电商商品页缓存,用户登录后基本信息缓存。
- 会话存储
- 分布式系统中存储用户登录会话(如 Session),实现多服务器间会话共享。
- 例:用户登录状态在多台应用服务器间同步。
- 计数器与限流
- 用
INCR实现实时计数器(如文章阅读量、接口调用次数)。 - 结合过期时间实现限流(如限制单 IP 每秒请求次数)。
- 用
- 排行榜与实时排序
- 用有序集合(Sorted Set)实现按分数排序的排行榜(如游戏积分、商品销量排行),支持动态更新和范围查询。
- 消息队列
- 用列表(List)的
LPUSH/RPOP实现简单消息队列,解耦系统模块(如订单创建后通知库存系统)。
- 用列表(List)的
- 分布式锁
- 基于
SET NX命令实现分布式环境下的互斥锁,保证临界资源的原子操作(如秒杀库存扣减)。
- 基于
- 地理位置服务
- 用 GEO 类型存储经纬度,实现附近的人、商家定位等功能(如外卖平台显示周边店铺)。
- 热点数据临时存储
- 存储临时高频数据(如秒杀活动库存、限时优惠券),利用过期时间自动清理。
- 社交网络
- 点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。
这些场景的核心是利用 Redis 的高速读写、多结构支持、过期机制,解决传统数据库在性能或功能上的短板。
7. Memcached和Redis的区别?
Memcached 与 Redis 核心区别对照表
| 对比维度 | Memcached | Redis |
|---|---|---|
| 核心定位 | 轻量级分布式内存缓存工具 | 多功能内存数据库(兼具缓存能力) |
| 支持数据结构 | 仅支持 Key-Value(Value 为字符串) | 字符串、哈希、列表、集合、有序集合、GEO 等 |
| 数据持久化 | 不支持(重启后数据全丢失) | 支持 RDB 快照、AOF 日志(可选持久化) |
| 分布式能力 | 依赖客户端哈希分片 | 自带集群(Cluster)功能,支持自动分片 |
| 高级功能 | 无(仅基础缓存) | 支持分布式锁、计数器、排行榜、发布订阅、事务 |
| 内存管理 | 采用 Slab Allocation(易有内存碎片) | 支持多种内存淘汰策略,可自定义内存分配器 |
| 并发性能 | 多线程模型(适合多核,但有锁开销) | 单线程核心模型(避免锁开销,高并发更优) |
| 适用场景 | 简单纯缓存需求(如数据库查询结果缓存) | 复杂业务场景(需多结构、持久化、高级功能) |
8. 为什么要用 Redis 而不用 map/guava 做缓存?
Guava 是 Google 开源的 Java 工具类库,里面包含了很多实用工具,其中 Guava Cache 是它提供的单机内存缓存组件。它能解决普通 Map 做缓存的痛点(如自动过期、容量限制、缓存淘汰),但本质还是 “运行在单个 JVM 进程内的缓存”,无法跨服务器共享。
总结核心:Map、Guava Cache、Redis 的本质和适用场景完全不同,选择的关键是 “是否分布式”“是否需缓存管理能力”:
- 普通 Map:只是基础内存数据结构,无缓存管理(过期、淘汰),仅适合单线程、临时存少量数据(如方法内临时数据)。
- Guava Cache:是单机内存缓存组件,支持自动过期、LRU 淘汰,适合单机应用(无需跨服务共享)的本地缓存(如单服务配置缓存)。
- Redis:是分布式内存缓存系统,支持跨服务共享、持久化、集群扩容,适合分布式系统(多服务共享数据、需高可用 / 大容量)的场景(如共享会话、秒杀库存)。
简单说:单机小场景用 Guava,分布式大场景用 Redis,Map 仅能做临时存储,不能算 “正经缓存工具”。
9. Redis 数据类型有哪些?
Redis 核心支持 8 种数据类型,可按 “基础常用” 和 “特殊场景” 分类,每种类型对应特定业务需求,核心总结如下:
1. 基础常用类型(5 种)
- 字符串(String):最基础类型,值可存字符串、数字或二进制数据(如图片 base64),支持
SET/GET/INCR(自增)等操作,适合存单个值(如用户昵称、商品库存)。 - 哈希(Hash):键值对的集合(类似 Java 的 HashMap),键和值都是字符串,支持单独操作某个字段(如
HSET user:1 name "张三"),适合存结构化数据(如用户信息、商品详情)。 - 列表(List):有序的字符串集合(底层是双向链表),支持从首尾增删元素(
LPUSH/RPOP),适合做消息队列、最新消息列表(如朋友圈动态)。 - 集合(Set):无序的字符串集合,自动去重,支持交集、并集、差集(如
SINTER求共同好友),适合存不重复数据(如用户标签、抽奖中奖名单)。 - 有序集合(Sorted Set):带分数(score)的 Set,按分数自动排序,支持按分数范围查询(如
ZRANGEBYSCORE),适合做排行榜(如游戏积分、商品销量排行)。
2. 特殊场景类型(3 种)
- 地理位置(GEO):存储经纬度数据,支持计算两点距离、查询附近的点(如
GEORADIUS查周边商家),适合 LBS 服务(如外卖、打车定位)。 - 位图(Bitmap):用二进制位存储数据(1 个键对应多个二进制位),支持位运算(如
BITCOUNT统计活跃天数),适合存海量布尔值(如用户签到、是否在线)。 - 基数统计(HyperLogLog):用极小内存统计集合的不重复元素个数(误差约 0.81%、绝对不会超过 12 KB),适合海量数据去重计数(如统计网站独立访客 UV)。
简单说:基础类型覆盖多数常规场景,特殊类型针对定位、海量统计等细分需求,选择时需结合 “数据结构” 和 “业务操作” 匹配。
10. SortedSet和List异同点?
Redis 的 SortedSet(有序集合)和 List(列表)都是 “有序的元素集合”,但核心差异在于排序机制、去重能力和适用场景,具体异同点总结如下:
一、相同点
- 元素有序性:两者存储的元素都有顺序,支持按顺序获取元素(如获取前 N 个、后 N 个)。
- 元素类型:元素均为字符串类型,且都支持动态增删元素。
- 支持范围操作:都能按 “位置范围” 获取元素(如 List 的
LRANGE、SortedSet 的ZRANGE)。
二、核心差异(关键区别)
| 对比维度 | List(列表) | SortedSet(有序集合) |
|---|---|---|
| 排序机制 | 按 “插入顺序” 排序(插入时的先后决定位置) | 按 “用户指定的 score(分数)” 排序,自动维护有序性 |
| 去重能力 | 允许重复元素(同一值可多次插入,位置不同) | 自动去重(元素值唯一,重复插入会更新 score) |
| 核心操作 | 支持首尾快速增删(LPUSH/RPOP)、按索引操作 | 支持按 score 排序 / 查询(ZRANGEBYSCORE)、修改 score(ZINCRBY) |
| 适用场景 | 需按插入顺序存储的场景(如消息队列、最新动态) | 需按自定义规则排序的场景(如排行榜、带权重的任务调度) |
三、总结
- 用 List:当你需要 “按插入顺序保存元素,允许重复,且需快速操作首尾” 时(如消息队列按发送顺序消费、存储用户最新浏览记录)。
- 用 SortedSet:当你需要 “按自定义分数排序,自动去重,且需按分数筛选” 时(如游戏积分排行榜按分数降序、按用户等级排序展示)。
简单说:List 是 “插入有序”,SortedSet 是 “score 有序”,去重和排序逻辑的不同决定了它们的适用场景完全不同。
11. Redis的内存用完了会怎样?
Redis 内存用完后的行为,核心由 是否配置 maxmemory 和 内存淘汰策略 决定,总结为两点:
未配置 maxmemory(默认):Redis 会持续占满服务器内存,最终可能被操作系统的 OOM killer 杀死,导致数据丢失,风险高。
配置了 maxmemory:内存达上限时触发预设策略,分两类:
- 拒绝写入(默认
noeviction):新写请求报错,读请求正常,适合不允许丢数据的场景; - 主动淘汰(如
allkeys-lru、volatile-lru):按规则淘汰旧数据(如最近少用、过期时间最短)释放空间,适合缓存场景,需按业务选策略。
- 拒绝写入(默认
实际用的时候,推荐配置 maxmemory 并搭配 LRU 类策略(如 allkeys-lru),同时监控内存避免频繁淘汰。
12. Redis如何做内存优化?
Redis 内存优化核心是 “减少占用、提升效率”,关键策略总结为:
- 选对数据结构:
- 用紧凑编码(如 ziplist 哈希、整数编码字符串),避免拆分数据(一个 Hash 存多字段比多 String 省空间);
- 海量布尔值用 Bitmap,替代 Set/String。
- 优化存储格式:
- 缩短键名 / 字段名(如
u:1:n代替user:1:name); - 大值数据(如 JSON)压缩后存储。
- 缩短键名 / 字段名(如
- 合理配置策略:
- 设
maxmemory及淘汰策略(如allkeys-lru淘汰冷数据); - 临时数据加过期时间,避免集中过期(加随机值分散)。
- 设
- 利用内置特性:
- 开启自动内存碎片整理;
- 用高效的内存分配器(jemalloc)。
- 分布式分片:集群 / 分片分散数据,避免单节点内存过载。
核心逻辑:针对性减少冗余存储,结合配置和特性控制内存增长,平衡空间与性能。
13. Redis存在线程安全问题吗?
Redis 的线程安全问题需分 服务端 和 客户端 两层看,核心结论是 “服务端天然安全,客户端需额外保障”,具体总结如下:
服务端层面:无线程安全问题
- Redis 本质是线程安全的 K-V 数据库,即使 6.0+ 引入多线程,也仅用于处理网络 IO 事件(如接收请求、返回结果)。
- 所有指令的执行仍由 主线程串行处理(一个接一个,无并发执行),指令本身是原子操作,无需额外同步机制,不会出现数据不一致。
客户端层面:存在线程安全风险
虽服务端单条指令原子,但多个客户端(或同一客户端多线程)并发执行 “多指令组合操作” 时,原子性无法保障。
例:两个客户端同时读取 k = 10 的值、在执行扣减操作
set k 9导致最终数据还是9,导致业务逻辑错误,少扣减库存。解决方案:用 Redis 原子指令(如
INCR自增原子指令)、加分布式锁、或通过 Lua 脚本将多指令打包成原子操作。sh# 初始:key不存在时,默认从0开始自增 127.0.0.1:6379> INCR visit_count # 对"访问量计数器"自增1 (integer) 1 # 结果:1(初始化为0,+1后为1) # 对"商品库存"每次增加10件 127.0.0.1:6379> INCRBY goods_stock 10 (integer) 10 # 初始库存10
简言之,Redis 服务端靠 “主线程串行执行指令” 保证安全,客户端需通过合理设计规避多端并发带来的风险。
14. keys命令存在的问题?
在 Redis 中,KEYS 命令用于查找所有匹配给定模式(如 *、user:*)的键,但它存在严重的性能和安全性问题,在生产环境中强烈不建议使用,具体问题如下:
1. 阻塞 Redis 服务,引发性能雪崩
- Redis 是单线程模型,KEYS命令会遍历整个键空间(扫描所有键),执行时间与键的总数成正比。
- 若 Redis 中存在百万级、千万级以上的键,
KEYS可能需要几秒甚至几十秒才能完成。 - 期间 Redis 无法处理其他任何请求(包括读写操作),导致整个服务阻塞,引发业务超时、雪崩等严重问题。
- 若 Redis 中存在百万级、千万级以上的键,
2. 无分页机制,返回数据可能撑爆内存
- KEYS会将所有匹配的键一次性返回给客户端,若匹配的键数量极多(如几十万、上百万),返回的数据包会非常大:
- 一方面,Redis 服务器需要占用大量内存临时存储结果,可能导致内存溢出(OOM)。
- 另一方面,客户端接收大量数据时,也可能因内存不足崩溃,或占用过多网络带宽,影响其他服务。
3. 无法用于生产环境的批量操作场景
- 即使仅需查找少量键,
KEYS仍会扫描全量键,效率极低(例如,从 1000 万键中找 10 个匹配键,仍需遍历 1000 万次)。 - 相比之下,
SCAN命令通过游标分批迭代键空间,每次只返回少量结果,不会阻塞服务,是更安全的替代方案。
4. 潜在的安全风险
KEYS *会暴露 Redis 中所有键的名称,可能泄露业务敏感信息(如用户 ID、订单号等)。- 若 Redis 未做好权限控制(如未设置密码、绑定了公网 IP),恶意用户可通过
KEYS探测数据结构,进而尝试攻击。
总结:替代方案
- 生产环境中如需遍历键,必须使用
SCAN命令,它通过游标分批次返回结果,每次执行时间短,不会阻塞服务。 - 示例:
SCAN 0 MATCH user:* COUNT 100(从游标 0 开始,匹配user:*模式,每次最多返回 100 个键)。 - 若需频繁根据模式查询键,建议提前通过二级索引(如用 Set 存储某类键的集合)优化,避免动态扫描。
15. Redis事务
Redis 事务(Transaction)是一组命令的集合,它允许将多个命令打包执行,保证这些命令要么全部执行,要么全部不执行(原子性),但与传统数据库的事务相比,Redis 事务的特性和实现机制有显著差异。
一、Redis 事务的核心特性
- 原子性(Atomicity)的局限性
- Redis 事务中的所有命令会被序列化执行,中间不会插入其他客户端的命令(隔离性)。
- 但不支持回滚(Rollback):若事务中某条命令因语法错误无法执行,整个事务会被拒绝;若因逻辑错误(如对字符串执行自增)导致命令失败,其他命令仍会继续执行,不会回滚。
- 原因:Redis 设计上追求简单高效,避免回滚带来的性能开销,认为逻辑错误应由开发者在代码中规避。
- 隔离性(Isolation)
- 事务执行期间,其他客户端的命令无法插入到事务中间,事务内的命令会按顺序执行,不受外部干扰。
二、事务的基本命令
Redis 事务通过以下 4 个命令实现:
| 命令 | 作用 |
|---|---|
MULTI | 标记事务开始,之后输入的命令会被放入事务队列,不会立即执行。 |
普通命令(如 SET、INCR) | 被加入事务队列,返回 QUEUED 表示入队成功。 |
EXEC | 执行事务队列中的所有命令,返回各命令的执行结果(按入队顺序)。 |
DISCARD | 取消事务,清空队列,放弃执行。 |
WATCH | 监视一个或多个键,若事务执行前这些键被其他客户端修改,则事务被打断(EXEC 返回 nil)。 |
三、使用示例
# 开启事务
127.0.0.1:6379> MULTI
OK
# 命令入队
127.0.0.1:6379> SET a 10
QUEUED
127.0.0.1:6379> INCR a
QUEUED
127.0.0.1:6379> GET a
QUEUED
# 执行事务
127.0.0.1:6379> EXEC
1) OK
2) (integer) 11
3) "11"四、WATCH 命令:实现乐观锁
WATCH 用于解决事务中的并发问题,原理类似乐观锁:
- 事务开始前,通过
WATCH key1 key2 ...监视关键键。 - 若在
MULTI到EXEC之间,被监视的键被其他客户端修改,EXEC会放弃执行事务,返回空结果(nil),开发者需重试。
示例:模拟并发扣减库存
# 客户端 A 监视库存
127.0.0.1:6379> WATCH stock
OK
# 客户端 A 开启事务,计划扣减库存
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECR stock
QUEUED
# 此时客户端 B 修改了 stock
127.0.0.1:6379> SET stock 100
OK
# 客户端 A 执行事务,因 stock 被修改,事务失败
127.0.0.1:6379> EXEC
(nil) # 事务未执行,需重试五、Redis 事务的局限性
- 无回滚机制:逻辑错误导致命令失败时,其他命令仍会执行。
- 不支持复杂条件判断:事务队列中无法嵌入
IF-ELSE等逻辑,需依赖WATCH或 Lua 脚本补充。 - 长时间阻塞风险:若事务包含大量命令,
EXEC执行时会阻塞其他请求(Redis 单线程),需控制事务命令数量。
六、替代方案:Lua 脚本
对于需要更复杂原子操作的场景,推荐使用 Lua 脚本:
- Redis 会将整个 Lua 脚本作为一个原子操作执行,中间不会被打断。
- 支持条件判断、循环等逻辑,功能比事务更强大。
示例:用 Lua 脚本实现 “库存大于 0 时才扣减”
127.0.0.1:6379> EVAL "if redis.call('get', KEYS[1]) > 0 then return redis.call('decr', KEYS[1]) else return 0 end" 1 stock
(integer) 99 # 库存扣减成功总结:Redis 事务适合简单的批量原子操作,若需复杂逻辑或更严格的并发控制,建议结合 WATCH 或使用 Lua 脚本。
16. Redis事务支持隔离性吗?
Redis 事务支持隔离性:事务内命令会连续串行执行,期间其他客户端命令无法插入干扰,通过单线程特性实现类似 “串行化” 的隔离效果。
17. Redis事务保证原子性吗,支持回滚吗?
Redis 事务的原子性和回滚支持有其特殊性:
- 原子性:事务中的命令会被整体执行,中间不会插入其他命令,但仅保证 “要么全部执行,要么全部不执行”(若命令因语法错误无法入队,整个事务会被拒绝;若因逻辑错误执行失败,其他命令仍会继续执行),并非传统数据库的严格原子性。
- 回滚:不支持回滚。即使事务中某条命令执行失败(如对字符串执行自增),其他命令仍会继续执行,不会回滚已执行的命令。Redis 设计上避免了回滚机制,以追求简单高效,逻辑错误需由开发者自行规避。
18. 持久化机制?
Redis 的持久化机制用于将内存中的数据同步到磁盘,防止服务宕机后数据丢失,主要有两种方案:RDB(快照) 和 AOF( Append Only File ),可单独使用或结合使用。
1. RDB(Redis Database):快照式持久化
- 原理:在指定时间间隔内,将内存中的全量数据生成快照(二进制文件,默认
dump.rdb)并写入磁盘。 - 触发方式
- 自动触发:通过配置
save <秒数> <修改次数>定义触发条件(如save 60 1000表示 60 秒内有 1000 次修改则触发)。 - 手动触发:执行
save(阻塞主进程,不推荐)或bgsave(fork 子进程执行,不阻塞主进程)命令。 - Redis 正常关闭(shutdown):
- 仅开 RDB:触发
save生成全量快照,完成后关闭;
- 仅开 RDB:触发
- 自动触发:通过配置
- 优点
- 快照文件体积小,恢复速度快(直接加载二进制文件到内存)。
- 适合备份、灾难恢复场景。
- 缺点
- 可能丢失最近一次快照后的所有数据(如配置
save 300 10(5 分钟内有 10 次修改触发快照),假设 10:00 触发了一次快照,10:03 时 Redis 宕机,那么 10:00 到 10:03 之间的所有数据修改都会丢失。)。 bgsave时 fork 子进程会消耗额外内存(尤其是数据量大时)。
- 可能丢失最近一次快照后的所有数据(如配置
2. AOF(Append Only File):日志式持久化
- 原理:记录所有写命令(如
SET、INCR)到日志文件(默认appendonly.aof),重启时通过重新执行这些命令恢复数据。 - 触发方式
- 开启需配置
appendonly yes,默认关闭。 - 配置写入策略(通过
appendfsync配置):always:每个命令立即写入磁盘(安全性最高,性能最差)。everysec:每秒批量写入(默认,平衡安全与性能,最多丢失 1 秒数据)。no:由操作系统决定何时写入(性能好,安全性差)。
- Redis 正常关闭(shutdown):
- 开启 AOF(无论是否开 RDB):先强制刷盘 AOF 缓冲区命令,若开 RDB 再额外生成快照,全部完成后关闭。
- 开启需配置
- 优点
- 数据丢失风险低(可配置到秒级)。
- 日志文件是文本格式,易排查问题。
- 缺点
- 日志文件体积大(命令重复记录),需定期通过
bgrewriteaof重写(合并重复命令,减小体积)。 - 恢复速度比 RDB 慢(需重新执行所有命令)。
- 日志文件体积大(命令重复记录),需定期通过
3. 选择与组合建议
- 追求性能 + 可容忍部分数据丢失:用 RDB。
- 追求数据安全性 + 可接受稍低性能:用 AOF(
everysec策略)。 - 生产环境推荐组合使用:RDB 做定期全量备份,AOF 记录实时命令,兼顾恢复速度和数据安全性。
重启时,Redis 优先加载 AOF 文件(若开启),因为 AOF 数据更完整;若 AOF 关闭,则加载 RDB 文件。
4.(RDB 和 AOF)的对比表格:
| 对比维度 | RDB(快照) | AOF(日志) |
|---|---|---|
| 核心原理 | 定时生成内存全量数据的二进制快照文件 | 记录所有写命令到日志文件,恢复时重放命令 |
| 文件格式 | 二进制(dump.rdb),体积小 | 文本(appendonly.aof),体积较大(可重写) |
| 数据安全性 | 较低,可能丢失两次快照间的所有数据 | 较高,可配置为每秒或实时写入,最多丢失 1 秒数据 |
| 恢复速度 | 快(直接加载二进制文件到内存) | 慢(需重新执行所有命令) |
| 性能影响(写操作) | 低(仅 fork 子进程时短暂影响,平时无额外开销) | 较高(每次写命令需记录日志,受 appendfsync 策略影响) |
| 触发方式 | 自动(save 配置)或手动(bgsave) | 开启后实时记录(依赖配置),重写需 bgrewriteaof |
| 适用场景 | 备份、灾难恢复、可容忍数据丢失的高性能场景 | 对数据安全性要求高,需减少数据丢失的场景 |
19. RDB和AOF如何选择?
选择 RDB 还是 AOF,核心看业务对数据丢失的容忍度、性能需求和运维场景:
- 可容忍短期丢失、需快速恢复或依赖备份,选 RDB;
- 要零 / 低丢失、需查操作日志,选 AOF;
- 生产环境追求安全、性能、恢复效率平衡,优先 RDB+AOF 组合(AOF 保实时安全,RDB 保快速恢复与备份)
20. Redis有哪些部署方案?
Redis 有 4 类核心部署方案,差异主要体现在 “是否解决单点故障”“能否扩展性能 / 存储” 上,可根据业务规模和需求细化选择:
1. 单机部署:简单但仅适基础场景
- 架构:单台服务器跑一个 Redis 实例,所有读写都在这一个实例上完成。
- 核心特点:部署维护最简单,无网络通信开销,单机性能最优(纯内存操作 QPS 可达 10 万 +);但有致命缺点 ——单点故障(实例或服务器宕机,服务直接中断),且数据量、并发量受单机 CPU / 内存限制。
- 适用场景:开发测试环境、临时缓存(如会话存储)、非核心业务(可容忍短暂不可用)。
2. 主从复制:解决 “读性能”,但没解决 “主节点单点”
- 架构:1 个主节点(Master)+ 多个从节点(Slave),主节点处理所有写操作,从节点只处理读操作,主节点会自动把数据同步给从节点(首次全量同步 RDB,之后增量同步写命令)。
- 核心特点:通过 “读写分离” 提升读吞吐量(比如 3 个从节点可分担 3 倍读请求),从节点还能作为主节点的备份,减少主节点持久化压力;但主节点仍是单点—— 主节点宕机后,无法处理写操作,需手动把从节点切换为主节点,运维成本高。
- 适用场景:读多写少的业务(如商品详情页、用户信息查询),需提升读性能,但能接受手动故障切换。
3. 哨兵模式:在主从基础上,解决 “高可用”
- 架构:在主从复制的基础上,增加 3~5 个 “哨兵进程”,哨兵会持续监控主从节点状态。
- 核心特点:继承主从 “读写分离、数据备份” 的优势,关键是能自动处理故障—— 主节点宕机时,哨兵会投票判定主节点 “下线”,自动把一个从节点升级为新主节点,其他从节点切换到新主节点,无需人工干预;但缺点是写性能和存储仍受单机限制(所有节点存全量数据,数据量太大时单机内存不够,且所有写操作仍集中在一个主节点)。
- 适用场景:中小规模业务(数据量 < 100GB),需高可用(如订单缓存、支付状态存储),但写并发不算极端。
4. Redis Cluster(集群):解决 “大规模扩展”,适合高并发大数据
- 架构:多主多从(通常 3~6 主,每个主节点配 1 个从节点),核心是 “数据分片”—— 把所有键通过哈希映射到 16384 个 “槽(Slot)”,每个主节点负责一部分槽,实现数据分布式存储。
- 核心特点:既解决 “高可用”(主节点宕机,从节点自动补位),又解决 “扩展问题”—— 想提升写性能或存更多数据,直接加主节点即可(新主节点会分担一部分槽),支持动态扩容;但架构最复杂,部署维护成本高,且跨槽的命令(如同时操作不同主节点的键)需特殊处理。
- 适用场景:大规模生产环境(数据量 > 100GB、高并发读写),如社交平台用户数据、电商全量商品缓存。
简单说,测试用单机,中小规模读多写少用 “主从 + 哨兵”,大规模高并发用 Cluster 集群,按业务增长逐步升级即可。
21. 主从架构?
Redis 主从架构(Master-Slave)是基于 “1 主多从” 的数据同步模式,核心作用是实现读写分离和数据备份,解决单机部署的读性能瓶颈,是 Redis 高可用方案的基础。
一、核心架构与角色
- 主节点(Master):唯一的写节点,负责处理所有写操作(如
SET、INCR、DEL),同时将数据变更同步给从节点。 - 从节点(Slave):只读节点,仅处理读操作(如
GET、HGET),数据完全从主节点复制而来,不能主动写数据(配置slave-read-only yes限制,默认开启)。 - 数量关系:1 个主节点可挂载多个从节点(通常建议不超过 5 个,避免主节点同步压力过大),从节点之间也可级联(从节点作为其他从节点的 “主节点”,减少主节点负担)。
二、数据同步原理(核心流程)
主从数据同步分两个阶段:全量同步(首次连接)和增量同步(后续变更)。
1. 全量同步(首次复制)
当从节点首次连接主节点时,触发全量同步,流程如下:
- 从节点发送
PSYNC ? -1命令,请求全量同步(?表示未知主节点偏移量,-1表示全量)。 - 主节点收到请求后,执行
bgsave生成 RDB 快照文件,并记录此时开始的所有写命令到 “复制缓冲区”。 - 主节点将 RDB 文件发送给从节点,从节点接收后清空本地数据,加载 RDB 文件恢复数据。
- 主节点将复制缓冲区中的写命令发送给从节点,从节点执行这些命令,最终与主节点数据一致。
2. 增量同步(后续复制)
全量同步完成后,主节点会实时将新的写命令(如 SET name "redis")记录到 “复制缓冲区”,并异步发送给从节点;从节点接收后立即执行,保持与主节点数据实时同步。
- 关键依赖:主节点和从节点都维护一个 “偏移量”(记录已同步的命令位置),通过偏移量匹配确保数据不丢失、不重复
22. 哨兵Sentinel?
Redis 哨兵(Sentinel)是基于主从架构的高可用解决方案,核心作用是自动监控主从节点状态、检测故障并执行故障转移,解决主从架构中 “主节点单点故障需手动切换” 的痛点。
一、核心定位与核心功能
哨兵本质是一组独立运行的进程(通常 3~5 个,避免哨兵自身单点),围绕主从集群提供 4 大核心能力:
- 监控(Monitoring):持续通过
PING命令检测主节点、从节点及其他哨兵进程的存活状态。 - 故障检测(Notification):若某节点超时未响应,哨兵会标记其为 “主观下线”;当多数哨兵(超过半数)确认主节点下线,会标记为 “客观下线”,并触发后续操作。
- 故障转移(Failover):主节点客观下线后,哨兵会自动从从节点中选一个 “最优节点” 升级为新主节点,同时让其他从节点切换到新主节点同步数据,最后通知客户端新主节点地址。
- 配置提供(Configuration Provider):客户端连接哨兵集群时,哨兵会返回当前主节点的 IP 和端口,让客户端无需硬编码主节点地址。
23. Redis cluster?
Redis Cluster(Redis 集群)是 Redis 官方提供的分布式解决方案,通过 “多主多从 + 数据分片” 架构,同时解决了主从 / 哨兵模式的 “写性能瓶颈” 和 “存储容量限制”,支持大规模高并发场景下的横向扩展。
一、核心架构设计
1. 节点组成
- 多主多从:集群至少包含 3 个主节点(Master),每个主节点建议搭配 1 个从节点(Slave),形成 “主从组”。
- 主节点:负责数据存储、处理读写请求,并管理一部分 “槽位”。
- 从节点:作为主节点的备份,主节点故障时自动升级为主节点,保障高可用。
- 示例架构:3 主 3 从(主 1 + 从 1、主 2 + 从 2、主 3 + 从 3),可满足中小规模分布式需求;若需扩容,直接新增 “主 + 从” 节点即可。
2. 核心机制:数据分片(Slot)
- 槽位划分:Redis Cluster 将所有数据映射到 16384 个槽位(Slot)(范围 0-16383),每个主节点负责一部分槽位(如 3 主节点各分配~5461 个槽)。
- 数据定位:客户端写入键时,通过哈希算法 CRC16(key) % 16384计算键对应的槽位,再将请求路由到负责该槽位的主节点。
- 例:
key = "user:100"计算后槽位为 547,若该槽位由主 2 负责,则请求会发送到主 2。
- 例:
- 槽位迁移:扩容 / 缩容时,可通过工具(如
redis-cli --cluster)将槽位及对应数据在主节点间迁移,实现 “无缝扩展”。
二、核心能力:高可用与扩展
1. 自动故障转移(主从切换)
每个 “主从组” 内置高可用逻辑,类似哨兵模式:
- 当主节点宕机(如网络中断、服务器故障),其从节点会通过 “投票机制” 检测主节点状态,确认故障后自动升级为新主节点。
- 新主节点接管原主节点的所有槽位,其他节点会更新 “槽位 - 节点” 映射关系,客户端后续请求会自动路由到新主节点,无需人工干预。
2. 横向扩展(性能 + 容量)
- 性能扩展:新增主节点后,可将现有主节点的槽位迁移给新主节点,分散读写压力(写请求随槽位分布到多个主节点,突破单机写瓶颈)。
- 容量扩展:数据按槽位分散存储在不同主节点,集群总存储容量 = 所有主节点内存容量之和(如 3 个主节点各 64GB 内存,总容量可达 192GB)。
三、客户端访问逻辑
客户端与 Cluster 交互时,无需依赖代理(原生支持),流程如下:
- 客户端首次连接任意节点,会获取整个集群的 “槽位 - 节点” 映射表,缓存到本地。
- 后续请求时,客户端先本地计算键的槽位,再直接发送到对应主节点。
- 若槽位已迁移(如扩容后槽位归属变化),目标节点会返回 “重定向响应”(携带新主节点地址),客户端更新本地映射表后重试,实现 “动态路由”。
24. 过期键的删除策略?
Redis 过期键删除通过三种机制协同实现,平衡内存与性能:
- 惰性删除:访问键时才检查是否过期,过期则删除(省 CPU,可能留内存)。
- 定期删除:每隔一段时间随机抽查部分过期键并删除(控内存,避免 CPU 过载)。
- 内存淘汰:内存满时,按策略(如 LRU 最近少用、过期时间最短)删除部分键(兜底防内存溢出)。
25. 内存淘汰策略有哪些?
Redis 内存淘汰策略共 8 种,按 “删除范围” 可分 3 类,核心区别是 “删哪些键” 和 “怎么选要删的键”:
1. 只删 “设了过期时间的键”(4 种)
volatile-lru(默认):淘汰「最近最少被访问」的键(按最后访问时间排序,最久未用的优先)。volatile-lfu:淘汰「累计访问频率最低」的键(按总访问次数排序,次数最少的优先)。volatile-ttl:淘汰「剩余过期时间最短」的键(即将过期的优先)。volatile-random:随机淘汰该范围内的键。
2. 删 “所有键(不管是否过期)”(3 种)
allkeys-lru:淘汰所有键中「最近最少被访问」的键。allkeys-lfu:淘汰所有键中「累计访问频率最低」的键。allkeys-random:随机淘汰所有键中的任意键。
3. 不删键(1 种)
noeviction:内存满了拒接写操作,只允许读(不推荐生产用)。
日常用得最多的是 volatile-lru(默认)和 allkeys-lru,前者保核心永久数据,后者适合全缓存场景。
26. MySQL 与 Redis 如何保证数据一致性?
这三种都是数据变更时同步缓存的核心方案,核心差异在 “操作顺序” 和 “是否处理并发脏数据”,具体总结如下:
1. 先删缓存,再更数据库
- 操作流程:第一步删除 Redis 缓存键 → 第二步更新 MySQL 数据。
- 核心问题:并发场景下易产生长时间脏数据(如线程 1 删缓存后未更库,线程 2 查缓存空、读 MySQL 旧数据回写缓存,后续线程 1 更库后,缓存仍存旧数据,需等缓存过期或下次更新才修复),实际很少用。
- 适用场景:无并发读写、数据变更频率极低的场景(几乎无实用价值)。
2. 先更数据库,再删缓存(Cache Aside Pattern)
- 操作流程:第一步更新 MySQL 数据 → 第二步删除 Redis 缓存键。
- 核心优势:最常用、实现简单,仅可能出现短暂不一致(如线程 1 更库后未删缓存,线程 2 读缓存命中旧数据,线程 1 删缓存后,后续请求会读 MySQL 新数据回写缓存),不一致概率低、影响范围小。
- 潜在问题:若第二步删缓存失败(如 Redis 宕机),会残留旧缓存,需依赖后续读请求回写修复(最终一致)。
- 适用场景:多数业务场景(如商品详情、用户基础信息缓存),可接受短暂最终一致。
3. 延迟双删除
- 操作流程:第一步删除 Redis 缓存键 → 第二步更新 MySQL 数据 → 第三步延迟一段时间(如 500ms),再次删除 Redis 缓存键。
- 核心目标:解决 “先删缓存,再更数据库” 的并发脏数据问题(防止第一步删缓存后,其他线程读旧库数据回写缓存,第三步二次删除可清空回写的旧数据)。
- 潜在问题:延迟时间难把控(太短可能没覆盖 “读旧数据回写” 的耗时,太长会占用线程资源),代码复杂度比前两种高。
- 适用场景:一致性要求高、并发读写频繁的核心场景(如订单状态、支付金额更新)。
27. 缓存常见问题
1. 缓存穿透
- 定义:请求查询 “既不在缓存、也不在数据库” 的数据(如查不存在的用户 ID),每次请求都穿透缓存直连数据库,导致数据库压力陡增。
- 核心诱因:数据本身不存在,缓存无法命中且不回写,请求持续打库。
- 解决方式:缓存空值(数据库查不到时,缓存
NULL并设短过期)、布隆过滤器(提前过滤无效数据,拦截不存在的请求)。
2. 缓存击穿
- 定义:“高频热点数据”(如秒杀商品、热门文章)的缓存突然过期,大量并发请求瞬间穿透缓存,集中访问数据库,导致数据库瞬时过载。
- 核心诱因:热点数据缓存失效,且并发量高,无防护机制。
- 解决方式:热点数据永不过期(业务主动更新缓存)、互斥锁(仅一个线程查库回写缓存,其他线程等待)、缓存预热(提前加载热点数据到缓存)。
3. 缓存雪崩
- 定义:两种场景引发:①大量缓存设置相同过期时间,到期时同时失效;②缓存集群整体故障(如 Redis 宕机),所有缓存不可用,请求全量打向数据库,可能导致数据库雪崩。
- 核心诱因:缓存 “批量失效” 或 “整体不可用”,失去对数据库的保护。
- 解决方式:过期时间加随机值(避免批量失效)、缓存集群高可用(哨兵 / Cluster 模式,容错故障节点)、降级熔断(缓存故障时返回默认数据,阻断请求打库)。
4. 缓存预热
- 定义:系统上线或活动开始前,主动将 “热点数据”(如活动商品、首页推荐)加载到缓存,避免用户请求时 “查库→回写缓存” 的冷启动过程,减少数据库压力、提升用户体验。
- 核心价值:跳过首次请求的 “缓存空命中” 阶段,直接用预热好的缓存响应请求。
- 实现方式:项目启动时自动加载、定时任务批量刷新、手动触发预热脚本。
5. 缓存降级
- 定义:当缓存服务故障(如响应慢、集群不可用)或业务流量超预期时,主动 “牺牲非核心缓存功能”,返回默认数据或提示,避免缓存问题扩散到数据库,保障核心服务可用。
- 核心目标:“丢卒保帅”,优先保障核心业务(如支付、下单),而非缓存的完整性。
- 实现方式:故障时返回默认值(如商品库存查不到时返回 “100+”)、关闭非核心缓存查询(如推荐列表故障时不返回推荐数据)、熔断请求(短时间内阻断对故障缓存的请求)。
一句话区分核心差异
- 穿透:查 “不存在的数据”;击穿:查 “存在但缓存过期的热点数据”;雪崩:“大量缓存失效 / 缓存挂了”;预热:“提前装缓存”;降级:“缓存坏了就兜底”。
28. Redis 怎么实现消息队列?
Redis 实现消息队列主要依赖 3 种数据结构,各有侧重,适合不同场景:
- List 结构:
- 用
LPUSH生产消息、BRPOP阻塞消费,支持 FIFO 和持久化,实现简单。 - 缺点:无消息确认、不支持多消费者负载均衡,适合简单异步任务(如日志收集)。
- 用
- Pub/Sub(发布订阅):
- 生产者
PUBLISH到频道,消费者SUBSCRIBE接收,支持广播。 - 缺点:消息不持久化、无确认机制,适合实时通知(如聊天室)。
- 生产者
- Stream 结构(Redis 5.0+):
- 支持消息持久化、确认(
XACK)、消费组(负载均衡),功能最完善,接近专业 MQ。 - 缺点:语法较复杂,适合对可靠性、顺序性有要求的场景(如订单处理)。
- 支持消息持久化、确认(
29. Redis 怎么实现延时队列?
延时队列是一种能让消息在指定时间后才被消费的队列,核心是 “延迟触发”—— 消息进入队列后不会立即被处理,而是等待预设的延时时间到期后,才会被消费者获取并执行,就像 “定时任务的消息版”。
基于 Sorted Set(最常用)
- 原理:用 “消息过期时间戳” 当分数(score),消息内容当元素(member),按分数排序存;消费者定时取 “分数≤当前时间” 的消息(即到期消息)。
- 流程:
- 生产者:
ZADD 队列键 过期时间戳 消息(如 30 秒后到期,就用当前时间 + 30000 当时间戳)。 - 消费者:每 1 秒查一次,用
ZRANGEBYSCORE取到期消息,再用ZREM删除避免重复消费。
- 生产者:
- 特点:支持持久化,可靠;但定时查有轻微延迟,适合多数场景(如订单超时取消)。
30. pipeline的作用?
Redis 的 Pipeline(管道)作用是批量执行多个命令,减少网络往返次数,提升通信效率,尤其适合需要连续执行多条命令的场景。
核心问题:无 Pipeline 时的效率瓶颈
Redis 是客户端 - 服务器架构,默认情况下:
- 客户端发送 1 条命令 → 服务器处理 → 返回结果(1 次网络往返)。
- 若要执行 100 条命令,需 100 次网络往返,网络延迟(哪怕每次毫秒)会被放大 100 倍,严重影响效率。
Pipeline 的解决思路:Pipeline 允许客户端一次性发送多条命令到服务器,服务器批量处理后一次性返回所有结果,将 “N 次网络往返” 压缩为 “1 次往返”,大幅减少网络延迟开销。
具体作用与优势
- 降低网络开销:减少网络往返次数,尤其在客户端与 Redis 服务器物理距离远(如跨机房)时,效果更明显。
- 提升吞吐量:批量处理命令减少了服务器的 I/O 等待,单位时间内能处理更多命令。
- 简化批量操作:无需手动逐条发送命令,通过 Pipeline 一次性组织多条命令,代码更简洁。
注意点
- Pipeline 中的命令是批量执行,但并非原子化(一条命令失败不影响其他命令执行)。
- 批量命令不宜过多(如超过 1000 条),避免占用过多服务器内存或阻塞其他请求。
总结
Pipeline 是 Redis 优化批量命令执行效率的核心手段,通过 “合并网络请求” 解决网络延迟问题,适合日志批量写入、数据批量更新等场景。
31. Redis 的 LUA脚本是什么?
LUA 脚本是一种轻量的嵌入式脚本语言,在 Redis 中主要用于:
- 原子性执行多条命令:脚本内所有命令一次性执行,中间不被其他请求打断,解决并发安全问题(如 “查值 + 修改” 的组合操作)。
- 减少网络交互:多条命令通过脚本一次发送,比逐条发送更高效。
比如用它实现 “判断键存在则自增,否则返回 0”,确保整个逻辑无并发干扰,常用于分布式锁、库存扣减等场景。
32. 什么是RedLock?
RedLock 是 Redis 官方提出的一种分布式锁实现方案,用于解决 “单节点 Redis 部署时,锁的可靠性不足” 问题(如节点宕机导致锁丢失),核心是通过多个独立 Redis 节点共同持有锁,提升分布式锁的安全性。
核心思路
分布式场景下,单节点 Redis 的锁可能因节点故障(如宕机、网络分区)失效,导致 “多个客户端同时获取到锁”。RedLock 通过以下步骤避免这种情况:
- 多节点部署:至少部署 5 个独立的 Redis 节点(无主从关系,完全独立)。
- 获取锁流程:
- 客户端向所有 5 个节点发送 “加锁请求”(带随机值标识和过期时间)。
- 仅当超过半数节点(≥3 个)成功返回加锁,且总耗时不超过锁的过期时间时,才认为 “加锁成功”。
- 释放锁流程:客户端向所有节点发送 “释放锁请求”(通过随机值匹配,避免误删其他客户端的锁)。
核心优势
解决单节点 Redis 锁的 “单点故障风险”,即使部分节点宕机,只要仍有 ≥3 个节点正常,锁仍能可靠工作,适合对分布式锁安全性要求极高的场景(如金融交易、库存扣减)。
注意点
- 节点需完全独立(避免主从同步延迟导致的锁不一致)。
- 加锁超时时间需合理设置(既要短于锁的过期时间,又要容忍网络延迟)。
简单说,RedLock 是 “用多节点冗余” 换取分布式锁的高可靠性。
33. Redis大key怎么处理?
Redis 大 key 指的是存储值过大(如字符串超过 10KB,哈希 / 列表包含数万元素)的键,会导致内存占用集中、操作阻塞、网络传输慢等问题,处理核心是 “拆分大 key 或优化存储方式”,具体方案如下:
一、识别大 key
先通过工具定位大 key:
- 用
redis-cli --bigkeys扫描所有键,按类型统计最大键(字符串看大小,哈希 / 列表看元素数)。 - 用
DEBUG OBJECT key查看具体键的内存占用(如serializedlength字段)。
二、针对性处理方案
1. 字符串大 key(如单个值超 10KB)
- 拆分存储:将大字符串按业务拆分(如长文本按段落分拆),用多个小 key 存储(如
doc:1:part1、doc:1:part2)。 - 压缩存储:对文本类数据(如 JSON)先压缩(如 GZIP)再存入,读取时解压(适合非频繁读写场景)。
2. 哈希 / 集合 / 列表大 key(元素数超 1 万)
- 哈希拆分:按哈希字段拆分(如用户订单哈希
user:100:orders拆分为user:100:orders:0、user:100:orders:1,通过字段哈希值取模分片)。 - 列表拆分:按时间 / 数量拆分(如日志列表
log:all拆分为log:202401、log:202402,按月份存储)。 - 集合 / 有序集合拆分:类似哈希,按元素特征分片(如用户 ID 集合按区间拆分为
users:1-10000、users:10001-20000)。
3. 通用优化手段
- 避免批量操作:大 key 禁止用
HGETALL、LRANGE 0 -1等全量查询,改用分批获取(如HSCAN、LRange 0 100)。 - 过期时间分散:若大 key 需过期,设置随机过期时间(如
EXPIRE key 3600 + 随机数),避免集中删除触发 Redis 阻塞。 - 迁移至其他存储:超大型数据(如百万级元素集合)可迁移到数据库(如 MySQL 分表)或搜索引擎(如 Elasticsearch),Redis 只存热点子集。
三、处理注意事项
- 操作原子性:拆分大 key 时,若需保证多小 key 的原子性,可用 LUA 脚本或分布式锁。
- 平滑过渡:线上处理时先灰度拆分部分数据,验证无误后全量迁移,避免直接替换导致业务异常。
核心原则:将 “集中存储” 转为 “分散存储”,减少单 key 的内存占用和操作压力。
34. Redis常见性能问题和解决方案?
- 内存问题
- 原因:大 Key 占用高、内存碎片多、无淘汰策略
- 解决:拆分大 Key,开启自动碎片整理(activedefrag),配置 LRU 淘汰策略(maxmemory-policy)
- 命令效率
- 原因:高耗时命令(KEYS、HGETALL)、热点 Key 集中访问
- 解决:用 SCAN/HSCAN 替代全量命令,分片热点 Key 分散压力
- 网络瓶颈
- 原因:连接数超限、小请求频繁往返
- 解决:调大 maxclients,用 Pipeline/MGET 批量操作减少网络交互
- 持久化影响
- 原因:RDB 频繁 fork、AOF 刷盘策略过严
- 解决:放宽 RDB 触发条件(如 save 3600 1),AOF 用 everysec 刷盘
- 集群负载
- 原因:哈希槽不均、主从延迟、脑裂
- 解决:重平衡哈希槽,限制主从延迟(min-replicas-max-lag),防止脑裂(min-replicas-to-write)
35. 说说为什么Redis过期了为什么内存没释放?
Key 过期时间被误改已存在的 Key 因误操作被覆盖(如重复执行
SET或EXPIRE),导致原过期时间失效,Key 未按预期过期,持续占用内存。过期键处理策略的特性
惰性删除:Key 过期后不主动删除,仅在下次读写该 Key 时才检查并删除,未被访问的过期 Key 会一直占用内存。
定时删除(抽样):默认每 100ms 仅随机抽查部分过期 Key 删除,而非全量清理,未被抽到的过期 Key 会暂时留存,无法即时释放内存。
36. Redis突然变慢,有哪些原因?
Redis 变慢的常见原因可归纳为 bigkey、内存配置、系统设置、网络及连接 五大类,具体如下:
1. bigkey 导致删除耗时
存储 bigkey(如含大量元素的 Hash/List)时,淘汰或主动删除该键会占用大量时间,阻塞主线程,拖慢整体响应。需避免存储 bigkey,降低内存释放耗时。
2. 内存上限(maxmemory)触发淘汰
当内存达到 maxmemory 阈值,Redis 每次写入新数据前,必须先淘汰部分旧数据以腾出空间,这个淘汰扫描过程会消耗资源,导致写入延迟增加。
3. 系统配置不合理
- 开启内存大页:主进程 fork 子进程做 RDB/AOF 重写时,主进程处理写请求会触发 “写时复制”。若开启内存大页(默认 2MB),即使修改少量数据,也需申请 2MB 内存,耗时变长,导致写请求延迟升高,需关闭内存大页。
- 使用 Swap 分区:内存不足时数据被换至磁盘,Redis 访问这些数据需读取磁盘,速度远慢于内存,会大幅增加响应时间。需增加机器内存或释放内存空间,避免使用 Swap。
4. 网络带宽过载
网络带宽占满时,会出现数据包延迟、丢包,影响 Redis 网络 IO 效率。需确认高流量实例,正常业务则扩容 / 迁移;同时监控网络流量,阈值报警提前处理。
5. 频繁短连接消耗资源
频繁建立 / 释放短连接,会让 Redis 耗费大量时间在 TCP 三次握手、四次挥手上,增加访问延迟。应用需改用长连接操作 Redis。
37. 为什么 Redis 集群的最大槽数是 16384 个?
Redis 集群最大槽数设为 16384,核心是在通信开销、数据分片粒度和兼容性之间的最优权衡,具体原因可总结为三点:
- 降低节点通信成本:用 bitmap 标记槽归属时,16384 个槽仅需 2KB 空间,通过 Gossip 协议同步集群状态时,数据包小、带宽和 CPU 消耗低;若槽数过多(如 65536),同步数据量会增至 8KB,累积开销显著增加。
- 平衡分片粒度:16384 个槽足够精细,即使集群节点少(如 3 主节点),每个节点也能分配约 5000 个槽,保证数据均匀分布;若槽数太少(如 1024),节点增多时易出现槽分配不均,导致热点 Key 集中。
- 历史设计与兼容性:Redis 3.0 引入集群时便确定该数值,经实践验证效率达标,后续版本为保持兼容性未修改,且实际使用中未出现明显瓶颈。
简单说,16384 是用最小成本实现高效分片的最优选择。
38. Redis遇到哈希冲突怎么办?
Redis 中哈希冲突指的是不同的 key 通过哈希函数计算后得到相同的哈希值(即落到同一个哈希槽或哈希表索引),其处理方式与哈希表的底层实现密切相关,具体如下:
1. Redis 哈希表的底层结构
Redis 的哈希表(如存储 Hash 类型数据的结构、集群中的哈希槽映射)底层采用「数组 + 链表」的链式地址法(Separate Chaining)解决冲突:
- 数组(哈希表)的每个元素称为「桶(bucket)」,存储链表的头指针。
- 当不同 key 计算出相同的哈希值(冲突)时,会被放入同一个桶对应的链表中,通过链表串联所有冲突的 key-value 对。
2. 冲突后的查询与操作
- 查询:通过 key 计算哈希值找到对应的桶,然后遍历链表,对比每个节点的 key 以找到目标值(时间复杂度从 O (1) 退化为 O (n),n 为链表长度)。
- 插入 / 删除:同样先定位到桶,再在链表中执行对应操作。
3. 避免冲突导致的性能退化
当链表过长时,查询效率会下降,Redis 通过「rehash(重哈希)」机制优化:
- 当哈希表的负载因子(
已使用桶数 / 总桶数)超过阈值(默认 1),会触发 rehash:创建一个更大的新数组(通常是原大小的 2 倍),将旧数组中的所有 key 重新计算哈希值并迁移到新数组,使链表长度均匀缩短。- 例子:假设哈希表总共有 16 个桶,其中有 16 个桶已经存了数据(不管链表多长),开始扩容。
- 为避免 rehash 阻塞主线程,Redis 采用「渐进式 rehash」:分多次将旧数组的键值对迁移到新数组,期间新旧数组同时存在,查询时会同时检查两个数组,保证操作正常进行。
总结
Redis 通过「链式地址法」直接解决哈希冲突,将冲突的 key 存储在同一桶的链表中;同时通过「渐进式 rehash」机制动态扩容哈希表,避免链表过长导致的性能下降,兼顾了冲突处理效率和主线程的稳定性。
39. Redis实现分布式锁有哪些方案?
Redis 实现分布式锁的核心是利用其原子性操作保证锁的唯一性,常见方案如下,各有适用场景:
1. 基础方案:SET 命令(推荐)
实现:通过
powershellSET key value NX PX milliseconds命令获取锁:
NX:仅当 key 不存在时才设置(保证互斥,同一时间只有一个客户端能拿到锁)。PX milliseconds:设置过期时间(避免锁未释放导致死锁)。
释放锁:需先判断锁归属(避免误删他人锁),再删除,推荐用 Lua 脚本保证原子性:
luaif redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end优点:简单可靠,Redis 2.6.12+ 支持,满足大多数场景。
缺点:单节点 Redis 存在宕机风险(锁丢失),需配合高可用(如主从)。
2. RedLock 算法(多节点增强)
- 实现:针对单节点不可靠问题,在多个独立 Redis 节点(如 5 个)上申请锁:
- 客户端向每个节点发送
SET NX PX命令(超时时间较短,如 50ms)。 - 若超过半数节点(如 3/5)成功获取锁,且总耗时小于锁过期时间,则认为锁获取成功。
- 客户端向每个节点发送
- 优点:通过多节点投票,降低单节点故障导致的锁失效风险,适合强一致性场景。
- 缺点:实现复杂,性能略低(需访问多个节点)。
3. 基于 Hash 类型(可重入锁)
- 实现:用 Hash 存储锁的持有者 ID 和重入次数:
- 获取锁:
HINCRBY lock_key client_id 1,首次获取时同时设置过期时间。 - 释放锁:
HDECRBY lock_key client_id 1,当次数为 0 时删除锁。
- 获取锁:
- 优点:支持重入(同一客户端可多次获取锁),避免嵌套逻辑死锁。
- 缺点:需额外维护重入次数,实现稍复杂。
4. 借助第三方库(如 Redisson)
- 实现:使用开源客户端(如 Redisson),内部封装了分布式锁逻辑,支持:
- 可重入锁、公平锁、读写锁。
- 自动续约(避免锁过期前业务未完成)。
- 基于 Redis 集群的高可用实现。
- 优点:开箱即用,解决了手动实现的诸多细节(如续约、异常处理)。
- 缺点:依赖第三方库,需引入额外依赖。
总结
- 简单场景:优先用
SET NX PX+ Lua 释放锁,配合主从架构保证可用性。 - 强一致性场景:采用 RedLock 算法(需多节点部署)。
- 重入需求:使用 Hash 类型或 Redisson 的可重入锁。
- 降低复杂度:直接使用 Redisson 等成熟库,避免重复造轮子。
