这份面试题覆盖原理底层、高可用架构、生产实践、分布式场景、性能优化五大核心维度,完全匹配中高级后端开发的面试要求,每个问题都包含踩分点和生产级的深度解答。


一、核心基础与底层模型(必问,区分初级/中高级的基础门槛)

1. Redis 为什么这么快?核心原因是什么?

  1. 纯内存操作:所有读写都在内存完成,内存访问延迟在纳秒级,是性能的核心基础。
  2. 高效的单线程命令执行模型:命令执行全程单线程,避免了多线程的上下文切换、锁竞争开销,保证了操作的原子性。
  3. IO多路复用机制:基于epoll(Linux)实现IO多路复用,单线程可处理上万并发的TCP连接,把瓶颈从CPU转移到网络IO。
  4. 极致优化的底层数据结构:内置SDS、跳表、压缩列表等高效结构,操作时间复杂度极低,且做了大量内存优化。
  5. C语言实现:贴近操作系统,执行效率远高于高级语言实现的数据库。

补充踩分点:Redis 6.0+ 引入了多线程IO,仅把网络请求的收发、协议解析交给多线程,核心命令执行依然是单线程,没有改变单线程模型的本质。

2. Redis 的过期键删除策略是什么?内存淘汰机制有哪些?生产怎么选?

过期键删除策略

Redis采用**「定期删除+惰性删除」**的组合策略,平衡性能和内存:

  • 惰性删除:Key被访问时才检查是否过期,过期则删除,对CPU友好,但会造成过期Key堆积占用内存。
  • 定期删除:每隔一段时间(默认100ms),随机抽取一批设置了过期时间的Key,清理过期的Key,调整抽取频率平衡CPU和内存占用。
内存淘汰机制(8种,Redis 4.0+)

当Redis内存达到maxmemory上限时,触发的淘汰规则:

  1. noeviction(默认):不淘汰任何Key,新写入直接报错,生产不推荐。
  2. allkeys-lru:从所有Key中,淘汰最近最少使用的Key,通用缓存场景首选。
  3. volatile-lru:仅从设置了过期时间的Key中,淘汰最近最少使用的Key。
  4. allkeys-lfu(4.0新增):从所有Key中,淘汰使用频率最低的Key,适合热点数据缓存。
  5. volatile-lfu(4.0新增):仅从设置了过期时间的Key中,淘汰使用频率最低的Key。
  6. allkeys-random:从所有Key中随机淘汰,几乎不用。
  7. volatile-random:仅从设置了过期时间的Key中随机淘汰,几乎不用。
  8. volatile-ttl:仅从设置了过期时间的Key中,淘汰最快过期的Key。

生产建议:通用缓存场景选allkeys-lru;有明确冷热区分、热点数据固定的场景选allkeys-lfu;需要保证永久数据不被淘汰的场景选volatile-lru/lfu

3. Redis 6.0 引入多线程的原因?多线程的实现逻辑是什么?

  • 引入原因:Redis的性能瓶颈逐渐从内存操作转移到网络IO处理,单线程处理网络请求的收发、协议解析,无法充分利用多核CPU的性能,高并发下会出现网络IO瓶颈。
  • 实现逻辑
    1. 多线程仅负责网络IO的读写、请求协议的解析,核心的命令执行、内存操作依然是单线程串行执行,完全保证线程安全。
    2. 主线程接收连接后,把读请求分发给多个IO线程处理,解析完成后统一交给主线程串行执行命令,执行完成后再把结果分发给IO线程写回客户端。
    3. 默认关闭多线程IO,需要手动配置开启,适合高并发大流量的场景。

二、数据结构与底层实现(中高级必问,核心区分点)

1. Redis 5种基础数据类型的底层编码结构、转换规则、适用场景?

