Redis面试题

Redis 是面试中的高频考点,重点集中在 数据结构、持久化机制、高可用(主从 / 哨兵 / 集群)、缓存问题(穿透 / 击穿 / 雪崩)、性能优化 等方面。

什么是Redis

Redis(Remote Dictionary Server)是一个开源的高性能键值对(key-value)数据库,它将数据存储在内存中,因此具有极高的读写速度,同时也支持数据持久化到磁盘,以防止数据丢失。

  1. 基于内存存储:数据主要存于内存,读写速度极快(毫秒级响应),适合高频访问场景。
  2. 支持多种数据结构:不仅支持字符串(String),还包括哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)、位图(Bitmap)、地理空间(Geospatial)等,灵活满足不同业务需求。
  3. 持久化机制:通过 RDB(快照)和 AOF(日志追加)两种方式将内存数据持久化到磁盘,平衡性能与数据安全性。
  4. 高并发与扩展性:支持主从复制、哨兵模式(Sentinel)和集群(Cluster),可实现高可用和水平扩展,应对海量请求。
  5. 丰富的功能:提供事务、发布订阅、Lua 脚本、过期键自动删除等功能,适合缓存、计数器、消息队列、排行榜等场景。

Redis系统架构模型

Redis采用单线程 + 多路复用 + 内存存储:

  • Redis 的核心读写操作由单个线程处理(避免多线程上下文切换开销),通过 “串行化” 处理命令保证原子性(无需锁机制)。

  • 基于操作系统的 IO 多路复用机制(如 Linux 的 epoll、Windows 的 IOCP),单线程可同时监听多个客户端连接的 IO 事件(读 / 写请求),高效处理并发请求。

  • 所有数据默认存储在内存中,读写速度极快(微秒级响应),远超磁盘数据库。

Redis为什么快

  • Redis的大部分操作都是基于内存完成的,并且采用了高效的数据结构,因此Redis瓶颈可能是机器的内存和网络带宽。
  • Redis采用单线程的话避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销
  • Redis采用了I/O多路复用机制处理大量的客户端Socket请求,IO多路复用机制是指一个线程处理多个IO流,就是我们经常听到select/epoll机制。简单来说,在Redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听Socket和已连接Socket。内核会一直监听这些Socket上的连接请求或数据请求。一旦有请求到达,就会交给Redis线程处理,这就实现了一个Redis线程处理多个IO流的效果。

Redis的基本数据类型

数据类型 核心特性 常用命令(示例) 典型应用场景
字符串(String) 存储文本、数字、二进制数据;最大 512MB;支持原子操作和过期时间 SET/GETINCR/DECRAPPENDEXPIRE 缓存简单数据(用户信息)、计数器(阅读量)、分布式锁
哈希(Hash) 键值对集合,适合存储对象;可单独操作字段;节省内存 HSET/HGETHGETALLHDELHLEN 存储对象信息(用户资料、商品属性)
列表(List) 有序、可重复元素;双向操作;底层为链表 / 压缩列表 LPUSH/RPUSHLPOP/RPOPLRANGE 消息队列、最新列表(朋友圈动态)、栈 / 队列实现
集合(Set) 无序、不可重复元素;支持交集 / 并集 / 差集;查找效率 O (1) SADDSMEMBERSSISMEMBERSINTER(交集)、SUNION(并集) 标签系统(文章标签)、好友关系(共同好友)、去重操作
有序集合(Sorted Set) 元素关联分数(score),按分数排序;支持范围查询;底层为跳表 ZADDZRANGE(升序)、ZREVRANGE(降序)、ZSCOREZINCRBY 排行榜(游戏积分、销量排名)、优先级队列、范围统计(分数区间用户)
位图(Bitmap) 二进制位操作(String 扩展);适合存储布尔值序列;节省空间 SETBIT(设定位)、GETBIT(获取位)、BITCOUNT(统计 1 的个数) 用户签到、在线状态、权限标记
地理空间(Geospatial) 存储经纬度;支持距离计算、范围查询 GEOADD(添加坐标)、GEODIST(计算距离)、GEORADIUS(查找范围内元素) 附近的人、LBS 服务(附近门店)
流(Stream) 专为消息队列设计;支持持久化、消费确认、分组消费 XADD(添加消息)、XREAD(读取消息)、XGROUP(创建消费组) 高可靠消息队列(日志收集、异步任务)
HyperLogLog 近似统计基数(不重复元素数);占用内存极小(约 12KB);误差率低 PFADD(添加元素)、PFCOUNT(统计基数)、PFMERGE(合并多个 HyperLogLog) 海量数据去重统计(UV 计数、访问 IP 去重)

Redis各数据类型有哪些应用场景?

Redis在互联网产品中使用的场景实在是太多太多,这里分别对Redis几种数据类型做了整理:

  • String:缓存、限流、分布式锁、计数器、分布式Session等。
  • Hash:用户信息、用户主页访问量、组合查询等。
  • List:简单队列、关注列表时间轴。
  • Set:赞、踩、标签等。
  • ZSet:排行榜、好友关系链表。

Redis 中 set和zset区别是什么?

对比维度 Set(集合) ZSet(有序集合)
有序性 无序。元素存储和返回的顺序与插入顺序无关(底层哈希表实现)。 有序。每个元素关联一个分数(score),元素按分数升序排列(底层跳表 + 哈希表实现)。
元素唯一性 元素不可重复(重复插入会被忽略)。 元素不可重复(重复插入会覆盖原元素的分数),但分数可以重复。
核心特性 侧重去重集合运算(交集、并集、差集)。 侧重排序范围查询(按分数筛选、取前 N 名等)。
常用命令 - 添加:SADD key member- 查所有:SMEMBERS key- 交集:SINTER key1 key2- 判断存在:SISMEMBER key member - 添加:ZADD key score member(需指定分数)- 升序查:ZRANGE key start end- 降序查:ZREVRANGE key start end- 查分数:ZSCORE key member- 分数自增:ZINCRBY key increment member
查询效率 单元素查询 O (1),但无法按顺序或范围批量查询(只能全量获取)。 支持按分数范围查询(如 ZRANGEBYSCORE),时间复杂度 O (logN + M)(N 为总数,M 为结果数),效率高。
内存占用 仅存储元素本身,内存占用较低。 需额外存储每个元素的分数,内存占用略高于 Set。

Redis哪些地方使用了多线程?

网络 IO 处理(Redis 6.0+):引入了多线程处理网络 IO,主要解决 “网络读写瓶颈” 问题。

主线程负责命令的解析、执行和结果处理,而网络数据的读取(read)和写入(write) 由专门的 IO 线程处理。

异步删除大键

当删除超大键(如包含百万级元素的哈希表、列表)时,直接删除会阻塞主线程。Redis 引入了后台线程(bio 线程) 异步处理删除操作:

  • 使用 unlink 命令替代 del 时,Redis 会将删除操作丢给后台线程,主线程无需等待,继续处理其他命令。
  • 类似的,flushdb asyncflushall async 等异步清空命令也依赖后台线程执行。

AOF 日志刷盘(fsync 操作)

AOF(Append Only File)持久化中,数据先写入内存缓冲区,再定期刷到磁盘。其中 fsync 系统调用(将缓冲区数据强制写入磁盘) 可能阻塞主线程,因此 Redis 提供了多线程选项:

  • 配置 appendfsync everysec 时,Redis 会使用后台线程每隔 1 秒执行一次 fsync,避免主线程阻塞。

RDB 持久化(部分阶段)

RDB 持久化通过 fork 子进程生成快照,子进程负责写入数据到磁盘,不影响主线程。虽然 fork 本身是单线程操作(主线程短暂阻塞),但子进程的 IO 写入过程本质上是独立的 “线程 / 进程” 行为,可视为多进程 / 线程协作的场景。

Redis怎么实现的io多路复用?

这里的“多路”指的其实是多个网络连接客户端,“复用”指的是同一个线程。I/O多路复用其实是使用一个线程来检查多个Socket的就绪状态,在单个线程中通过记录跟踪每一个socket的状态来管理处理多个I/O流。

img

如何实现redis 原子性?

Redis 原子性的核心是单线程串行执行,在此基础上通过事务、Lua 脚本扩展多命令原子性,并用原生命令覆盖高频场景,兼顾效率与一致性。

1. 单线程执行模型:单个命令的天然原子性

Redis 的核心命令执行是单线程的(网络 IO 等辅助操作可多线程,但命令逻辑串行)。这意味着:

  • 同一时间只会执行一个命令,不会有 “并行修改” 冲突;
  • 单个命令从读取、处理到结果写入的全过程,不会被其他命令打断。因此,所有单个 Redis 命令本身就是原子操作(比如INCR、HSET等)。

2. 事务(MULTI/EXEC):多命令的原子性打包

当需要多个命令作为整体执行时,用事务机制:

  • 流程:MULTI 开启事务 → 后续命令入队(不执行)→ EXEC 触发所有命令按顺序原子执行。
  • 原子性保证:EXEC 执行期间,单线程会串行处理队列中所有命令,中间不会插入其他客户端命令;若入队时有语法错误,事务会整体取消。
  • 注意:Redis 事务不支持回滚(执行中某命令失败,其他仍会继续),主要保证 “要么全执行,要么全不执行”。

3. Lua 脚本:复杂逻辑的原子性

对于需要依赖中间结果的复杂逻辑(如带条件判断),用 Lua 脚本:

  • 原理:整个脚本会被 Redis 作为 “单个单元” 执行,执行期间不会被任何其他命令打断。
  • 优势:支持 if-else 等逻辑,比事务更灵活;一次脚本调用完成多命令,减少网络开销。

4. 原生原子命令:高频场景的简化支持

Redis 针对常见场景设计了原生原子命令,避免用户手动组合命令的风险,比如:

  • INCR/DECR:原子性完成 “读 - 改 - 写” 自增;
  • SETNX:仅当 key 不存在时设置值,原子性实现加锁;
  • RPOPLPUSH:原子性转移列表元素。

Redis 事务的基本命令有哪些?执行流程?

核心命令MULTI(开启事务)、EXEC(执行事务队列)、DISCARD(放弃事务,清空队列)、WATCH(监视键,实现乐观锁)。

执行流程

  1. MULTI 开启事务,客户端进入 “命令入队模式”;
  2. 后续命令不会立即执行,而是被放入事务队列(返回 “QUEUED”);
  3. 执行 EXEC 时,Redis 按顺序原子性执行队列中所有命令;若执行 DISCARD,则清空队列,放弃事务。

Redis的持久化

为了保证内存中的数据不会丢失,Redis实现列数据了持久化机制,这个机制会把数据存储在磁盘,这样在Redis重启能够恢复数据。

AOF 日志

AOF 是命令日志,记录所有写操作命令(如 SETINCR 等),以文本形式追加到文件(默认 appendonly.aof)。恢复时,Redis 会重新执行日志中的所有命令,重建数据。