数据类型 底层编码 编码转换规则 核心适用场景
String int、embstr、raw 1. 整数值且在long范围内,用int编码;
2. 字符串长度≤44字节,用embstr(连续内存,一次分配);
3. 长度>44字节,用raw编码
缓存、计数器、分布式锁、Session存储
Hash ziplist(压缩列表)、hashtable(哈希表) 1. 哈希对象所有键值对长度<64字节,且键值对数量<512个,用ziplist;
2. 任意一个条件不满足,转为hashtable
结构化对象存储(用户信息、商品属性)、批量字段更新
List ziplist、quicklist(3.2+默认) 3.2版本之前用ziplist+linkedlist,之后统一用quicklist(双向链表+ziplist分段),平衡内存和读写性能 消息队列、排行榜、分页列表、发布订阅
Set intset(整数集合)、hashtable 1. 集合所有元素都是整数,且元素数量<512个,用intset;
2. 任意一个条件不满足,转为hashtable
去重、交集并集差集计算(好友推荐)、抽奖
ZSet(Sorted Set) ziplist、skiplist(跳表)+ hashtable 1. 有序集合元素数量<128个,且每个元素长度<64字节,用ziplist;
2. 任意一个条件不满足,转为skiplist+hashtable
排行榜、延时队列、范围查询、带权重的任务调度

补充踩分点:ziplist是连续内存的紧凑型结构,节省内存,但修改操作可能触发连锁更新(某个元素长度变化,导致后续所有元素的偏移量都要重新计算),大流量写入场景要避免频繁修改ziplist编码的结构。

2. 跳表的原理是什么?Redis为什么用跳表而不用红黑树实现ZSet?

跳表原理

跳表是基于有序链表的多层索引结构,底层是完整的有序链表,上层是下层的稀疏索引,每一层都是有序的,查询时从最高层开始,逐层向下查找,把有序链表的O(n)查询复杂度降到O(logn),插入、删除的复杂度也为O(logn)。

跳表对比红黑树的优势(Redis选型原因)
  1. 范围查询更高效:跳表的范围查询只需要找到起点,然后沿着底层链表遍历即可;红黑树的范围查询需要中序遍历,实现更复杂,效率更低。
  2. 实现更简单,维护成本更低:跳表的插入、删除只需要修改相邻节点的指针,红黑树需要频繁的旋转操作来维持平衡,实现复杂,且高并发下加锁粒度更大。
  3. 内存占用更灵活:跳表的层数可以根据元素数量动态调整,红黑树的平衡规则固定,内存占用相对固定。
  4. 对缓存更友好:跳表的底层是连续的链表结构,局部性更好,CPU缓存命中率更高。

3. Redis 高级数据类型的原理、适用场景与限制?

  1. Bitmap(位图)
    • 原理:基于String类型实现的位操作,每个位代表一个状态,1字节可以存储8个状态,极致节省内存。
    • 适用场景:签到统计、用户活跃状态、黑白名单、布尔值批量存储。
    • 限制:最大支持512MB的位图,对应2^32个bit位,偏移量过大会导致Redis阻塞。
  2. HyperLogLog
    • 原理:基于概率算法的基数统计结构,用12KB内存就能统计2^64个元素的去重数量,标准误差率0.81%。
    • 适用场景:UV统计、页面访问量去重、大规模数据的基数估算。
    • 限制:只能统计基数,无法获取具体的元素值,有固定的误差率。
  3. Geo
    • 原理:基于ZSet实现,用GeoHash算法把经纬度编码成整数,存入ZSet的score中,实现地理位置的范围查询、距离计算。
    • 适用场景:附近的人、门店距离排序、外卖骑手位置匹配。
    • 限制:只能处理地球球面的坐标,不支持高精度的地理计算,GeoHash有边缘误差。
  4. Stream
    • 原理:Redis 5.0新增的消息队列结构,基于基数树实现,支持消息持久化、消费者组、消息确认、消息回溯,是Redis最完善的消息队列实现。
    • 适用场景:轻量级消息队列、异步任务处理、事件流存储。
    • 限制:没有专业MQ的多副本、高可用保障,不适合大规模的消息堆积场景。

三、持久化机制(中高级必问,生产核心知识点)

1. RDB和AOF持久化的原理、优缺点?生产怎么选?

RDB持久化
  • 原理:把Redis某一时刻的全量内存数据,生成快照文件(dump.rdb)保存到磁盘,是全量持久化。
  • 触发方式:手动触发(save/bgsave)、自动触发(配置save规则)、主从全量复制触发、shutdown触发。
  • 优点
    1. 快照文件是单文件,适合备份、灾备,恢复速度远快于AOF。
    2. 对Redis性能影响小,bgsave是fork子进程完成持久化,主线程几乎不阻塞。
  • 缺点
    1. 无法做到实时持久化,宕机会丢失最后一次快照之后的所有数据。
    2. 大数据量下,fork子进程耗时高,会导致主线程短暂阻塞,大内存实例尤为明显。
AOF持久化
  • 原理:把Redis的每一条写命令,以追加的方式写入AOF日志文件,是增量持久化。
  • 刷盘策略
    1. always:每一条命令都刷盘,数据零丢失,但性能极差,生产几乎不用。
    2. everysec(默认):每秒刷一次盘,宕机最多丢失1秒的数据,平衡性能和安全性。
    3. no:交给操作系统控制刷盘,性能最好,宕机丢失数据最多,生产不推荐。
  • 优点
    1. 数据安全性高,默认配置最多丢失1秒数据,适合对数据完整性要求高的场景。
    2. AOF重写机制可以压缩日志文件,清理无效命令。
  • 缺点
    1. 相同数据集下,AOF文件比RDB文件大,恢复速度远慢于RDB。
    2. 高并发写入场景下,AOF的刷盘会对性能有一定影响。

生产建议:Redis 4.0+ 推荐开启混合持久化,兼顾RDB的恢复速度和AOF的数据安全性;如果是纯缓存场景,对数据丢失不敏感,可以只开RDB;如果需要保证数据不丢失,必须开启AOF+混合持久化。

2. AOF重写机制是什么?重写过程中数据会不会丢失?

AOF重写原理

AOF文件会随着写命令的增加越来越大,重写机制会读取当前Redis的全量内存数据,生成对应的最小化写命令集,写入新的AOF文件,替换掉旧的、包含大量无效命令的AOF文件,达到压缩文件的目的。重写不是对旧文件的读取修改,而是直接基于当前内存数据生成,效率更高。

重写触发条件
  1. 手动触发:执行BGREWRITEAOF命令。
  2. 自动触发:配置auto-aof-rewrite-min-size(触发重写的最小AOF文件大小)、auto-aof-rewrite-percentage(AOF文件增长比例),两个条件同时满足触发。
重写过程的数据安全性

重写过程中不会丢失数据,Redis做了双重保障:

  1. 重写由fork出的子进程完成,主线程依然可以正常处理写命令。
  2. 重写期间,主线程的写命令会同时写入旧AOF缓冲区AOF重写缓冲区,重写完成后,把重写缓冲区的增量命令追加到新的AOF文件中,再替换旧文件,保证重写期间的增量数据不会丢失。

3. 混合持久化是什么?解决了什么问题?

  • 原理:Redis 4.0+ 新增的持久化方式,开启后,AOF重写时,会把当前内存数据以RDB格式写入AOF文件的开头,重写期间的增量写命令以AOF格式追加到文件末尾。
  • 解决的核心问题
    1. 解决了纯RDB数据丢失多的问题:增量数据以AOF格式保存,最多丢失1秒数据。
    2. 解决了纯AOF恢复速度慢的问题:全量数据是RDB格式,加载速度远快于逐条执行AOF命令。
  • 生产配置:开启aof-use-rdb-preamble yes,配合appendonly yesappendfsync everysec使用,是目前生产环境的最优解。

四、高可用架构(中高级核心考点,架构设计必备)

1. Redis主从复制的原理?全量复制和增量复制的流程?

主从复制是Redis高可用的基础,实现数据的多副本备份、读写分离,主节点负责写,从节点负责读。