工作流程

  1. 命令追加:执行写命令后,先将命令追加到内存中的 AOF 缓冲区(避免频繁 IO)。
  2. 文件写入:缓冲区数据定期同步到磁盘 AOF 文件(同步策略可配置)。
  3. 文件重写:AOF 文件会随命令增多而变大,通过 “重写” 机制压缩(合并重复命令,如多次 INCR 合并为最终值的 SET

关键机制

  • 同步策略(通过 appendfsync 配置,平衡安全性和性能):
    • always:每个命令执行后立即同步到磁盘(安全性最高,但 IO 开销大,性能差)。
    • everysec:每秒同步一次(默认,最多丢失 1 秒数据,平衡安全与性能)。
    • no:由操作系统决定何时同步(性能最好,但可能丢失大量数据)。
  • AOF 重写(解决文件过大问题):
    • 触发方式:手动执行 bgrewriteaof;或自动触发(配置 auto-aof-rewrite-percentageauto-aof-rewrite-min-size,当文件大小超过阈值且增长比例达标时触发)。
    • 过程:主线程 fork 子进程,子进程遍历内存数据,生成新的 AOF 日志(只保留最终状态的命令),期间新命令会写入 “重写缓冲区”,重写完成后合并到新文件,替换旧文件。

优缺点

  • 优点
    • 数据安全性高:可通过 everysec 配置将数据丢失控制在 1 秒内。
    • 日志是文本命令,易理解和修复(如误操作可手动编辑日志删除错误命令)。
  • 缺点
    • 文件体积大(文本命令,无压缩),恢复速度慢(需重新执行所有命令)。
    • 重写时 fork 子进程有开销,且高并发下 everysec 策略可能导致短暂阻塞(同步时若磁盘 IO 慢,主线程会等待)。

RDB 快照

RDB 是某一时刻内存数据的二进制快照(类似 “照片”),将当前内存中所有键值对以压缩的二进制形式写入磁盘(默认文件 dump.rdb)。恢复时,直接加载该文件到内存即可。

触发方式

  1. 手动触发
    • save:主线程直接执行快照,期间会阻塞所有客户端请求(不推荐,可能导致 Redis 卡顿)。
    • bgsave:主线程 fork 一个子进程,由子进程负责写入 RDB 文件,主线程继续处理命令(非阻塞,推荐)。
  2. 自动触发(通过配置文件 redis.conf
    • 配置 save m n:表示 “m 秒内有 n 次键修改” 时,自动触发bgsave
    • 其他场景:主从复制时主节点自动触发;执行 shutdown 命令且未开启 AOF 时,会触发 bgsave 确保数据落地。

优缺点

  • 优点
    • 文件体积小(二进制压缩),恢复速度快(直接加载到内存,无需解析命令)。
    • bgsave 不阻塞主线程,对性能影响小。
  • 缺点
    • 数据安全性低:快照间隔期间若 Redis 崩溃,会丢失这段时间的数据(如配置 save 300 10,则可能丢失 300 秒内的数据)。
    • bgsavefork 操作有开销:fork 子进程时需复制父进程的内存页表,若内存大,fork 可能短暂阻塞主线程。

缓存淘汰和过期删除

Redis 中的过期删除缓存淘汰是两个关联但不同的机制:前者解决 “过期键如何被清理” 的问题,后者解决 “内存不足时如何淘汰键以释放空间” 的问题。两者共同保证了 Redis 高效利用内存并维持数据有效性。

一、过期删除策略(处理已过期的键)

Redis 允许为键设置过期时间(如 EXPIRE key 10 表示 10 秒后过期),当键过期后,需要通过特定策略将其从内存中删除。Redis 采用 **“惰性删除 + 定期删除” 的混合策略 **,而非 “立即删除”(避免频繁删除对性能的影响)。

1. 惰性删除(Lazy Expiration)

  • 原理:键过期后不会主动删除,而是在下次被访问时才检查是否过期:若过期则删除并返回 nil,否则正常返回值。
  • 优点:无需额外消耗 CPU 监控过期键,只在必要时处理,对性能影响小。
  • 缺点:若过期键长期未被访问,会一直占用内存(可能导致内存泄漏)。

2. 定期删除(Periodic Expiration)

  • 原理

    :Redis 每隔一段时间(默认 100ms 左右)会主动扫描部分过期键并删除,具体流程:

    1. 从过期键字典(记录所有键的过期时间)中随机抽取部分键;
    2. 删除其中已过期的键;
    3. 若删除比例超过 25%,则重复步骤 1(避免大量过期键堆积),否则结束本轮扫描。
  • 优点:主动清理部分过期键,减少 “惰性删除” 导致的内存浪费。

  • 缺点:扫描频率和范围需控制(避免长时间阻塞主线程),Redis 通过限制每次扫描的时间(默认不超过 25ms)来平衡性能。

二、缓存淘汰策略(内存不足时淘汰键)

当 Redis 内存使用达到配置的 maxmemory 阈值时,会触发缓存淘汰策略,淘汰部分键以释放内存(即使这些键未过期)。Redis 提供 8 种淘汰策略,可通过 maxmemory-policy 配置,核心分为三大类:

1. 只淘汰 “设置了过期时间” 的键(volatile-*)

  • volatile-lru:淘汰最近最少使用(LRU,Least Recently Used)的过期键。
  • volatile-lfu:淘汰最近最不常用(LFU,Least Frequently Used)的过期键(4.0+,比 LRU 多统计访问频率)。
  • volatile-ttl:淘汰剩余过期时间最短的键。
  • volatile-random:随机淘汰过期键。

2. 淘汰所有键(包括未设置过期时间的,allkeys-*)

  • allkeys-lru:淘汰所有键中最近最少使用的键。
  • allkeys-lfu:淘汰所有键中最近最不常用的键(4.0+)。
  • allkeys-random:随机淘汰所有键中的键。

3. 不淘汰键(noeviction)

  • noeviction(默认策略):不淘汰任何键,当内存不足时,拒绝所有新的写操作(返回错误),读操作正常。

主从同步中的增量和完全同步怎么实现?

完全同步

完全同步发生在以下几种情况:

  • 从节点首次连接主节点(无主节点的runid和同步偏移量offset);
  • 从节点重连时,主节点的runid已变化(如主节点重启);
  • 从节点的offset不在主节点的复制积压缓冲区范围内。

同步流程

  • 从节点发起请求:从节点发送PSYNC ? -1命令(?表示未知主节点runid-1表示无offset)。
  • 主节点生成 RDB:主节点收到请求后,执行bgsave生成全量数据的 RDB 快照(异步执行,不阻塞主节点处理新命令),同时将生成 RDB 期间的所有写命令记录到复制缓冲区(replication buffer,每个从节点独立维护)
  • 发送 RDB 给从节点:主节点将 RDB 文件发送给从节点,从节点接收后清空自身原有数据,加载 RDB 文件(此过程会阻塞从节点,无法处理读请求)。
  • 同步缓冲命令:RDB 发送完成后,主节点将复制缓冲区中记录的 “生成 RDB 期间的写命令” 发送给从节点,从节点执行这些命令,最终与主节点数据完全一致。

增量同步

增量同步是指主从节点已完成过完全同步,后续因网络短暂中断等原因重连时,主节点仅向从节点发送 “从节点缺失的那部分命令”,而非全量数据,以减少资源消耗。

  1. 正常同步阶段:主节点处理写命令时,会将命令同步给从节点,同时更新自身offset,并将命令写入复制积压缓冲区;从节点接收命令后执行,更新自身offset
  2. 网络中断与重连
    • 网络中断后,主节点继续处理命令并写入复制积压缓冲区,从节点暂停同步。
    • 网络恢复后,从节点发送PSYNC <master_runid> <slave_offset>命令(携带记录的主节点runid和自身当前offset)。
  3. 增量同步验证
    • 主节点检查runid是否匹配(确认是同一主节点),并判断从节点的offset是否在复制积压缓冲区的范围内(即offset >= 缓冲区起始位置)。
    • 若验证通过,主节点从offset开始,将缓冲区中后续的命令发送给从节点,从节点执行后更新offset,完成增量同步。

Redis主从和集群可以保证数据一致性吗 ?

Redis 的主从架构和集群(Redis Cluster)不能保证强一致性,但能在大多数场景下保证最终一致性,其数据一致性的保障程度受架构设计、同步机制和配置参数的影响,存在一定的局限性。(AP)

一、主从架构的一致性问题

主从架构(一主多从)的核心是 “主节点写入、从节点复制”,但同步机制的本质决定了它无法保证强一致性:

  • 异步复制的天然缺陷主节点处理写命令后,会立即返回结果给客户端,不会等待从节点同步完成。此时若主节点突然宕机,未同步到从节点的命令会丢失。从节点提升为主节点后,这部分数据会永久缺失,导致数据不一致。
  • 配置优化的局限性虽然 Redis 提供了一些参数减少数据丢失风险(如min-replicas-to-writemin-replicas-max-lag),要求主节点必须有至少 N 个从节点在max-lag时间内完成同步才允许写入,但这只能降低丢失概率,无法完全避免(如主从网络突然中断时,主节点可能已接受写入但未同步)。
  • 从节点读取的 “滞后性”从节点默认允许读操作,但由于同步存在延迟(即使毫秒级),客户端可能从从节点读取到 “旧数据”,出现 “读写不一致”。

二、Redis Cluster(集群)的一致性问题

Redis Cluster 通过分片(Sharding)将数据分散到多个主节点,每个主节点有从节点作为备份,其一致性问题本质与主从架构类似,且新增了分片间的协调问题:

  1. 分片内的异步复制集群中每个分片(主节点 + 从节点)的同步机制与主从架构一致,仍是异步复制。主节点写入后立即返回,未同步的命令在主节点宕机时可能丢失,导致分片内数据不一致。
  2. 故障转移的 “数据缺口”当主节点故障时,从节点通过选举成为新主节点。若从节点未完全同步主节点的最新数据(如同步延迟或网络中断),新主节点的数据会存在 “缺口”,与客户端预期的最新数据不一致。
  3. 跨分片操作的一致性集群中跨分片的命令(如MSET涉及多个 key 分布在不同分片)是非原子的,可能部分分片执行成功、部分失败,导致跨分片数据不一致。Redis Cluster 不支持分布式事务,无法保证跨分片操作的原子性。
  4. 最终一致性的前提只有在网络稳定、无节点故障、同步延迟可接受的情况下,集群内各节点的数据才会逐渐趋于一致(最终一致性)。但极端场景(如网络分区、节点频繁故障)可能导致长时间的数据不一致。

Redis 哨兵(Sentinel)机制

Redis 哨兵(Sentinel)机制是 Redis 官方提供的高可用解决方案,主要用于监控主从架构中的节点状态,并在主节点故障时自动完成故障转移(Failover),确保服务持续可用。

一、哨兵的核心功能

哨兵机制通过一组(通常为 3 个及以上)哨兵进程(Sentinel)协同工作,实现以下功能:

  1. 监控(Monitoring):持续检查主节点(master)和从节点(slave)是否正常运行。
  2. 自动故障转移(Automatic Failover):当主节点故障时,自动将一个从节点升级为新主节点,并让其他从节点切换到新主节点进行复制。
  3. 通知(Notification):当节点状态变化(如主节点下线、故障转移完成)时,通过 API(如 Redis 的发布订阅机制)通知管理员或客户端。
  4. 配置管理(Configuration Provider):客户端通过哨兵获取当前主节点的地址,无需硬编码主节点信息(当主节点切换后,客户端可自动获取新主节点地址)。

二、哨兵的架构设计

哨兵机制通常采用 “多哨兵集群”(而非单个哨兵),原因是:单个哨兵可能因自身故障误判主节点状态,而多哨兵通过 “共识机制” 可减少误判,提高可靠性。

典型架构如下:

  • 1 个主节点 + N 个从节点(主从复制架构);
  • M 个哨兵节点(M ≥ 3,且为奇数,便于选举决策);
  • 哨兵节点之间相互通信,同时也与所有主从节点保持连接。

三、哨兵的工作原理(核心流程)

哨兵的工作过程可分为监控与状态判断故障转移触发新主节点选举配置更新四个阶段。

1. 监控与状态判断(节点健康检测)

哨兵通过定期发送命令与主从节点通信,判断节点是否存活,核心涉及两个状态:

  • 主观下线(Subjectively Down, SDOWN):单个哨兵向某节点(主或从)发送PING命令,如果在配置的down-after-milliseconds时间内未收到有效响应(如超时、错误回复),则该哨兵认为此节点 “主观下线”(仅自身判断,可能存在误判)。
  • 客观下线(Objectively Down, ODOWN):仅针对主节点。当一个哨兵判断主节点 “主观下线” 后,会向其他哨兵发送is-master-down-by-addr命令,询问它们是否也认为该主节点下线。如果超过quorum(配置的最小哨兵数量)个哨兵均认为主节点 “主观下线”,则该主节点被标记为 “客观下线”(达成共识,确认故障)。

2. 故障转移触发(当主节点 “客观下线” 后)

主节点被标记为 “客观下线” 后,哨兵集群会启动故障转移流程,核心是先选举一个 “哨兵领导者”(Leader Sentinel),由它负责执行后续的故障转移操作(避免多个哨兵同时操作导致混乱)。

  • 哨兵领导者选举

    采用类似 Raft 算法的选举机制:

    1. 每个哨兵向其他哨兵发送vote-for-leader命令,请求将自己选为领导者;
    2. 每个哨兵在一轮选举中只能投票给一个候选者;
    3. 若某哨兵获得超过半数((M/2)+1,M 为哨兵总数) 的选票,则成为领导者;
    4. 若一轮选举未产生领导者,等待一段时间后重新选举,直到选出领导者。

3. 新主节点选举(从从节点中选一个升级为主节点)

哨兵领导者需从原主节点的所有从节点中,选择一个 “最优” 的从节点升级为新主节点,选择标准如下(优先级从高到低):

  1. 排除不健康的从节点:过滤掉 “主观下线”、连接断开时间过长(超过down-after-milliseconds * 10)的从节点。
  2. 优先级最高的从节点:通过replica-priority配置(默认 100,值越小优先级越高),优先选择优先级高的。
  3. 复制进度最完整的从节点:选择与原主节点的复制偏移量(offset)最接近的从节点(即数据最新的)。
  4. 运行时间最久的从节点:若前两项相同,选择runid(节点唯一标识)最小的(启动时间最早)。

4. 故障转移执行(更新拓扑结构)

选定新主节点后,哨兵领导者执行以下操作,完成故障转移:

  1. 向新主节点发送slaveof no one命令,使其停止作为从节点,升级为主节点。
  2. 向其他所有从节点发送slaveof <新主节点IP> <新主节点端口>命令,让它们切换到新主节点进行复制。
  3. 更新哨兵集群的配置信息(记录新主节点的地址、从节点列表等),并通过发布订阅机制(__sentinel__:hello频道)同步给所有哨兵。
  4. 通知客户端(通过客户端订阅的哨兵频道)新主节点的地址,客户端后续将请求发送到新主节点。

5. 原主节点恢复后的处理

若原主节点(故障节点)恢复上线,哨兵会将其自动转为新主节点的从节点,避免其再次成为主节点导致冲突。

Redis集群的模式了解吗 优缺点了解吗

Redis 集群(Redis Cluster)是 Redis 官方提供的分布式解决方案,主要用于解决单机 Redis 的容量瓶颈(内存、QPS 上限)和高可用问题,支持数据分片、自动故障转移和水平扩展。其核心设计围绕 “分片存储” 和 “去中心化高可用” 展开,以下是其模式细节及优缺点分析:

一、Redis 集群的核心模式(设计特点)

Redis 集群的核心目标是实现大规模数据的分布式存储自动化的高可用保障,其关键机制包括:

1. 数据分片:基于哈希槽(Hash Slot)

Redis 集群将数据分散存储在多个节点上,核心通过 “哈希槽” 实现分片:

  • 总共有 16384 个哈希槽(固定值,范围 0-16383),每个槽对应一部分数据。
  • 集群中的每个主节点(Master)负责一部分哈希槽(例如,3 个主节点可能分别负责 5000、5000、6384 个槽)。
  • 数据分配规则:对键(key)计算哈希值 CRC16(key) % 16384,得到对应的哈希槽,该键就存储在负责此槽的主节点上。

这种设计的优势是:

  • 分片规则简单,客户端可直接计算键所在的节点,无需通过中心节点转发;
  • 支持动态调整槽分配(例如,新增节点时可将部分槽从旧节点迁移到新节点,实现水平扩展)。

2. 主从复制与故障转移

为保证高可用,集群中每个主节点会配置 1 个或多个从节点(Slave),形成 “一主多从” 的分片单元:

  • 从节点复制主节点的数据,作为备份;
  • 当主节点故障(如宕机、网络中断)时,集群会自动从其从节点中选举一个升级为新主节点,接管原主节点的哈希槽,整个过程无需人工干预(类似哨兵机制,但集成在集群内部)。

3. 去中心化设计

集群中没有 “中心节点”,所有节点(主 + 从)通过Gossip 协议相互通信:

  • 节点定期交换状态信息(如自身健康状态、负责的槽、主从关系等),维护整个集群的元数据(如哪个节点负责哪些槽);
  • 客户端连接集群时,只需与任意一个节点通信,即可获取全量的槽分配信息,进而直接与目标节点交互。

4. 容错与可用性保障

  • 集群通过 “投票机制” 判断节点是否故障:当超过半数的主节点认为某个节点不可达时,标记该节点为 “故障”;
  • 若主节点故障且无可用从节点,集群会进入 “部分可用” 状态(仅故障节点负责的槽不可用,其他槽正常服务);
  • 支持配置cluster-require-full-coverage(默认 yes):若设为 no,即使部分槽不可用,集群仍允许其他槽的读写。

二、Redis 集群的优点

  1. 水平扩展能力强支持动态增加节点(主从),通过迁移哈希槽实现数据负载均衡,理论上可无限扩展(受限于实际部署成本),解决了单机 Redis 的内存和 QPS 上限问题。
  2. 高可用内置化集成了主从复制和自动故障转移功能(无需额外部署哨兵),主节点故障后自动切换从节点为新主,减少服务中断时间。
  3. 去中心化,无单点瓶颈节点间通过 Gossip 协议平等通信,无中心节点,避免了中心节点的性能或故障瓶颈。
  4. 分片规则高效基于哈希槽的分片逻辑简单,客户端可直接定位键所在节点,减少转发开销;槽迁移过程中数据可正常访问(通过 “ASK” 重定向机制)。
  5. 适合大规模数据场景相比主从架构(全量数据复制),集群通过分片将数据分散存储,每个节点仅存储部分数据,降低了单节点的存储压力。

三、Redis 集群的缺点

  1. 数据一致性无法保证(异步复制缺陷)主节点向从节点的复制是异步的(主节点写入后立即返回客户端,不等待从节点同步),若主节点故障,未同步到从节点的数据会丢失,只能保证最终一致性,不适合强一致性场景。
  2. 不支持跨槽的复杂命令涉及多个键的命令(如MGET key1 key2)若这些键分布在不同哈希槽,集群会直接返回错误(CROSSSLOT Keys in request don't hash to the same slot)。需通过客户端手动将键 “哈希标签”(如{user:1}:name{user:1}:age强制分配到同一槽),增加了开发复杂度。
  3. 运维复杂度高
    • 节点扩容 / 缩容时需手动迁移哈希槽(或依赖工具),过程中需确保数据一致性;
    • 集群元数据(槽分配、主从关系)维护复杂,故障排查难度高于主从架构;
    • 对网络稳定性要求高:Gossip 协议的信息同步依赖网络,网络分区可能导致集群状态判断混乱。
  4. 内存和网络开销较大
    • 每个节点需存储全量的集群元数据(槽分配、节点列表等),节点越多,元数据开销越大;
    • Gossip 协议的定期信息交换会消耗额外网络带宽(尤其是节点数量较多时)。
  5. 兼容性限制部分 Redis 功能在集群模式下受限:
    • 不支持事务(MULTI/EXEC)跨多个节点;
    • 不支持KEYSFLUSHALL等全局命令(需指定节点执行);
    • 对 Lua 脚本的支持有限(脚本中涉及的键必须在同一槽)。

四、适用场景与总结

  • 适用场景:需要存储大规模数据(超过单机内存)、要求高可用性(自动故障转移)、可接受最终一致性的场景(如电商缓存、用户会话存储、分布式计数器)。
  • 不适用场景:强一致性需求(如金融交易)、依赖大量跨键命令(如复杂的多键事务)、小规模数据且追求简单运维的场景(此时主从 + 哨兵更合适)。

场景题(重点)

为什么使用redis?

主要是因为 Redis 具备「高性能」和「高并发」两种特性

1、Redis 具备高性能

假如用户第一次访问 MySQL 中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据缓存在 Redis 中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了,操作 Redis 缓存就是直接操作内存,所以速度相当快。

如果 MySQL 中的对应数据改变的之后,同步改变 Redis 缓存中相应的数据即可,不过这里会有 Redis 和MySQL 双写一致性的问题。

2、 Redis 具备高并发

单台设备的 Redis 的 QPS(Query Per Second,每秒钟处理完请求的次数)是 MySQL的 10倍,Redis 单机的 QPS 能轻松破 10w,而 MySQL 单机的 QPS 很难破 1w。所以,直接访问 Redis 能够承受的请求是远远大于直接访问 MVSQL 的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

为什么redis比mysql要快?

内存存储:Redis 是基于内存存储的 NoSQL 数据库,而 MySQL 是基于磁盘存储的关系型数据库。由于内存存储速度快,Redis 能够更快地读取和写入数据,而无需像 MySQL 那样频繁进行磁盘 V0 操作。

简单数据结构:Redis 是基于键值对存储数据的,支持简单的数据结构(字符串、哈希、列表、集合有序集合)。相比之下,MSQL需要定义表结构、索引等复杂的关系型数据结构,因此在某些场景下Redis 的数据操作更为简单高效,比如 Redis 用哈希表查询,只需要O1 时间复杂度,而MvSOL引擎的底层实现是B+Tree,时间复杂度是O(logn)

线程模型:Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。

本地缓存和Redis缓存的区别?

  • 本地缓存的核心优势是极致性能(无网络开销)部署简单,但局限于单机、无法共享、容量有限;
  • Redis 缓存的核心优势是分布式共享能力可扩展性,但存在网络延迟和额外的运维成本。

redis应用场景是什么?

热点数据缓存:电商商品详情、用户基本信息、新闻列表、APP 首页推荐数据。

分布式计数器:基于 Redis 的incr/decr等原子命令,实现跨服务、高并发的计数功能,无需担心并发竞争问题。文章阅读量、视频播放量、商品点赞数、秒杀活动库存计数、接口调用次数统计。

分布式会话存储:在分布式系统中(多台应用服务器),用户 Session 无法通过本地内存共享,Redis 可作为集中式会话存储,实现跨服务器的 Session 同步。Web 网站登录状态、APP 用户登录令牌(如 Token)存储。

有序排行榜:用 Redis 的ZSet(有序集合)数据结构,天然支持按 “分数” 排序,可高效实现实时排行榜功能。游戏玩家积分排名、直播平台礼物榜、电商商品销量榜、社区话题热度榜

简单消息队列:通过Pub/Sub(发布订阅)或Stream(Redis 5.0+)功能,实现轻量级消息通知或异步任务处理,适合对消息可靠性要求不极致的场景。用户注册后的短信 / 邮件通知、系统日志实时收集、微服务间简单通信。

分布式锁:基于 Redis 的SET NX PX(仅不存在时设置 + 过期时间)命令的原子性,实现跨服务、跨机器的资源互斥访问,解决分布式环境下的并发冲突。秒杀库存扣减(防止超卖)、定时任务(防止多实例重复执行)、跨服务修改同一条数据(防止脏写)。

Hash(哈希表):存储结构化数据,如购物车(cart:user123的 Key 下,商品ID为 Field、数量为 Value),支持单独增删改某个字段,无需操作整个对象。

Redis分布式锁的实现原理?

分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用

1. 加锁:通过SET命令的原子性实现

加锁的核心命令是

SET lock_key random_value NX PX 30000

各参数含义:

  • lock_key:锁的标识(如 “order:123” 表示订单 123 的锁);
  • random_value:随机值(如 UUID),用于唯一标识当前加锁的线程 / 服务,避免释放锁时误删其他线程的锁;
  • NX:仅当lock_key不存在时才设置成功(保证互斥性,同一时间只有一个线程能加锁);
  • PX 30000:设置锁的过期时间为 30 秒(防止死锁,即使持有锁的线程崩溃,锁也会自动释放)。

加锁逻辑

  • 若命令返回OK,表示加锁成功;
  • 若返回nil,表示锁已被其他线程持有,加锁失败。

2. 释放锁:通过 Lua 脚本保证原子性

释放锁时,不能直接用DEL lock_key(可能误删其他线程的锁),需先验证 “当前锁的持有者是否为自己”,再删除。这两步操作需通过Lua 脚本原子执行:

-- 脚本逻辑:若lock_key的值等于传入的random_value,则删除锁,返回1;否则返回0
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end

执行脚本时,传入参数:

  • KEYS[1] = lock_key(锁标识);
  • ARGV[1] = 加锁时的random_value(当前线程的唯一标识)。

释放逻辑

  • 若锁的 value 与当前线程的random_value一致,说明锁是自己的,执行删除(释放成功);
  • 若不一致,说明锁已被其他线程持有(可能是自己的锁过期后被别人加锁),不做操作(避免误删)。

什么场景下用到分布式锁?

1. 库存扣减(防止超卖)

场景:电商秒杀、限量商品抢购等场景中,多个用户同时下单,需扣减同一商品的库存。问题:若没有分布式锁,多个服务实例可能同时读取到 “剩余 1 件库存”,并各自执行扣减,导致最终库存为 - 1(超卖)。分布式锁的作用:保证同一时间只有一个请求能执行 “查库存 + 扣减库存” 的逻辑,避免并发冲突。

2. 定时任务(防止重复执行)

场景:分布式系统中,多个服务实例部署了相同的定时任务(如每日数据统计、订单超时关闭)。问题:若没有锁,所有实例会在同一时间执行任务,导致重复计算(如同一批订单被多次标记为 “超时关闭”),浪费资源且可能引发数据异常。分布式锁的作用:任务触发时,多个实例竞争同一把锁,只有抢到锁的实例执行任务,其他实例放弃,保证任务仅执行一次。

3. 分布式事务(跨服务数据一致性)

场景:跨多个服务的事务操作(如 “下单” 需同时扣减库存、扣减余额、生成订单),需保证操作的原子性(要么全成功,要么全回滚)。问题:若中间某一步失败(如扣减余额超时),其他服务可能已完成操作,导致数据不一致。分布式锁的作用:通过锁约束整个事务流程,确保只有一个进程能处理该业务(如同一订单 ID 的事务),失败时可安全回滚所有操作,避免部分成功的中间状态。

4. 共享资源修改(防止脏写)

场景:多个服务实例同时修改同一条共享数据(如用户的积分、账户余额、配置项)。问题:并发修改可能导致 “覆盖写”(如 A 服务读取余额 100 并加 10,B 服务同时读取 100 并加 20,最终结果可能为 110 而非 130)。分布式锁的作用:保证 “读取 - 修改 - 写入” 的原子性,同一时间只有一个服务能修改数据,避免脏写。

5. 分布式限流(控制全局并发量)

场景:限制某个接口的全局并发请求数(如同一时间最多允许 100 个请求访问支付接口)。问题:单机限流(如每个实例限 20 并发)无法控制全局总量(5 个实例可能同时达到 20,总并发 100,但实际想限制的是全局不超过 100)。分布式锁的作用:通过锁的竞争机制控制并发数(如每次请求获取锁,释放锁时计数减 1,超过阈值则拒绝),实现全局统一的限流。

6. 分布式 ID 生成(保证 ID 唯一性)

场景:生成全局唯一 ID(如订单号、流水号),通常基于 “计数器 + 时间戳” 实现。问题:多个服务实例同时读取计数器并自增,可能生成重复 ID(如同时读取到计数器值 100,都生成 ID=101)。

Redis的大Key问题是什么?

Redis 的 “大 Key 问题” 指的是 Redis 中某个 Key 对应的 Value 过大,或集合类 Key(如 Hash、List、Set、ZSet)包含过多元素,导致 Redis 性能下降、运维困难甚至服务不稳定的现象。

没有绝对标准,通常结合业务场景和 Redis 性能表现判断,常见参考阈值:

  • String 类型:Value 大小超过 100KB(甚至 50KB)即可视为大 Key(如序列化后的大对象、长文本);
  • 集合类型(Hash/List/Set/ZSet):元素数量超过 1 万(甚至 5000),或整体占用内存超过 1MB(如包含百万用户 ID 的 Set、存储大量订单明细的 Hash)。

大 Key 的具体表现形式

  • 大 String:例如存储整个网页 HTML、序列化的大对象(如包含数百个字段的用户信息)、长日志等;
  • 大 Hash:例如以用户 ID 为 Key,存储该用户的所有行为记录(字段数过万);
  • 大 List:例如存储某热门话题的所有评论(元素数几十万);
  • 大 Set/ZSet:例如存储某大型活动的所有参与用户 ID(元素数上百万)。

具体缺点

1. 阻塞 Redis 主线程,降低整体吞吐量

Redis 的核心命令处理是单线程的,大 Key 的操作(如GET大 String、HGETALL大 Hash、DEL大集合)会占用大量 CPU 时间,阻塞后续所有命令的执行,导致:

  • 其他正常 Key 的请求延迟飙升(从毫秒级增至秒级);
  • 极端情况下,单条大 Key 操作可能阻塞数秒,引发客户端超时、服务不可用。
2. 内存分布不均,影响集群负载均衡
  • 在 Redis 集群中,大 Key 会导致其所在的节点内存占用远高于其他节点(“数据倾斜”),该节点容易成为整个集群的性能瓶颈(如 CPU、内存使用率过高);
  • 内存碎片化加剧:大 Key 的频繁创建 / 删除可能导致 Redis 内存管理器产生大量碎片(尤其是非连续内存分配),降低实际内存利用率(例如总内存使用率 80%,但可用内存可能仅剩 20%)。
3. 网络带宽消耗激增
  • 读写大 Key 时,需要传输大量数据(如 100KB 的 String 每次GET需传输 100KB 数据),占用服务器与客户端、主从节点之间的网络带宽;
  • 在主从复制或集群数据迁移场景中,大 Key 的同步 / 迁移会持续占用网络资源,导致:
    • 从节点同步延迟(数据落后主节点);
    • 集群迁移超时(大 Key 传输未完成,触发迁移重试);
    • 其他正常数据的网络传输被挤占,延迟增加。
4. 持久化与恢复效率低下
  • RDB 持久化:生成快照时,大 Key 的序列化会消耗大量 CPU 和 IO 资源,导致 RDB 文件生成时间过长(甚至超时失败),且大 Key 会增大 RDB 文件体积,占用更多磁盘空间;
  • AOF 持久化:大 Key 的写命令(如HMSET包含 10 万字段)会生成庞大的 AOF 日志,导致 AOF 文件膨胀,且fsync刷盘时 IO 压力剧增;
  • 数据恢复:Redis 重启时加载 RDB/AOF 文件,解析大 Key 会耗时极长(如加载包含 100 万元素的 Hash 可能需要数十秒),导致服务启动缓慢,可用性下降。
5. 过期删除与内存淘汰成本高
  • 大 Key 过期时,Redis 需要执行删除操作(无论是主动删除还是惰性删除),遍历大集合类 Key 的所有元素会消耗大量 CPU,阻塞主线程;
  • 当 Redis 内存达到maxmemory上限时,内存淘汰机制(如 LRU)筛选和删除大 Key 会比小 Key 更耗时,进一步加剧性能波动。

如何解决

一、针对不同类型大 Key 的拆分策略

大 Key 的根本问题是 “单 Key 体积过大或元素过多”,拆分的核心是将一个大 Key 拆分为多个小 Key,分散存储压力。

1. String 类型大 Key(如大对象、长文本)

问题:单个 String 的 Value 过大(如 100KB 以上,例如序列化的完整用户信息、长日志)。解决方案

  • 拆分对象字段:将大对象拆分为多个小 String,按字段存储。例:原user:1000 {id:1000, name:"xxx", age:20, ...(100个字段)} → 拆分为user:1000:nameuser:1000:ageuser:1000:xxx等,每个 Key 仅存储一个字段。优势:减少单 Key 体积,读取时按需获取字段(避免一次性加载全量数据)。
  • 压缩存储:对长文本(如 JSON、HTML)用压缩算法(如 gzip、snappy)压缩后再存入 Redis,读取时解压。适用场景:文本压缩率高(如重复内容多的日志),且读写频率不极端(避免压缩 / 解压开销抵消收益)。
  • 按需缓存:只缓存高频访问的核心字段,非核心字段直接从数据库查询(避免缓存冗余数据)。
2. Hash 类型大 Key(如包含大量字段的哈希表)

问题:Hash 的 Field 数量过多(如 10 万 +,例如存储某用户的所有订单记录)。

解决方案

  • 按字段范围拆分 Hash:将一个大 Hash 拆分为多个小 Hash,按 Field 的特征(如 ID 范围、哈希值)分配到不同的小 Hash 中。
3. List/Set/ZSet 类型大 Key(如包含大量元素的集合)

问题:List 的元素过多(如 10 万 +,例如热门话题的评论列表)、Set/ZSet 的元素过多(如百万 +,例如活动参与用户 ID 集合)。

解决方案

  • 按数量 / 时间拆分集合
    • List:按元素数量拆分(如每 1000 个元素一个 List),或按时间拆分(如每天一个 List,comment:topic:100:20231001comment:topic:100:20231002)。
    • Set/ZSet:按元素的哈希值拆分(如用户 ID 模 50,分散到 50 个 Set 中),或按范围拆分(如用户 ID 1-10000 存user:act:1,10001-20000 存user:act:2)。
  • 用 ZSet 替代 List 做分页:若 List 用于分页查询(如 “最新 100 条评论”),可改用 ZSet(Score 为时间戳),按 Score 范围查询,避免 List 的lrange操作遍历大量元素。
二、删除大 Key 的特殊处理

直接用DEL命令删除大 Key(尤其是集合类)会阻塞 Redis 主线程(单线程需遍历所有元素),需用异步删除

  • Redis 4.0 + 提供UNLINK命令:将大 Key 的删除操作放入后台线程执行,主线程不阻塞(仅记录删除任务后立即返回)。
  • 若使用低版本 Redis,可分批删除集合元素(如用SPOP每次删 100 个元素,循环执行直到集合为空),避免单次操作耗时过长。

什么是热key?

热 key(热点 Key)指的是在短时间内被异常高频访问的 Redis 键(Key),其访问量远高于其他普通 Key,导致该 Key 所在的 Redis 节点(或服务器)承受巨大的负载压力,甚至成为整个系统的性能瓶颈。

热 key 的核心特征:

  1. 访问频率极高:单位时间内的请求量远超其他 Key(例如,某 Key 每秒被访问 10 万次,而普通 Key 仅几十次);
  2. 时间集中性:访问往往集中在某个时间段爆发(如秒杀活动开始时的商品 ID、突发热门事件的相关 Key);
  3. 节点绑定:在 Redis 集群中,热 key 会固定落在负责其哈希槽的某个主节点上,导致该节点成为 “热点节点”。

举例说明:

  • 电商平台的 “秒杀商品 ID”:活动开始后, millions of 用户同时刷新该商品详情,对应的缓存 Key(如goods:10086)被高频访问;
  • 社交平台的 “热门话题 ID”:某事件突发后,大量用户查询该话题内容,对应的topic:999成为热 key;
  • 直播平台的 “主播在线状态 Key”:头部主播开播时, millions of 粉丝同时查看其状态,live:user:888被频繁读取。

如何解决:

多副本存储(热 key 复制到多个节点):将热 key 复制到 Redis 集群的多个节点(主节点),客户端访问时随机选择一个副本,分散单个节点的压力。

本地缓存(应用层缓存热 key):在应用服务器的本地内存中缓存热 key(如 Java 用 Caffeine、Python 用 functools.lru_cache),减少对 Redis 的直接访问。

热 key 拆分(将一个热 key 拆分为多个子 key):若热 key 的 Value 可拆分(如计数、集合),将其拆分为多个子 key,分散到 Redis 集群的不同节点,客户端访问时随机选择子 key 操作。

读写分离 + 主从扩容(针对读热 key):若热 key 是 “读多写少” 类型,可给其所在的主节点增加更多从节点,将读请求分流到从节点,减轻主节点压力。

如何保证 redis 和 mysql 数据缓存一致性问题?

保证 Redis 与 MySQL 的数据缓存一致性,核心是解决 “缓存数据与数据库数据不同步” 的问题。由于两者的存储特性(内存 vs 磁盘)和更新机制不同,需根据业务对一致性的要求(最终一致性 / 强一致性)、读写频率等选择方案。以下是经过实践验证的主流方案:

一、核心原则:优先 “删缓存”,而非 “更缓存”

直接更新缓存(如set cache_key new_value)容易引发并发冲突:例如,两个线程同时更新数据库,若线程 A 先更新缓存,线程 B 后更新缓存,但 B 的数据库更新晚于 A,会导致缓存存 B 的旧值。更安全的做法是删除缓存(让后续请求从数据库加载最新数据到缓存),配合过期时间兜底(即使偶发不一致,缓存过期后也会自动同步)。

二、主流方案详解

1. 旁路缓存模式(Cache-Aside Pattern):先更 DB,再删缓存

适用场景:读多写少、对一致性要求为 “最终一致”(如商品详情、用户信息)。

流程

  • 读操作:先查 Redis,命中则返回;未命中则查 MySQL,再将结果写入 Redis 后返回。
  • 写操作:先更新 MySQL,再删除 Redis 中对应的缓存(而非更新缓存)。

为什么有效:删除缓存后,后续读请求会从 MySQL 加载最新数据到 Redis,自然恢复一致性。

潜在问题:若 “更新 MySQL 成功” 但 “删除 Redis 失败”(如网络波动),会导致缓存留存旧数据。

解决方案

  • 重试机制:删除缓存失败时,将 “删除任务” 丢入消息队列(如 RabbitMQ),通过消费者重试删除(设置 3-5 次重试上限,间隔递增如 1s→3s→5s,避免无效循环)。
  • 定时校验:定时任务(如每 5 分钟)对比缓存与 MySQL 的 “数据版本号”(需额外存储),若缓存版本落后,则删除缓存或重新同步。
2. 先删缓存,再更 DB + 延迟双删

适用场景:写操作较频繁,需避免 “缓存污染”(旧数据被误写入缓存)。

解决的问题:若 “删缓存后、DB 更新前” 有请求查询,会从 DB 读旧数据并写入缓存,导致后续不一致。例如:

  • 线程 A:删除缓存 → 开始更新 DB(未完成);
  • 线程 B:查询数据 → 缓存已删,查 DB 旧数据 → 写入缓存;
  • 线程 A:更新 DB 完成 → 此时缓存中是 B 写入的旧数据,不一致。

流程优化:写操作 = 先删缓存 → 更新 DB → 延迟一段时间(如 500ms)后再次删除缓存

第二次删除的作用:等线程 B 完成 “查旧 DB→写旧缓存” 后,再删一次缓存,清除旧数据。后续请求会从 DB 加载最新数据。

注意:延迟时间需根据业务耗时调整(如通过压测确定 DB 更新 + 缓存写入的最大耗时),通常设为 500ms-1s。

3. 基于 Binlog 的异步同步(Canal 方案)

适用场景:多服务共享数据、缓存更新逻辑复杂(如多表关联更新)。

原理:利用 MySQL 的 Binlog 日志(记录所有数据库变更),通过 Canal(阿里开源工具)监听 Binlog,实时感知 DB 变化,异步更新 / 删除 Redis 缓存。

流程

  1. Canal 模拟 MySQL 从库,订阅主库的 Binlog;
  2. MySQL 数据更新时,Canal 捕获变更事件(如update/delete);
  3. Canal 将事件推送给消息队列,消费端根据事件更新 / 删除对应的 Redis 缓存。

优势

  • 解耦缓存更新逻辑(业务代码无需嵌入删缓存逻辑);
  • 支持多服务场景(避免各服务重复处理缓存)。

缺点:存在毫秒级延迟(Binlog 同步→缓存更新),适合最终一致性场景。

4. 强一致性方案:加锁 + 双写

适用场景:对一致性要求极高(如金融交易、库存扣减),可接受性能损耗。

流程

  • 写操作:加分布式锁 → 更新 MySQL → 更新 Redis → 释放锁;
  • 读操作:加分布式锁 → 查 Redis(未命中则查 MySQL 并回写 Redis) → 释放锁。

原理:通过锁保证 “读写互斥”,避免并发导致的不一致。

缺点:锁会降低并发性能(读写均需等待锁),仅适合低并发、高一致性场景。

三、必加兜底:缓存过期时间

无论采用哪种方案,都需为 Redis 缓存设置合理的过期时间(如 5-10 分钟)。即使出现偶发不一致(如删除缓存失败、延迟时间不足),缓存过期后会被自动淘汰,后续请求从 DB 加载最新数据,保证 “最终一致性”。

缓存雪崩、击穿、穿透是什么?怎么解决?

    • 穿透:缓存穿透是指请求查询的数据在缓存和数据库中都不存在,导致每次请求都会 “穿透” 缓存,直接访问数据库。

      • 缓存空值(Null Cache)当数据库返回空结果时,也将空值写入缓存,并设置较短的过期时间(如 3~5 分钟)。
      • 布隆过滤器(Bloom Filter)将所有存在的 Key 哈希到一个位数组中,请求先经过布隆过滤器判断是否可能存在。
    • 击穿:某个热点 Key 突然过期,而此时恰好有大量请求查询该 Key,这些请求会同时绕过缓存,集中访问数据库。

      • 互斥锁(分布式锁)当缓存失效时,只有第一个请求能获取锁去数据库查询,其他请求等待并重试。用这个setnx命令或者Redisson 分布式锁
      • 对热点 Key 不设置过期时间,通过后台任务定时更新缓存。
    • 雪崩:缓存中大量 Key 在同一时间段内集中过期,或缓存服务器集群整体故障,导致海量请求无法从缓存获取数据,全部涌向数据库。

      • 过期时间随机化:在设置过期时间时增加一个随机值(如 30 分钟 ± 5 分钟),避免大量 Key 同时过期。
      • 缓存集群化:使用 Redis Cluster 或主从 + 哨兵模式,避免单点故障。
      • 熔断降级处理:当缓存失效或数据库压力过大时,通过熔断机制直接返回默认数据或错误提示,保护后端。
      • 限流处理:对数据库入口做限流保护,防止瞬时请求量过大。
      • 多级缓存:结合本地缓存(Caffeine、Guava)+ 分布式缓存(Redis),减少对单一缓存层的依赖。

布隆过滤器原理介绍一下

布隆过滤器(Bloom Filter)

布隆过滤器是一种高效的空间优化型数据结构,用于判断一个元素是否“可能存在”或“一定不存在”。

原理:

  • 初始化一个长度为 m 的 bit 数组(全 0)。

  • 定义 k 个哈希函数。

  • 每插入一个元素,计算它的 k 个哈希值,将对应位置设为 1。

  • 判断是否存在:检查所有哈希位置是否都是 1。

    • 如果有 0 → 一定不存在
    • 如果全是 1 → 可能存在(存在误判)

应用场景:

  • 缓存穿透防护(Redis + 布隆过滤器)
  • 黑名单快速判断
  • 垃圾邮件过滤、爬虫去重等

如何设计秒杀场景处理高并发以及超卖现象?

用户 → 前端(验证码/排队) → CDN(静态资源) → Nginx(限流) → 应用服务(接口限流/熔断) → 消息队列(削峰) → 消费者(处理订单)

Redis(原子扣减库存) ← 库存预热 ← MySQL(原始库存)

MySQL(最终扣减库存,带条件更新)
  • 按钮置灰与防重复提交:用户点击秒杀按钮后立即置灰,禁止重复点击;通过前端防抖(如 1 秒内只允许 1 次请求)减少重复提交。
  • 验证码 / 排队机制:秒杀开始前要求用户输入图形验证码或完成滑块验证,过滤机器人脚本;将用户放入前端排队队列(如显示 “排队中,第 XX 位”),分散请求发送时间。
  • 静态资源 CDN 化:秒杀页面的 HTML、CSS、JS、图片等静态资源全部部署到 CDN,避免请求穿透到应用服务器。
  • Nginx 限流:通过limit_req模块设置每秒最大请求数(如限制单 IP 每秒 5 次请求),超过阈值直接返回 “系统繁忙”。
  • 黑名单拦截:通过网关拦截频繁请求的恶意 IP(如 1 分钟内请求超过 100 次),加入临时黑名单。
  • 接口限流:在应用服务中通过 Redis 实现分布式限流(如滑动窗口算法),限制秒杀接口的全局 QPS(如每秒 1 万次),超过则返回降级提示。
  • 服务熔断降级:使用 Sentinel/Hystrix 等组件,当服务响应时间超过阈值或错误率过高时,自动熔断,返回 “稍候再试” 等友好提示,避免服务雪崩。
  • 通过消息队列(如 RabbitMQ、Redis List)将同步请求转为异步处理,将瞬时高流量 “削平” 为匀速流量

防超卖:库存操作的原子性与一致性

超卖的本质是 “库存扣减非原子化”(如并发查库存→扣库存导致多扣),需通过 “预扣减 + 原子操作 + 多层校验” 保证库存安全。

1. 库存预热:Redis 预存库存(快速判断)

秒杀开始前,将商品库存从 MySQL 加载到 Redis(如seckill:stock:1001 → 100),后续库存判断和扣减优先操作 Redis(内存操作,支持高并发)。

  • 预热时机:秒杀活动开始前 5-10 分钟,通过定时任务从 MySQL 同步库存到 Redis。
  • 优势:避免秒杀开始时大量请求查询 MySQL,减轻数据库压力。
2. Redis 原子扣减:拦截超量请求(核心防超卖)

利用 Redis 的DECR命令(原子操作)预扣库存,确保同一时间只有一个请求能成功扣减:

  • 流程:用户请求进入后,先执行DECR seckill:stock:1001,若返回值≥0,说明库存充足,允许继续下单;若返回值 <0,直接返回 “已抢完”。
  • 原理DECR是单线程原子操作,即使数万并发请求同时执行,也能保证库存扣减的准确性,避免超卖。
3. MySQL 最终校验:保证数据一致性(兜底机制)

Redis 扣减成功后,需异步同步到 MySQL(通过消息队列消费),此时需再次校验 MySQL 库存,避免 Redis 与 MySQL 不一致导致的超卖:

  • SQL 层面:扣减库存时用带条件的更新语句(利用 MySQL 行锁保证原子性):

    -- 仅当库存>0时才扣减,返回影响行数(1=成功,0=失败)
    UPDATE seckill_goods
    SET stock = stock - 1, version = version + 1
    WHERE goods_id = 1001 AND stock > 0;
  • 若更新影响行数为 0,说明 MySQL 实际库存已不足,需回滚 Redis 库存(INCR seckill:stock:1001),并取消用户订单。

4. 库存一致性维护
  • 定时对账:启动定时任务(如每秒一次),对比 Redis 库存与 MySQL 库存,若存在差异(如 Redis 因网络问题未扣减),以 MySQL 为准修正 Redis。
  • 最终库存锁定:订单创建后设置 15 分钟支付超时,未支付则释放库存(Redis+MySQL 同时加回),避免库存长期占用。

假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以 某个固定的已知的前缀开头的, 如何将它们全部找出来?

首先禁止使用 KEYS 命令,keys prefix*虽然可以返回所有匹配前缀的key,阻塞Redis单线程执行期间无法处理其他命令;内存消耗激增,可能导致OOM

SCAN 是 Redis 2.8+ 提供的渐进式遍历命令,通过游标迭代的方式分批返回匹配的 key,每次扫描只处理部分数据,不会阻塞 Redis 服务,适合处理大规模 key 场景。

# 语法:SCAN cursor [MATCH pattern] [COUNT count]
# cursor:游标(初始为0,每次扫描后返回新游标,直到游标为0表示遍历完成)
# MATCH:匹配模式(如 "prefix*" 匹配前缀为prefix的key)
# COUNT:指定每次扫描的"预估数量"(非精确值,Redis会根据内部数据结构调整)

MySQL 里有 2000w 数据, redis 中只存 20w 的数据,如 何保证 redis 中的数据都是热点 数据?

首先需通过maxmemorymaxmemory-policy配置,限制 Redis 缓存的 “数据量” 或 “内存占用”(假设每条数据平均 1KB,20 万条约 200MB,可据此设置maxmemory

推荐allkeys-lfu:比lru更精准(lru可能误淘汰 “曾经热、近期没访问但未来可能热” 的数据,lfu基于访问频率,更贴合 “热点” 定义)。

避免一次性将 MySQL 的 2000 万数据全量加载到 Redis,而是按需加载(仅当数据被访问时才加载),确保进入 Redis 的数据都是 “被访问过的”

  1. 基础配置:Redis 开启maxmemory=200mbmaxmemory-policy=allkeys-lfu,自动淘汰冷数据。
  2. 业务埋点:统计数据访问频率,仅将高频数据写入 Redis,减少冷数据进入。
  3. 定时同步:每 5 分钟筛选 Top20 万热点数据,同步到 Redis 并删除冷数据,严格控制数量。
  4. 优化防护:用 Cache-Aside 模式更新缓存,布隆过滤器防穿透,核心热点数据永不过期。