核心原理

主节点把写命令同步给从节点,从节点执行相同的命令,保证主从数据一致,分为全量复制增量复制两种模式。

全量复制流程(首次同步、主从断开过久无法增量复制时触发)
  1. 从节点发送SYNC/PSYNC命令给主节点,请求同步。
  2. 主节点执行bgsave生成RDB快照文件,期间的写命令存入复制缓冲区。
  3. 主节点把RDB文件发送给从节点,从节点清空本地数据,加载RDB文件。
  4. 主节点把复制缓冲区的增量写命令发送给从节点,从节点执行,完成全量同步。
  5. 后续主节点的写命令,会异步发送给所有从节点执行,维持数据一致。
增量复制流程(网络闪断重连后,支持部分同步)
  1. 主从断开连接后,主节点会把断开期间的写命令存入复制积压缓冲区(环形固定大小的缓冲区)。
  2. 从节点重连后,发送PSYNC 主节点runid 复制偏移量给主节点,请求增量同步。
  3. 主节点校验runid是否匹配,且复制偏移量是否在复制积压缓冲区范围内,满足则发送增量命令给从节点,从节点执行后完成同步,无需全量复制。

踩分点:复制积压缓冲区的大小默认1MB,生产环境建议调大,避免主从频繁触发全量复制。

2. 哨兵(Sentinel)的作用?故障转移的完整流程?

哨兵是Redis的高可用组件,实现主从集群的自动故障转移,解决主节点单点故障的问题。

哨兵的核心作用
  1. 监控:持续监控主节点、从节点的健康状态。
  2. 通知:节点故障时,通过API通知管理员或其他应用。
  3. 自动故障转移:主节点故障时,自动选举一个从节点升级为新的主节点,其他从节点切换到新主节点同步数据。
  4. 配置中心:客户端连接哨兵集群,获取当前主节点地址,故障转移后自动更新。
故障转移完整流程
  1. 主观下线:单个哨兵节点检测到主节点超时未响应,标记为主观下线。
  2. 客观下线:该哨兵向其他哨兵节点发送询问,超过配置的quorum数量的哨兵都认为主节点下线,标记为客观下线。
  3. 哨兵leader选举:通过Raft算法选举出一个哨兵leader,负责执行故障转移。
  4. 从节点选举:leader从健康的从节点中,选举出最优的从节点(优先级>复制偏移量>runid小),升级为新的主节点。
  5. 故障转移执行
    • 给新主节点发送slaveof no one,升级为主节点。
    • 给其他从节点发送slaveof 新主节点地址,切换到新主节点同步数据。
    • 把旧主节点标记为从节点,恢复后自动同步新主节点的数据。

生产建议:哨兵集群至少部署3个节点,且部署在不同的机器上,quorum设置为哨兵数量的一半+1,保证脑裂时能正确选举。

3. Redis Cluster集群的核心原理?槽位分配与数据路由规则?

Redis Cluster是Redis官方的分布式集群方案,实现数据的分片存储,水平扩展,解决单节点内存上限、并发上限的问题。

核心原理
  1. 数据分片:把所有数据划分为16384个哈希槽(slot),每个节点负责一部分槽位,数据根据key计算对应的槽位,存储到负责该槽位的节点。
  2. 槽位计算:对key做CRC16校验,结果对16384取模,得到对应的槽位(slot = CRC16(key) % 16384)。
  3. 去中心化:集群每个节点都保存完整的槽位映射关系,节点之间通过Gossip协议通信,交换节点状态和槽位信息。
数据路由规则
  1. 客户端发送命令给任意集群节点,节点计算key对应的槽位。
  2. 如果槽位是当前节点负责的,直接执行命令,返回结果。
  3. 如果槽位是其他节点负责的,返回MOVED重定向指令,告诉客户端槽位对应的节点地址,客户端再向正确的节点发送命令。
  4. 集群扩容/缩容时,槽位在节点之间迁移,迁移期间会返回ASK重定向指令,引导客户端访问目标节点。

踩分点:Redis Cluster不支持跨槽位的多键操作(比如MSET、事务),除非key都在同一个槽位,可以用{}哈希标签强制把多个key分配到同一个槽位。

4. Redis的脑裂问题是什么?怎么解决?

脑裂问题

Redis的主节点因为网络分区,和从节点、哨兵集群断开连接,但主节点本身还在正常运行,客户端依然在向旧主节点写入数据。此时哨兵集群会选举出新的主节点,集群出现两个主节点,网络恢复后,旧主节点会降级为从节点,同步新主节点的数据,导致网络分区期间写入旧主节点的数据全部丢失。

解决方案

通过Redis的两个核心配置限制,避免脑裂数据丢失:

  1. min-replicas-to-write 1:主节点至少有1个健康的从节点连接,才允许写入数据。
  2. min-replicas-max-lag 10:从节点的复制延迟最大不能超过10秒,否则主节点拒绝写入。

原理:网络分区后,主节点和从节点断开连接,不满足min-replicas-to-write的条件,主节点会拒绝新的写入,网络恢复后不会出现数据丢失的问题,生产环境必须配置这两个参数。


五、缓存核心问题与解决方案(面试必问,生产踩坑重灾区)

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

问题 定义 核心原因 解决方案
缓存穿透 大量请求查询不存在的数据,请求直接穿透缓存打到数据库,导致数据库压力过大 1. 业务代码异常,传入不存在的key;
2. 恶意攻击,用不存在的key刷接口
1. 缓存空值:查询结果为空也缓存,设置短过期时间;
2. 布隆过滤器:把所有存在的key存入布隆过滤器,请求先经过过滤器,不存在的key直接拒绝;
3. 接口参数校验,拦截非法请求
缓存击穿 某个热点key过期,瞬间大量请求打到数据库,导致数据库压力骤增 热点key过期,同时有大量并发请求访问该key 1. 热点key永不过期:物理上不设置过期时间,后台异步更新;
2. 互斥锁:key过期时,只有一个线程能去查询数据库更新缓存,其他线程等待重试;
3. 逻辑过期:缓存中保存过期时间,过期后异步更新缓存,同步请求直接返回旧数据
缓存雪崩 大量缓存key同时过期,或者Redis集群宕机,大量请求直接打到数据库,导致数据库宕机 1. 大量key设置了相同的过期时间,同时失效;
2. Redis集群故障,无法提供服务
1. 过期时间加随机值:给key的过期时间加上随机偏移量,避免同时过期;
2. 服务熔断与降级:Redis故障时,熔断缓存请求,返回降级数据,保护数据库;
3. Redis集群高可用:搭建主从+哨兵/Cluster集群,避免单点故障;
4. 多级缓存:本地缓存+Redis缓存,减少Redis宕机的影响

2. 缓存与数据库的一致性怎么保证?各种方案的坑点?

缓存和数据库的一致性,核心是最终一致性,强一致性只能通过分布式事务实现,性能极低,生产几乎不用,主流方案如下:

方案1:先更新数据库,再更新缓存
  • 坑点:并发场景下,两个线程同时更新,会出现“先更新数据库的线程后更新缓存”,导致缓存和数据库数据不一致;且频繁更新会导致缓存被频繁改写,浪费性能,生产不推荐
方案2:先删除缓存,再更新数据库
  • 坑点:并发场景下,线程1删除缓存后,还没更新数据库,线程2查询到旧数据,把旧数据写回缓存,导致缓存一直是脏数据,仅适用于低并发场景
方案3:延迟双删(先删缓存,更新数据库,延迟一段时间再删缓存)
  • 原理:解决方案2的并发脏数据问题,第二次延迟删除,把线程2写回的脏数据删掉,保证最终一致。
  • 坑点:延迟时间必须大于“读请求的耗时+写缓存的耗时”,否则依然会有脏数据;第二次删除失败会导致脏数据,需要重试机制。
方案4:先更新数据库,再删除缓存(Cache Aside Pattern,业界主流方案)
  • 原理:读请求先读缓存,缓存没有就读数据库,把数据写回缓存;写请求先更新数据库,成功后删除缓存。
  • 优势:并发冲突的概率极低,只有“缓存刚好失效,读请求读到旧数据,写请求更新完数据库删除缓存之前,读请求把旧数据写回缓存”的极端场景才会出现不一致,概率极低。
  • 坑点:删除缓存失败会导致脏数据,解决方案:
    1. 引入消息队列,删除失败后重试;
    2. 监听数据库binlog(用Canal),异步消费binlog更新/删除缓存,保证最终一致。

六、生产性能优化与问题排查(中高级核心能力,区分普通开发和高级开发)

1. 什么是BigKey?有什么危害?怎么发现和处理?

BigKey定义
  • String类型:value大小超过10KB。
  • 集合类型:元素数量超过1万个,或者总大小超过100MB。
    (具体阈值根据生产环境调整,核心是占用内存大、操作耗时高的Key)
BigKey的危害
  1. 阻塞Redis:对BigKey的读写、删除操作,会导致主线程长时间阻塞,Redis卡顿。
  2. 内存占用高:BigKey占用大量内存,导致内存碎片、OOM,触发内存淘汰。
  3. 主从同步异常:BigKey的同步会导致主从延迟增大,甚至主从断开。
  4. 集群槽位迁移卡顿:集群模式下,BigKey的迁移会导致节点阻塞,迁移失败。
发现BigKey
  1. 官方工具:redis-cli --bigkeys,扫描所有Key,找出各类型的最大Key。
  2. 生产扫描:用SCAN命令分批扫描,避免KEYS *阻塞Redis。
  3. 监控告警:通过Redis的INFO stats中的latest_fork_usec、慢查询日志,定位BigKey。
处理BigKey
  1. 拆分BigKey:把大的String拆分成多个小的String;把大的Hash/List/Set拆分成多个小的集合。
  2. 惰性删除:不要直接用DEL删除BigKey,用UNLINK命令异步删除,避免主线程阻塞。
  3. 设置合理的过期时间:避免无用的BigKey长期占用内存。

2. 什么是HotKey?有什么危害?怎么发现和解决?

HotKey定义

某个Key的访问量远高于其他Key,导致该Key所在的Redis节点CPU负载过高,比如秒杀场景的商品库存Key,每秒访问量几十万。

HotKey的危害
  1. Redis节点CPU打满:HotKey的请求都打到单个节点,导致该节点CPU过载,无法处理其他请求。
  2. 集群负载不均:单个节点压力过大,其他节点空闲,集群整体性能下降。
  3. 缓存击穿风险:HotKey过期,瞬间大量请求打到数据库,导致数据库宕机。
发现HotKey
  1. Redis 4.0+:开启hotkey检测,redis-cli --hotkeys直接扫描热点Key。
  2. 客户端监控:在SDK层统计Key的访问次数,上报热点Key。
  3. 代理层监控:通过Twemproxy、Codis等代理,统计热点Key。
  4. 机器监控:通过Redis节点的CPU、网卡流量,定位热点节点,再找出热点Key。
解决HotKey
  1. 本地缓存:把HotKey缓存到应用的本地内存(Caffeine、Guava Cache),请求直接访问本地缓存,减少Redis的访问量。
  2. Key拆分:把HotKey复制成多个副本Key,分散到不同的Redis节点,请求随机访问副本,分散压力。
  3. Redis集群多副本:开启Redis Cluster的副本读,把读请求分散到从节点,减轻主节点压力。
  4. 热点Key永不过期:避免热点Key过期导致的缓存击穿。

3. Redis慢查询怎么分析?怎么优化?

慢查询配置

Redis会把执行时间超过slowlog-log-slower-than(单位微秒,默认10000微秒=10毫秒)的命令,存入慢查询日志,日志最多保存slowlog-max-len条(默认128条)。

分析慢查询
  1. 查看慢查询列表:SLOWLOG GET N,查看最近N条慢查询。
  2. 慢查询日志包含:命令执行的时间戳、执行耗时、执行的命令、客户端地址。
  3. 重点关注:执行耗时超过1ms的命令,尤其是批量操作、全量遍历命令。
慢查询优化方案
  1. 禁止使用高耗时命令:生产环境禁止使用KEYS *FLUSHALLFLUSHDB等命令,用SCAN代替KEYS
  2. 优化集合操作:避免对大集合执行HGETALLSMEMBERSZRANGE全量查询,用分批查询、分页查询代替。
  3. 减少命令执行次数:用Pipeline批量执行命令,减少网络IO次数,避免大量单条命令执行。
  4. 优化BigKey:拆分BigKey,避免对BigKey的操作耗时过高。
  5. 合理使用数据结构:比如用Hash代替多个String存储对象,减少命令次数。

七、分布式场景应用(中高级开发必备,架构设计高频考点)

1. 怎么用Redis实现分布式锁?需要满足哪些特性?有什么坑?

分布式锁需要满足的核心特性
  1. 互斥性:同一时间只有一个客户端能持有锁。
  2. 防死锁:客户端持有锁期间宕机,锁能自动释放,不会一直占用。
  3. 可重入性:同一个客户端已经持有锁,再次加锁能成功。
  4. 容错性:只要大部分Redis节点正常,就能正常加锁释放锁。
  5. 解铃还须系铃人:只能由加锁的客户端释放锁,不能被其他客户端释放。
Redis分布式锁的正确实现
  1. 基础加锁:用SET lock_key unique_value NX EX expire_time命令,原子性加锁,NX表示只有key不存在才加锁成功,EX设置过期时间,避免死锁;unique_value是客户端唯一标识,保证只有加锁的客户端能释放锁。
  2. 锁释放:用Lua脚本原子性判断锁的持有者,再删除锁,避免误删:
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end
  1. 可重入性:用Hash结构存储锁的持有者和重入次数,加锁时判断持有者,重入则次数+1,释放时次数-1,次数为0则删除锁,Redisson的可重入锁就是该实现。
  2. 锁续期:加锁成功后,开启守护线程(看门狗),每隔一段时间(过期时间的1/3)刷新锁的过期时间,避免业务执行时间超过锁的过期时间,导致锁提前释放。
  3. 集群容错:Redis集群模式下,主节点宕机可能导致锁丢失,用Redlock算法(向多个独立的Redis节点加锁,超过半数加锁成功才算持有锁)解决,生产环境推荐用Redisson的Redlock实现。
常见坑点
  1. 锁的过期时间设置不合理,业务没执行完锁就过期了,导致并发问题。
  2. SETNX + EXPIRE两条命令加锁,不是原子操作,中间宕机导致锁无法过期,死锁。
  3. 释放锁时没有判断持有者,直接DEL,导致误删其他客户端的锁。
  4. 集群模式下,主节点加锁成功后还没同步给从节点就宕机,新主节点没有锁信息,导致多个客户端同时持有锁。

2. 怎么用Redis实现分布式限流?常见的限流算法?

Redis实现分布式限流,核心是利用Redis的单线程原子性,保证限流规则的准确执行,主流算法如下:

1. 固定窗口限流(最简单)
  • 原理:把时间划分为固定的窗口(比如1分钟),每个窗口内统计请求次数,超过阈值则拒绝请求,窗口结束后重置计数。
  • Redis实现:用String类型,key为限流标识+窗口时间INCR计数,配合过期时间设置窗口长度。
  • 优点:实现简单,容易理解。
  • 缺点:存在临界问题,比如窗口切换的瞬间,可能出现两倍阈值的请求。
2. 滑动窗口限流(解决固定窗口临界问题)
  • 原理:把固定窗口拆分为多个小的时间格子(比如1分钟拆分为6个10秒的格子),每次请求统计当前时间往前推一个窗口长度内的所有格子的请求总数,超过阈值则拒绝,时间推进时,过期的格子会被删除。
  • Redis实现:用ZSet存储请求的时间戳和唯一标识,每次请求删除过期的记录,统计剩余的记录数,超过阈值则拒绝。
  • 优点:限流更平滑,解决了固定窗口的临界问题。
  • 缺点:实现相对复杂,大流量下ZSet会占用较多内存。
3. 漏桶算法
  • 原理:请求像水一样进入漏桶,漏桶以固定的速率出水(处理请求),桶满了则拒绝请求,强制控制请求的处理速率。
  • Redis实现:用List作为桶,请求入队,固定速率出队处理,或者用Redis的CL.THROTTLE命令(Redis Cell模块)实现。
  • 优点:严格控制请求的处理速率,平滑突发流量。
  • 缺点:无法应对突发流量,即使后端服务空闲,也只能以固定速率处理。
4. 令牌桶算法(生产最常用)
  • 原理:以固定的速率往令牌桶里添加令牌,桶满了则不再添加,请求过来需要拿到一个令牌才能处理,没有令牌则拒绝请求,既能控制平均速率,又能应对突发流量。
  • Redis实现:用String存储令牌数量,定时任务添加令牌,或者用Lua脚本原子性判断令牌数量、扣减令牌。
  • 优点:平滑限流的同时,能应对突发流量,适配大多数业务场景,生产环境首选。

八、高频补充面试题(进阶加分项)

  1. Redis的事务有什么特性?和MySQL的事务有什么区别?

    • Redis事务的特性:保证命令的批量串行执行,执行期间不会被其他客户端的命令打断,提供MULTI、EXEC、DISCARD、WATCH四个命令,实现简单的事务。
    • 和MySQL事务的核心区别:Redis事务不支持回滚,命令执行出错,已经执行的命令不会回滚;且Redis事务不支持ACID的隔离性、持久性,仅保证原子性(批量命令要么全执行,要么全不执行)。
  2. Pipeline、事务、Lua脚本的区别?

    • Pipeline:把多个命令打包发送给Redis,一次性返回结果,减少网络IO次数,不保证原子性,命令之间会被其他客户端的命令打断。
    • 事务:保证多个命令的原子性执行,执行期间不会被打断,但不支持回滚,需要多次网络交互。
    • Lua脚本:把多个命令写在Lua脚本中,Redis原子性执行整个脚本,执行期间不会被其他命令打断,支持复杂逻辑,一次网络交互,是Redis中最推荐的原子操作方案
  3. Redis的内存碎片是什么?怎么产生的?怎么优化?

    • 内存碎片:Redis申请和释放内存时,导致出现大量不连续的空闲内存块,无法被利用,导致Redis实际占用的内存远大于数据存储的内存。
    • 产生原因:频繁的修改、删除Key,导致内存分配释放频繁;不同大小的Value分配的内存块不连续。
    • 优化方案:
      1. Redis 4.0+ 开启内存碎片自动整理:activedefrag yes
      2. 避免频繁更新BigKey,减少内存的频繁分配释放。
      3. 手动触发内存整理:MEMORY DEFRAG(低峰期执行)。
      4. 重启Redis节点,重新加载数据,彻底清理内存碎片。
  4. Redis 7.0的核心新特性有哪些?

    • 新增Functions功能,替代Lua脚本,支持持久化、复用,更适合复杂逻辑开发。
    • 集群模式增强,支持分片发布订阅,解决了集群模式下发布订阅只能全节点广播的问题。
    • AOF持久化增强,支持AOF文件分卷存储,解决了AOF文件过大的问题,重写更高效。
    • 内存管理优化,提升了大内存场景下的性能,减少内存碎片。
    • 客户端缓存增强,支持服务端推送失效通知,提升客户端缓存的效率。

如果你需要,我可以把这份面试题整理成精简版背诵提纲,或者按面试难度拆分为「基础必背」「进阶高频」「难点加分」三个模块,方便你针对性复习。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