高并发面试3

第56题:MySQL 查询大量数据的内存管理机制

  • 常见误区:将 Java 思维(如 List 添加 2000 万对象会 OOM)套用至 MySQL,认为会内存溢出。
  • 真相:MySQL 服务端采用流式(Streaming)协议传输数据,类似水流过水管,不会在内存中积压。核心机制是(默认 16KB),数据读取后立即通过网络推给客户端,服务端内存中暂存的数据量很小,因此不会因查询大量数据导致内存溢出。

全表扫描的潜在风险

  • 虽然不会 OOM,但全表扫描可能引发更严重的问题:缓存污染(劣币驱逐良币)。
  • 原理:当扫描 2000 万行冷数据(如历史日志)时,InnoDB 会从磁盘读取这些数据并塞进 Buffer Pool,可能将热点数据挤出内存,导致后续请求需从磁盘读取,响应速度从毫秒级变为秒级,甚至引发全站 "假死"。

InnoDB 的冷热分离策略(缓存保护机制)

  • 改进的 LRU 算法:InnoDB 将 LRU 链表拆分为热区(New Sublist)和冷区,而非朴素 LRU。
  • 新数据默认进入冷区:全表扫描的新数据被 "打入冷宫",直接放在冷区头部,不影响热区热点数据。
  • 时间门槛控制:通过参数(默认 1 秒),只有在冷区停留超过该时间的冷数据才可能晋升到热区。全表扫描数据因访问密集(几毫秒内完成),小于 1 秒,无法进入热区,最终被逐出内存,从而保护热点数据。

面试回答模板

第一维度(不会 OOM 的原因):MySQL 采用流式协议,仅 16KB,数据不积压,服务端内存不会溢出。

第二维度(潜在隐患):全表扫描可能导致 Buffer Pool 缓存污染,冷数据挤出热数据,引发磁盘 IO 飙升。

第三维度(InnoDB 防御机制):采用中间插入策略(Midpoint Insertion),新数据默认进入冷区,配合时间门槛,确保扫描数据在冷区 "自生自灭",保护热点数据。

架构图解

MySQL内存管理
Buffer Pool
查询缓存
连接内存
数据页缓存
索引页缓存
脏页刷盘
查询结果缓存
MySQL 8.0已移除
连接线程内存
排序缓冲区
JOIN缓冲区
OOM预防
限制结果集大小
分批查询
合理配置参数


第57题:Redis 分布式锁

Redis 分布式锁是面试高频考点,也是构建高可用企业级系统的必修课。

常见面试问题

分布式锁如何实现?

业务超时导致锁提前过期怎么办?

锁被其他人误删怎么办?

Redis 主从切换时锁丢失如何处理?

核心问题解决:原子性与误删

  • 原子操作实现:不能先执行 setnx 再设置过期时间(非原子性),需使用命令,这是一个原子命令。
  • 释放锁防误删:使用 Lua 脚本,通过 UUID 判断是否为自己的锁,只有是自己的锁才删除,确保删锁安全性。

锁生命周期问题与解决方案

  • 问题:锁有效期(如 30 秒)可能短于业务执行时间(如因 GC 或网络抖动导致 45 秒),导致锁提前释放引发并发冲突。
  • 解决方案:Redisson 框架的看门狗机制。工作原理:加锁时不指定过期时间则自动启动,每隔 10 秒(1/3 过期时间)检查一次,业务未完成则通过 Lua 脚本自动重置过期时间为 30 秒,实现自动续期。

Redisson 的 "组合拳" 特性

  • 可重入锁:利用 Redis 哈希结构记录线程 ID 及重入次数,支持嵌套调用。
  • 高效等待:基于发布订阅机制,抢锁失败线程进入阻塞状态,锁释放后被唤醒,减少无效轮询(优于 CPU 自旋抢锁)。

极端场景:主从复制延迟导致锁丢失

  • 问题:主库加锁成功但未同步到从库时主库挂了,从库升级为新主库,新客户端可再次加锁,导致两个客户端同时持有锁。

解决方案:

  • Zookeeper 方案:基于强一致性,数据不丢失,适用于金融支付、分布式协调场景。
  • Redlock 方案:Redis 提出,运维复杂、争议大,通常作为备选。

常用方案:Redis 主从 + Redisson,追求高性能,适用于秒杀、常规限流等场景。

架构图解

MySQL Redis 应用服务 MySQL Redis 应用服务 分布式锁实现流程 alt [获取锁成功] [获取锁失败] SET lock_key value NX PX 30000 OK 执行业务操作 Lua脚本释放锁 释放成功 NULL 等待重试


第58题:订单超时自动取消方案解析

面试中被问及 "用户下单 30 分钟未支付如何自动取消",若仅回答 "定时任务每分钟扫数据库改状态",会面临面试官追问:千万级订单全表扫描导致数据库崩溃、订单取消延迟(如 31 分钟才取消)、定时任务机器故障风险。这实际考察海量数据场景下延迟任务的设计能力。

定时任务轮询数据库的问题

在低并发内部系统(如 OA)可用,但高并发场景有 3 个致命问题:

  • 一是时效性差,轮询间隔(如 1 分钟)导致订单取消延迟,影响用户体验;
  • 二是数据库压力大,全表扫描会使 CPU 飙升,1000 万条数据扫描耗时久,阻塞其他业务;三是资源浪费,无超时订单时任务仍空跑,浪费服务器资源。

核心思路:拉模式转为推模式

核心是 "不要主动捞订单,让超时订单自己找上门",即从主动轮询(拉模式)改为事件触发(推模式)。

Redis 过期回调的陷阱

直接用 Redis 过期时间 + 回调不可靠:

  • 一是 Redis 过期事件 "发后即忘",服务重启或网络抖动会导致事件丢失,订单无法取消;
  • 二是延迟大,Redis 采用惰性 + 定期删除策略,无法保证到点删除,可能延迟几分钟(如 30 分钟超时订单 35 分钟才取消),电商场景不可接受。

Redis Zset 方案

实现步骤

  • 生产阶段:调用 ZAdd 命令,将订单存入 Zset,score 设为 30 分钟后的时间戳(如当前 10 点,score 存 10:30 的时间戳)。
  • 消费阶段:启动后台线程每秒执行 ZRANGEBYSCORE 命令,捞取 score≤当前时间戳的元素(即超时订单),每次捞取加 limit(如 10 条)避免处理过多任务。

可靠性保障

为避免订单丢失,可补充 Ack 机制或两阶段处理:Lua 脚本不直接删除订单,而是移到 "处理中" 队列;业务逻辑执行完(如取消订单)再删除 "处理中" 队列的订单号。

若服务宕机,守护线程扫描 "处理中" 队列里停留过久的任务进行重试,确保订单至少被处理一次。

消息队列方案

订单量达亿级时,Redis Zset 大 key 有性能瓶颈,可使用消息队列的延时消息功能,如 RocketMQ 或 RabbitMQ。

注意:RocketMQ 4.x 版本延迟时间固定,5.0 版本支持任意时间;RabbitMQ 需通过 TTL + 死信队列实现,需注意 "队头阻塞" 问题。

时间轮算法

原理与优势

类似钟表结构,一圈 60 个格子,指针每秒走一格。订单 30 分钟后过期,挂在当前格子 + 1800 个槽位(30 分钟 = 1800 秒),指针走到对应槽位触发取消逻辑。优势是纯内存操作,性能极高。

可靠性优化

内存不可靠(服务重启任务丢失),可结合 Redis Zset 持久化:Redis 存储 1 小时后的远期任务,应用启动时加载近期任务到时间轮,既保证可靠性又保障高可用。

面试回答模板

可从 5 个维度回答:

  • 选型(根据订单量选择 Redis Zset / 消息队列 / 时间轮)、
  • 核心流程(任务存储与触发机制)、
  • 可靠性保障(Ack 机制、重试策略)、
  • 进阶优化(大 key 拆分、分布式锁防重复)、
  • 兜底方案(极端情况处理,如中间件故障时的降级策略)。
  • 技术面试考察对资源的敬畏和极端情况的防御思维,优先用 Redis 而非数据库,优先事件驱动而非全表扫描。
架构图解





订单创建
设置超时时间
存入延迟队列
延迟队列
时间到达?
检查订单状态
已支付?
正常流程
执行取消
释放库存
退还优惠券
更新订单状态
方案对比
定时轮询: 简单但效率低
延迟队列: 精准高效
时间轮: 极致性能


第59题:支付场景下的一致性保证方案

  • 用户支付成功后,需同时完成修改订单状态(写 MySQL)和发送 MQ 消息给积分系统添加积分,如何保证这两步操作的一致性是常见面试题。
  • 5 年开发经验的面试者直接回答使用 RocketMQ 事务消息,但因未深入理解生产环境细节而失败。

RocketMQ 事务消息的常见误区

  • 直接回答 "使用 RocketMQ 事务消息,先发半消息,再执行本地事务,根据结果 commit/rollback" 是不够的。

面试官会追问三个核心问题:

  • 事务回查时如何区分事务未执行完?
  • 数据库操作延迟导致 MQ 误判回滚怎么办?
  • 消费端如何保证幂等?
  • 这些问题暴露了仅背 API 调用流程而缺乏生产环境考量的问题。

强一致性与最终一致性的选择

面试官提问 "强一致性" 时需先明确概念:强一致性需借助 XA 协议或 Seata 的 AT 模式,但代价是性能损失;在千万级并发的支付场景下,为保证可用性,通常牺牲强一致性而追求最终一致性,此时可引出 RocketMQ 事务消息方案。

本地事务表的必要性

  • 直接使用业务表进行事务回查存在风险:若本地事务因锁竞争等原因执行延迟,MQ 回查时可能误判事务未执行而 rollback,导致后续本地事务提交后积分消息丢失。
  • 解决方案是引入事务日志表(事务表),记录事务执行状态(结合状态机策略),回查时通过事务日志表确认事务真实状态,避免误判。

消费端幂等性保证

  • 需确保消息重复消费时业务结果一致。
  • 实现方式:利用数据库唯一索引,插入以订单 ID 为唯一键的记录,若插入报错则证明已消费,直接返回;若插入成功则执行加积分逻辑。

事务日志表的优化

  • 事务日志表若长期不清理会导致数据量过大。
  • 解决方案采用冷热分离架构:利用 MySQL 分区表按天分区,保留 3 天热数据,旧数据归档至 HBase 用于审计,避免 DELETE 删除旧数据导致的间隙锁阻塞和主从延迟问题。

死信消息处理

-死信消息(消费失败的消息)需专业处理:搭建死信重投平台,当死信消息产生时触发告警推送至开发者,开发人员可在后台选中死信消息进行重投,若重投失败则人工介入修复。

替代方案:本地消息表

  • 不依赖 MQ 事务功能的替代方案:自建消息表,采用定时任务轮询消息表的方式投递消息(如投递到 Kafka),通过任务调度确保消息可靠发送。

总结:征服面试官的闭环思维

回答此类问题需展示闭环思维:

  1. 明确高并发下追求最终一致性而非强一致性;
  2. 引入事务日志表及状态机解决回查误判;
  3. 消费端实现幂等性避免重复处理;
  4. 考虑兜底策略(死信告警、批量重投工具)。
    清晰阐述这四点可体现生产环境架构思维。
架构图解

MQ 第三方支付 支付服务 订单服务 用户 MQ 第三方支付 支付服务 订单服务 用户 回调重试机制 幂等性保障 创建订单 生成订单号 返回订单信息 发起支付 调用第三方支付 返回支付链接 返回支付链接 完成支付 异步回调通知 发送支付成功消息 消费消息更新订单 更新订单状态 支付完成


第60题:Redis Key 过期后内存释放机制

  • 很多人误认为 Redis Key 的 TTL 归零后数据会立即消失、内存释放,但这是错误的。
  • 在高并发场景下(如大促零点 500 万 Key 同时过期),立即释放会导致 CPU 飙升至 100%、主线程卡死,引发系统性灾难。
  • Redis 采用 "惰性删除 + 定期删除" 的混合策略,而非为每个 Key 设置定时器。

惰性删除

  • 被动清理策略,仅当访问 Key 时才检查是否过期:若过期则删除并释放内存,未过期则返回数据。
  • 优点是简单高效
  • 缺点是冷数据堆积 ------ 大量设置过期时间但不再访问的 Key 会像 "僵尸数据" 长期占用内存,可能导致 Redis 达到 Max memory 限制触发 OOM 告警。

定期删除

  • 主动清理策略,运行在主线程中,通过 ServerCron 方法每 100 毫秒触发一次。
  • 机制为:随机从过期字典抽取 20 个 Key 检查,若过期比例超过 25%,则重复抽样清理(贪婪循环),但单次执行时间上限为 25 毫秒,防止主线程阻塞。
  • 注意:仅主节点执行主动过期策略,从节点不执行。

主从同步中的过期处理问题

  • 从库不会主动删除过期 Key,其过期 Key 的删除属于逻辑删除(仅返回 null),内存释放需等待主库的 Del 命令同步。
  • 若主从网络延迟高或主库清理不及时,从库会囤积大量已过期的 "僵尸数据",存在 OOM 风险。

内存淘汰策略

当写入速度远超惰性 / 定期删除的清理速度时,Redis 触发内存淘汰策略。

默认策略 noevication(内存满时拒绝写入并报错)需避免,推荐两种策略:

  • allkeys-lru:淘汰最近最少使用的数据,保证热点数据命中率,适用于纯缓存场景。
  • volatile-lru:仅在设置过期时间的 Key 中淘汰,保护持久化数据不被误删,适用于同时需要持久化和缓存数据的混合存储场景。

面试回答要点

  • Redis 过期删除并非准时,由惰性删除和定期删除配合完成,且定期删除运行在主线程。
  • 定期删除是概率抽样(每 100ms 抽 20 个 Key),存在 25 毫秒时长限制;主从架构下,从库依赖主库 Del 命令同步释放内存,可能因主从延迟导致从库内存问题。
  • 生产环境可通过三层防御优化:业务层用 TTL + 随机值打散过期时间防止过期风暴;配置层选择合适淘汰策略(纯缓存用 allkeys-lru,混合存储用 volatile-lru);利用 Redis 异步删除避免主线程卡顿。

Redis 设计哲学

Redis 过期策略的设计是对 CPU 算力、内存空间、系统稳定性的权衡:不为每个 Key 创建定时器以节省 CPU;允许少量过期数据暂时留存以平衡内存;限制定期删除时长防止主线程阻塞以保障稳定性。

架构图解

Redis Key过期
过期策略
惰性删除
访问时检查
CPU友好
定期删除
定时抽查
平衡CPU和内存
内存淘汰
达到maxmemory
LRU淘汰
LFU淘汰
随机淘汰
最佳实践
设置合理过期时间
配置淘汰策略


第61题:Redis 性能优化实战指南

  • Redis 性能未达官方 10 万 QPS 的核心瓶颈在网络。
  • 单线程同步读写场景下,即使内网延迟 1 毫秒,理论上限约 1000 QPS;若存储 20KB 大对象,会进一步阻塞单线程,导致 CPU 空等数据,无法充分利用计算能力。

应用层面优化

采用 Pipeline 管道模式,将多个请求(如 100 个)打包成批量操作,减少网络往返次数和系统调用,提升吞吐量。

Redis6 多线程优化

Redis6 的多线程并非指命令执行线程,核心命令执行仍为单线程(保证原子性,无需加锁),而是通过多线程处理耗时的网络读写和协议解析,主线程专注命令执行,可使单线程性能翻倍。低版本建议升级至 Redis6。

协议优化

  • 避免存储 JSON 字符串,其包含大量冗余符号(如大括号、引号),体积臃肿,是高并发场景的带宽杀手。
  • 推荐使用 Protobuf 等二进制协议,可使数据体积缩小一半以上,同时提升解析速度(空间换时间)。

内核调优

  • 单机多实例:多核服务器中,启用多个 Redis 实例,通过 taskset 命令绑定不同 CPU 核心,减少上下文切换,充分利用多核资源。
  • 关闭透明大页:该参数在数据库场景下会导致严重内存写放大和卡顿,必须禁用。

性能突破总结

  • 要使 Redis QPS 从 5 万突破至 20 万,需综合优化:应用层用 Pipeline 批量操作减少网络交互;Redis 层升级至 6.0 开启多线程;数据层用 Protobuf 替换 JSON;内核层进行 CPU 绑定和参数调优(如关闭透明大页)。
架构图解

Redis性能优化
内存优化
命令优化
网络优化
选择合适数据类型
避免BigKey
设置过期时间
避免慢命令
使用Pipeline
Lua脚本原子化
减少网络往返
批量操作
监控指标
内存使用率
命令延迟
连接数


第62题:Redis 大 Key 问题在线优化方案

线上存在一亿数据的 Redis 大 Key,需要进行在线优化,要求业务不中断、Redis 不卡顿、数据库不崩溃,是阿里、字节等大厂面试高频题。

面临的三个约束

  • 业务无感知:不能停机维护,用户正在使用系统,需保证业务持续运行。
  • Redis 不阻塞:大 Key 可能达几个 G,禁止直接删除,否则 Redis 主线程会卡死导致线上故障。
  • 数据库不穿透:迁移过程中若缓存为空,流量瞬间打到数据库会导致数据库崩溃,这是最关键的约束。

五步优化组合拳

第一步:数据分片

将大 Key 拆分为小 Key,例如假设有一千万用户,可使用用户 ID 取模 100 的算法,拆成 0-99 共 100 个小 Key,每个小 Key 仅包含十万用户数据,解决单点热点问题。

第二步:同步双写

修改代码写逻辑,数据更新时同时写入老 Key 和新 Key,此时读操作仍读取老 Key,确保新 Key 从当前开始数据实时更新,是平滑过渡的基础。

第三步:存量数据迁移

通过后台脚本将老 Key 中的旧数据迁移到新 Key,严禁使用 HGETALL 命令,需使用 HSCAN 每次获取一千条数据,迁移完成后休息 50 毫秒(蚂蚁搬家方式),避免阻塞 Redis 主线程。

第四步:灰度切读与兜底

  • 设计多级兜底策略进行流量切读:请求进来先判断灰度比例(如先切 1%),命中灰度则读新 Key;
  • 若新 Key 未读到(因迁移延迟),不能直接查数据库,需降级查老 Key,老 Key 未删除可作为防线保护数据库。

第五步:清理战场

  • 流量 100% 切到新 Key 且稳定运行一周后,删除老 Key。
  • 严禁使用 DEL 命令直接删除,Redis 4.0 + 可使用 UNLINK 命令将删除操作放在后台执行,不卡主线程;老版本可写脚本用 HSCAN 逐步删除。

方案总结

核心是用分片解决大 Key 问题,双写保证业务不中断,HSCAN 保证 Redis 不卡顿,老 Key 兜底保证数据库不崩溃。掌握此方案能有效应对面试官关于 Redis 大 Key 优化的考察。

架构图解

Redis大Key问题
问题识别
解决方案
内存占用大
操作耗时长
阻塞其他请求
拆分大Key
压缩数据
本地缓存
在线优化
UNLINK异步删除
SCAN渐进遍历
预防措施
控制Value大小
监控告警


第63题:线程池拒绝策略全解析

线程池触发拒绝策略需满足三个条件:核心线程全部在工作、任务队列已满、非核心线程达到上限。当三者同时满足时,新任务进入会触发拒绝策略,例如秒杀场景中流量突增导致资源被打满。

JDK 提供的四种拒绝策略

默认策略(抛异常)

默认策略会抛出异常,导致任务丢失,适用于需要感知异常的场景(如金融转账失败需明确反馈)。

静默丢弃策略

会默默丢弃任务,无任何提示,仅适合不重要的日志收集等场景。

丢弃最老任务策略

丢弃任务队列中最旧的任务,放入新任务,适用于实时数据处理(如传感器最新数据),但仍存在任务丢失风险。

CallerRuns 策略

让提交任务的线程自行执行任务,可避免任务丢失,但可能阻塞提交线程(如 Tomcat 工作线程执行耗时任务时,会导致服务器无法响应新请求)。

大厂终极方案:自定义策略 + MQ 异步重试

核心业务(如订单、支付)需保证任务不丢失且不阻塞主线程,可采用自定义拒绝策略:将任务序列化后发送至 MQ(如 RabbitMQ)持久化,再由后台消费者线程异步拉取并重新提交至线程池。若线程池仍满则重试,直至成功。需注意任务需携带唯一 ID,通过数据库唯一索引或 Redis 键实现幂等性,防止重复执行。

面试高频问题及解决方案

CallerRuns 策略阻塞主线程的解决办法

可为任务设置超时时间,或仅在非核心业务中使用该策略。

MQ 重试避免重复执行的方案

通过数据库唯一索引或 Redis 键进行幂等性校验。

除拒绝策略外的线程池优化手段

从源头治理,如提前预热线程池参数、在任务入队前实施限流。

架构图解





任务提交
线程池状态
核心线程未满
创建核心线程执行
核心线程已满
队列未满?
加入队列等待
达到最大线程?
创建非核心线程
执行拒绝策略
AbortPolicy: 抛异常
CallerRunsPolicy: 调用者执行
DiscardPolicy: 静默丢弃
DiscardOldestPolicy: 丢弃最老任务


第64题:MySQL 中 IN 子句的限制与优化

  • MySQL 官方并未规定 IN 子句最多放 1000 个值,1000 是某些版本优化器的软阈值而非硬性限制;
  • IN 底层并非通过排序加二分查找优化;
  • 增大 Max allowed packet 来无限增加 IN 值是治标不治本的错误做法,可能导致严重性能问题。

IN 子句底层执行原理

有索引场景

  • 若查询字段有索引(如主键 ID),MySQL 会先对 IN 列表去重,再组织成 "ID=1 OR ID=3 OR ..." 的等价条件,直接扫描索引对应位置,时间复杂度为 k(匹配结果数),与 IN 列表长度无关,即使 IN 有 500 个值,匹配结果少则执行效率仍很高。

无索引场景

主表无索引时,MySQL 会将 IN 列表所有值加载到内存构建哈希表(同时去重),然后全表扫描,每行数据都去哈希表判断,时间复杂度为 O (T)(T 为主表总行数),即使 IN 只有 100 个值,主表 1000 万行仍会全表扫描。

IN 子句长度的实际限制

SQL 语句长度限制

受 Max allowed packet 属性控制,该属性限制单条 SQL 最大字节数(如 MySQL 8 中默认 64 兆),超过会抛出异常。

内存限制

IN 列表值过多会导致内存中哈希表过大,可能引发 MySQL OOM。

索引失效风险

IN 列表过大可能导致索引失效,触发全表扫描,执行时间随 IN 列表增大而增加。

IN 列表过大的优化方案

分批次查询

最常用方案,无需大改,将数据分成多个批次查询(如插入 1 万条记录分 20 批次,每批 500 条),简单且性能稳定。

临时表加 JOIN

超大量值的首选方案,创建仅含 IN 列表值的临时表(如 1 万个 ID),通过 JOIN 查询,性能远高于超大 IN 列表,适合批量查询。

VALUES 子句代替

构建虚拟表后 JOIN 查询,本质与临时表类似,语法简洁,执行效率接近临时表。

优化方案选择建议

数量不大时用分批次查询;百万级数据可用临时表加 JOIN 或 MySQL 8 后的 VALUES 子句,主要为避免索引失效和性能下降。

面试回答要点

MySQL 官方无 IN 值上限,实际限制来自 Max allowed packet(SQL 长度)、内存解析开销、索引失效风险;实践中建议将 IN 列表安全阈值控制在 500 以内,超过 500 用分批次查询,超过 1 万可用临时表加 JOIN 方案,目的是避免索引失效和性能下降。

架构图解

MySQL IN子句优化
限制分析
优化方案
IN列表过长
SQL解析开销
索引失效风险
分批查询
临时表JOIN
子查询优化
最佳实践
IN列表<1000
使用EXISTS替代
建立合适索引


第65题:500 万 QPS 秒杀系统架构设计

核心战术包括层层过滤、库存分片以及快速失败。面对 500 万 QPS 的流量,若直接进入数据库或 Redis,系统会挂掉,需采用流量漏斗架构将洪水变为涓涓细流。

流量漏斗架构

客户端与 CDN 层

承担 500 万 QPS,通过静态资源缓存和按钮置灰策略,在边缘侧拦截 90% 的无效流量。

网关层

剩余 50 万请求到达网关,利用 nginx、Lua 进行限流,超出阈值的请求直接丢弃。

服务层

10 万请求进入后端,利用 JVM 本地缓存进行重复请求校验。

Redis 层

约 5 万 QPS 触达 Redis 进行库存扣减,是业务核心。

数据库层

最后落到数据库的只有 1 万 TPS,且为异步写入。通过这 5 层漏斗,保证底层脆弱组件不直面流量海啸。

库存分片解决热点 Key 问题

  • 将 1 万台 iPhone 库存分成 100 份,拆分到 100 个桶(bucket 0 到 bucket 99)。
  • 用户通过 UserId%100 路由到对应桶,分散流量到 100 个 key 上,理论上集群抗压能力提升 100 倍,突破千万级 QPS 限制。

Redis+Lua 脚本防止超卖

  • 高频发下,传统先查库存再扣减不安全。采用 Redis 加 Lua 脚本方案,将查询和扣减库存逻辑封装到同一 Lua 脚本中。
  • 因 Redis 执行 Lua 脚本是原子性的,脚本执行期间无其他命令插入,物理上杜绝并发竞争导致的库存超卖,扣减成功返回 1,库存不足返回 0,无中间状态。

快速失败策略

若 Redis 集群中某节点(如 Node B)宕机,负责该分片区(如 Bucket34-66)的用户请求直接报错返回,提示抢购失败。宁愿牺牲部分用户体验,也要保住系统整体存活。

数据持久化与最终一致性

  • Redis 扣减成功即认为用户抢购成功,直接返回前端。随后将订单消息发送到 Rocketmq 削峰,消费者消费消息写入数据库,实现最终一致性。
  • 极端情况(Redis 扣减但 DB 未写入),活动结束后通过 T+1 对账程序回滚或补偿。

设计思路总结

四个核心设计思路:

  • 一是流量漏斗,层层拦截,避免数据库直面用户;
  • 二是库存分片,化整为零解决单点热点瓶颈;
  • 三是借助 Lua 脚本实现原子操作,物理隔绝超卖风险;
  • 四是舍得智慧,极端情况下熔断宕机以保系统存活。
架构图解

500万QPS
DNS分流
多机房部署
机房A: 125万QPS
机房B: 125万QPS
机房C: 125万QPS
机房D: 125万QPS
Nginx限流
应用服务集群
Redis集群
MQ削峰
数据库集群
关键设计
异地多活
流量调度
熔断降级
限流保护


第66题:分布式 ID 生成方案全解析

数据库自增 ID 存在三个硬伤:

  • 维护难,分库分表时不同库 ID 易冲突,数据迁移合并复杂;
  • 数据裸奔,连续 ID 易被爬虫获取并推算业务量;
  • 性能问题,InnoDB 引擎下会引发锁竞争和热点页问题,拖垮数据库性能。

主流分布式 ID 解决方案分类

  • 不推荐方案包括 DB 自增(大厂禁用)、DB 号段(依赖数据库有单点风险)、Redis 自增(增加架构复杂度,RBD 持久化可能导致 ID 重复)、UUIDv4(完全无序导致数据库频繁页分裂,性能极差)。
  • 推荐方案有美团 Leaf(号段模式和雪花算法模式)、雪花算法、UUIDv7(版本答案)。

雪花算法详解

  • 雪花算法是 64 位整型数据结构,前 41 位存储时间戳(支持系统运行 69 年不重复),中间 10 位是机器 ID(支持 1024 个节点),最后 12 位是序列号(同一毫秒内同一机器可生成 4096 个 ID)。
  • 性能极高,但存在致命缺点:时钟回拨,若服务器时间回调会导致 ID 重复,金融系统中是严重生产事故。

美团 Leaf 方案

  • 美团 Leaf 提供两种模式解决雪花算法不足。
  • 号段模式核心是双 buffer 预加载,当 ID 快用完时后台线程异步去数据库取一批放内存,应用层拿 ID 是纯内存操作,零阻塞性能稳。
  • 雪花模式引入 ZooKeeper 管理机器 ID,每次启动校准时间,彻底解决 ID 重复隐患。

UUIDv7 方案

  • UUIDv7 在高位嵌入毫秒级时间戳,实现趋势有序写入,数据追加式写入。
  • 保留 UUID 全局唯一、无需中心化生成的优点,同时拥有媲美主键自增的写入性能,是新项目推荐选择。

分布式 ID 方案选型建议

  • 首选 UUIDv7,新项目尤其云原生环境且不想维护额外中间件时性价比最高。
  • 其次选雪花算法,追求极致性能且能搞定服务器时间同步问题时,仍是 Java 领域王者。
  • 超大规模系统(如电商、外卖)需高可用和监控,引入美团 Leaf 最稳妥。
架构图解

分布式ID生成
UUID
数据库自增
雪花算法
号段模式
无序
索引不友好
单点瓶颈
暴露业务量
时间有序
高性能
时钟回拨问题
批量获取
数据库压力小


第67题:Redis 持久化机制详解

  • RDB 是定时快照机制,性能好且文件小,但两次快照间的数据在宕机时会丢失。
  • 需通过 save 配置控制快照触发条件,如 "save 900 1" 表示 900 秒内有 1 次写入就快照,"save 60 10000" 表示 60 秒内有 10000 次写入才快照。
  • 生产环境应根据业务调整 save 组合策略,数据重要时缩短时间窗口(如 "save 300 10""save 60 1000"),降低数据丢失风险;数据不重要时拉长间隔以减少 IO 压力。

AOF 持久化

  • AOF 通过记录命令日志实现持久化,核心参数 appendfsync 有三个选项。
  • always:每条命令立即刷盘,数据最安全(宕机最多丢 1 条命令),但性能最差(TPS 从 5 万降至 5 千);
  • everysec:每秒刷盘,最多丢 1 秒数据,性能与安全平衡,为默认配置;
  • no:由操作系统决定刷盘时机,性能最好,但可能丢失几十秒甚至几分钟数据。业务选择需结合数据重要性,如支付订单用 everysec,用户访问日志可用 no,建议通过压测对比不同策略的 TPS 和延迟。

AOF 重写优化

  • AOF 文件过大会触发重写(合并冗余命令),但重写时 fork 子进程会导致内存占用翻倍(如 8G Redis 可能需 16G 内存),易 OOM,且磁盘 IO 飙升可能引发服务卡顿。
  • 优化措施包括:设置 auto-aof-rewrite-min-size 为 64MB 或 128MB,避免小文件频繁重写;设置 auto-aof-rewrite-percentage 为 100,文件增长一倍再重写以减少次数;设置 no appendfsync on rewrite 为 yes,重写时暂停主进程刷盘避免磁盘竞争。需监控重写期间的内存使用、磁盘 IO 及主进程延迟变化。

混合持久化

Redis 4.0 后支持混合持久化(RDB+AOF),AOF 文件前半部分为 RDB 全量数据,后半部分为 AOF 增量命令。重启恢复时先加载 RDB(速度快),再加载少量 AOF 增量命令(数据完整),配置 aof-use-rdb-preamble yes 即可开启。测试显示,5GB 文件纯 AOF 加载需 5 分钟,混合持久化仅需 30 秒,恢复速度提升 10 倍,是兼顾性能与数据安全的最优解。

持久化监控告警

持久化需配合监控告警机制,通过 info persistence 命令查看关键指标:rdb_last_save_time(上次 RDB 快照时间,超时告警);aof_current_size 和 aof_base_size(计算 AOF 文件增长速度,预估重写时间);rdb_bgsave_in_progress 和 aof_rewrite_in_progress(监控持久化操作状态);aof_last_write_status 和 rdb_last_bgsave_status(持久化失败需立即告警)。建议编写监控脚本对接告警系统,确保持久化异常及时通知。

架构图解

Redis持久化
RDB快照
AOF日志
混合持久化
定时保存数据快照
文件小, 恢复快
可能丢失最后一次快照数据
记录每个写操作
数据更安全
文件大, 恢复慢
RDB+AOF组合
兼顾两者优点


第68题:百万级用户自动登录系统架构设计

三层存储设计思路:

  • 客户端存储 Token(cookie 或移动端 keystore) ;
  • Redis 缓存(核心,99% Token 验证请求命中,响应速度快) ;
  • MySQL 数据库兜底(Redis 未命中或故障时降级查询,保证高可用) 。
  • 架构理念:JWT 负责无状态验证,Redis 负责状态管控,MySQL 负责持久化存储,兼顾性能与可靠性 。

登录流程

共 5 个步骤:

  • 验证账号密码(使用 BCrypt 安全加密算法) ;
  • 生成 JWT Token(包含 JTI、用户 ID、过期时间) ;
  • 清理旧 Token(从 Redis set 集合批量删除用户所有旧 Token,防止泄露) ;
  • 三层存储(新 Token 同步写入 Redis 映射、MySQL 持久化及客户端 cookie,保证一致性和可靠性) ;
  • 返回用户信息,完成登录 。流程核心思想:安全优先,性能兜底,每步有错误处理和降级方案 。

Token 验证流程

五个环节:

  • 提取 Token(从客户端 cookie 或移动端请求头获取) ;
  • 黑名单校验(检查 Token 是否在黑名单,防止被盗用 Token 继续使用) ;
  • Redis 验证(主流程,99% 请求通过 Token 的 JTI 查询用户信息,响应速度几毫秒) ;
  • MySQL 兜底(Redis 未命中或故障时降级查询,保证高可用) ;
  • 验证通过,设置用户上下文供后续业务逻辑使用 。

安全防护措施

六层安全措施:

  • 短期有效机制(Token 有效期 7 天,支持自动续期) ;
  • 加密传输(全站 HTTPS 加密,移动端 keystore 安全存储) ;
  • 防 CSRF 攻击(使用 samesite=lax 的 cookie 配置) ;
  • 黑名单机制(Redis 存储 Token 的 JTI,异常登录时加入黑名单强制用户下线) ;
  • 设备绑定(校验设备 IMEI 或 user agent,防止跨设备盗用) ;
  • 二次验证(敏感操作要求输入验证码确认) 。

实战踩坑案例

Redis Keys 命令导致性能灾难:

  • 错误做法是使用模糊匹配批量删除,导致 Redis 阻塞、系统崩溃(Keys 命令全表扫描,时间复杂度 O (n),生产环境禁止使用) ;
  • 正确做法是改用 Redis 的 set 集合,为每个用户维护 usertokens+userid 的 set 集合,批量删除操作从 3 秒降至几毫秒 。

SameSite 配置导致转化率下降:

  • 错误做法是设置 SameSite=strict,外部链接跳转需重新登录,转化率下降 18% ;
  • 正确做法是改用 SameSite=lax 模式,允许顶级导航携带 cookie,转化率提升 23%,同时防御 CSRF 攻击 。

性能优化

三方面优化:

  • Redis 集群(部署主从集群,实现 99% 缓存命中率) ;
  • 分库分表(用户表按 id 分 16 个库,查询速度提升) ;
  • 索引优化(建立唯一索引,查询效率从全表扫描变为索引查找) 。
  • 优化后 QPS 从几千提升到 5 万,支撑百万级用户并发访问 。

总结

  • 架构设计:三层存储(JWT+Redis+MySQL),职责清晰易扩展 ;
  • 安全防护:六大措施(黑名单、设备绑定等)形成安全闭环 ;
  • 性能优化:Redis 集群、分库分表等实现 5 万 QPS 高并发支撑 。
  • 核心理念:安全优先,性能兜底,体验为王,已在生产环境稳定运行 。
架构图解

Redis 服务端 App 用户 Redis 服务端 App 用户 alt [RefreshToken有效] [RefreshToken过期] alt [Token有效] [Token过期] Token续期机制 打开App 读取本地Token 携带Token请求 验证Token 自动登录成功 携带RefreshToken 验证RefreshToken 验证通过 生成新Token 返回新Token 需要重新登录


第69题:海量数据查询优化面试题解析

首先要主动分析业务场景,这是展示架构思维的第一步。

系统中订单查询主要有三类场景:

  • 第一类占比 65% 是买家查询近期订单,需 100 毫秒内响应;
  • 第二类占比 25% 是商家多条件筛选,500 毫秒内可接受;
  • 第三类占比 10% 是报表统计和历史订单,可走离线查询。重点优化前两类高频场景。

整体架构设计

方案采用四层架构,从存储、缓存、检索、稳定性四个方面系统化优化。

存储层优化

设计双表结构(买家表和商家表),分别按买家 ID 和商家 ID 分 16 个库,每个库按月分表。

买家表分库规则为 buyer_id 模 16,分表规则为买家 ID 模 8 加上年月(如 buyer_order_0_202401);

商家表类似。

解决了单表 3 亿数据查询慢问题,买家和商家查单可直接定位到表。

数据一致性方案

采用主表 + MQ 异步同步:订单先写入买家主表,发送 MQ 消息异步写入商家表。允许秒级延迟,MQ 重试 3 次失败后记录到补偿表,每天凌晨 3 点自动对账修复,保证最终一致性。

缓存层优化

用 Redis 做缓存,实施防穿透、防击穿、防雪崩策略,保证缓存命中率 90% 以上。防穿透:Redis 前加布隆过滤器拦截,或对空结果缓存 5 分钟;防击穿:用 Redisson 分布式锁,第一个请求查库并回填缓存,其他请求等待;防雪崩:TTL 加随机偏移(如基准 30 分钟,设置 30±5 分钟随机值)。

检索层优化

用 ES 存储 30 天热数据,服务商家多条件筛选场景。查询方式从 must 改为 filter,性能提升 30%,同时设置降级策略,ES 挂了则查 MySQL。

稳定性层优化

用 Sentinel 做限流和降级,保证极端情况下系统不崩溃。实时监控缓存命中率,低于 90% 告警。

查询完整流程

用户请求经网关鉴权限流后查 Redis 缓存,命中则 10 毫秒响应;未命中时,买家查单直接查 MySQL 双表(80 毫秒),商家筛选走 ES(150 毫秒),历史订单查 HBase(300 毫秒)。

优化效果数据

买家查询 P99 延迟从 5.8 秒优化到 60 毫秒(提升 99.7%),商家筛选 P99 从 9 秒优化到 320 毫秒(提升 96.4%),MySQL CPU 使用率从 85% 降至 35%,缓存命中率稳定在 90% 以上,支持双十一峰值 QPS 1200 且 0 故障。

扩展方案(分库扩容)

未来数据量增长需从 16 库扩容到 32 库时,步骤为:

  1. 数据迁移(仅迁移 50% 数据,用 DataX/Canal 工具 3-5 天完成);
  2. 双写 7 天(新订单同时写入老库和新库,MQ 保证不丢数据);
  3. 灰度切流(5%→30%→100%);
  4. 对账补偿。

缓存一致性策略

采用延迟双删策略:1. 删除缓存;2. 更新数据库;3. 延迟 200 毫秒(覆盖主从同步延迟);4. 再次删除缓存。删除失败则异步重试(最多 3 次),仍失败进入补偿表,凌晨对账修复。

成本优化(分层存储)

按数据热度分层存储:热数据(7 天内)用 MySQL+Redis(50 毫秒响应);温数据(8-30 天)用 ClickHouse(300 毫秒);冷数据(30 天以上)归档到 HDFS(离线查询)。存储成本降低 60%(年省 200 万),MySQL 压力降低 70%。

踩坑经验总结

  • 只按买家 ID 分库:商家查询需扫 16 个库导致 8 秒超时,紧急改造双表设计后性能提升 10 倍,但迁移耗时 2 个月。教训:架构设计需考虑所有业务角色。
  • 先更新 DB 再删缓存:高频场景下出现缓存永久脏数据,改用延迟双删 + 异步重试 + 兜底对账解决。教训:分布式一致性需考虑极端并发。
  • 40 亿历史数据全存 ES:导致 ES 集群 OOM 崩溃,改为 30 天热数据存 ES(3000 万条)+ 历史数据 HBase,成本降低 60%。教训:技术选型需考虑成本,非所有数据需实时检索。

答题框架总结

核心思路:存储(双表 + 16 库分片)、缓存(防三坑 + 高命中率)、检索(ES + 降级)、稳定性(限流 + 降级 + 监控)。优化效果:买家 P99 60ms、商家 320ms,MySQL CPU 35%,缓存命中率 90%+。综合收益:性能提升 96%+,成本降低 60%,双十一 0 故障。答题公式:场景分析→四层架构→逐层展开→数据证明→踩坑经验。

架构图解

海量数据查询
索引优化
分库分表
读写分离
联合索引
覆盖索引
索引下推
水平分片
垂直分片
主库写入
从库读取
查询优化
避免全表扫描
限制返回字段
分页优化


第70题:防止接口重复提交的解决方案

接口重复提交的根源是 "一次操作,多次请求",主要场景包括用户侧因网络卡顿、页面无反应导致的重复点击,或恶意重复请求。可能导致重复创建订单、扣减库存、扣款等严重业务异常。

前端拦截方案

前端拦截是成本最低的方案,通过用户操作第一线阻止重复请求,主要有两种实现方式:

  • 一是按钮禁用,用户点击提交后立即用 JS 将按钮设为 disable 并显示 loading,在后端返回结果前不可点击;
  • 二是请求防抖,设置定时器,在用户最后一次点击后几百毫秒再发送请求,期间重复点击则重新计时。
  • 优点是实现简单、成本低,能拦截大部分用户误操作;缺点是无法防恶意绕过,需配合后端方案。

令牌机制

令牌机制是后端通用防重方案,核心流程类似 "领票 - 持票 - 检票":

  • 第一步,用户进入表单页面时,客户端向服务器申请唯一 token,服务器生成 token 存入 Redis 并设置过期时间(如 5 分钟);
  • 第二步,用户提交表单时,请求 header 或参数携带该 token;
  • 第三步,服务器收到请求后,执行 Redis 的 "检查并删除" 原子操作,若 token 存在且删除成功则为有效请求,放行处理业务;重复请求因 token 已被删除,检票失败被拒绝。

分布式锁方案

分布式锁适用于高并发场景(如秒杀),逻辑是通过唯一业务标识争抢锁:

  • 首先确定锁对象(如用户 ID + 商品 ID 构成唯一业务操作);
  • 然后请求同时争抢该锁,在 Redis 中使用 setnx(仅 key 不存在时设置成功)命令实现原子抢锁,只有第一个请求能抢到锁并执行业务逻辑,执行完毕后释放锁;
  • 未抢到锁的请求直接返回 "手慢了" 或 "系统繁忙"。

数据库唯一索引方案

  • 唯一索引是数据一致性的兜底方案,在业务表中对唯一标识字段(如订单号)添加唯一约束。
  • 当第一个请求插入数据成功后,重复请求插入相同唯一标识时,数据库会因唯一性约束抛出异常,应用层捕获异常后返回 "请勿重复操作" 提示。
  • 该方案简单、高效、可靠,是防止脏数据的最后一道防线。

接口幂等设计

幂等设计适用于可能被重复调用的接口(如第三方支付回调),核心是保证操作执行一次与多次的系统影响一致。

以支付回调为例:

  • 接口收到请求后,先根据订单 ID 查询订单当前状态;若状态为 "待支付",则执行添加余额、更新状态为 "已支付" 等操作;
  • 若状态为 "已支付" 或 "已完成",则直接返回成功响应,不重复处理。
  • 通过 "先查后改" 和状态判断实现幂等。

各方案的场景选型

前端拦截:成本最低,优化用户体验,适合拦截普通用户误操作。

令牌机制:通用后端防重方案,适合绝大多数表单提交场景。

分布式锁:为高频发、高并发场景(如秒杀)量身定做,强并发控制。

唯一索引:简单可靠的兜底方案,保证数据一致性。

幂等设计:通过状态机保证多次调用结果一致,是外部回调接口的标配。实际项目中常组合使用(如前端拦截 + 令牌 + 唯一索引)构建纵深防御体系。

架构图解

成功
失败
用户提交
生成唯一RequestID
Redis SETNX
执行业务逻辑
返回重复提交提示
业务处理完成
删除Redis Key
防重复提交方案
前端按钮禁用
Token机制
分布式锁
数据库唯一索引
幂等性保障
唯一ID
状态机校验
乐观锁版本号


第71题:HTTP 轮询与 WebSocket 深度解析

客户端每隔几秒发送请求询问是否有新消息,服务器响应后断开连接,反复进行。这种方式类似反复打电话询问,会造成资源浪费。

WebSocket 长连接特性

  • 一次握手后保持连接,双方可随时通信。
  • 服务器有新消息可直接推送,客户端发送数据也能立即传输。
  • 生产环境测试显示,相比 HTTP 轮询,WebSocket 能将 CPU 使用率从 70% 降低到 15%,消息延迟从 5 秒缩短到 300 毫秒,并发能力提升 3 倍以上。

WebSocket 断线重连解决方案

心跳保活机制

客户端每隔 30 秒发送 ping 包作为心跳,服务器返回 pong 包。若连续三次无响应则判定为断线,类似聊天时发 "在吗" 确认对方在线状态。

消息补发机制

客户端记住最后收到的消息 ID,重连后询问服务器断联期间遗漏的消息,服务器从数据库查询并补发,保证消息 0 丢失。

服务器集群同步方案

采用 Redis 消息订阅机制,所有服务器订阅同一个 Redis 频道。当某台服务器收到用户消息后向频道广播,其他服务器收到广播后推送给对应用户,解决集群消息孤岛问题,确保消息精准送达。

面试答题模板

场景

如实时监控项目,需实现消息秒级更新。

痛点

使用 HTTP 轮询时,服务器 CPU 跑满,消息延迟 5 秒,用户投诉不断。

方案

换成 WebSocket 长连接,添加心跳保活和断线重连机制,集群采用 Redis 订阅机制同步消息。

结果

CPU 使用率从 70% 降至 15%,延迟缩短到 300 毫秒,并发提升 3 倍。

总结

WebSocket 核心是一次连接持续通信,解决了 HTTP 轮询的资源浪费和延迟问题。结合心跳重连和 Redis 集群同步机制,形成能扛住高并发的方案。面试时清晰阐述此逻辑可提高通过率。

架构图解

服务端 客户端 服务端 客户端 HTTP轮询模式 loop [定时轮询] WebSocket长连接模式 loop [实时推送] 对比: WebSocket更高效实时 HTTP请求 响应数据 建立WebSocket连接 连接建立成功 主动推送消息 发送消息 响应确认


第72题:MySQL DATETIME 存储的 "幽灵一秒" 问题

面试场景问题:用户禁言时间需设为两天后 23:59:59,代码中日期正确,但数据库存储后约一半数据变为次日 00:00:00。现象表现为 Java 程序中的日期对象显示 23:59:59,从数据库查询时部分结果进位至次日零点。

现象分析

Java 日期对象包含毫秒数,而数据库 DATETIME 字段默认不存储毫秒信息。当 Java 日期的毫秒数≥500 时,MySQL 会对时间进行四舍五入,导致秒位进位至次日 00:00:00;当毫秒数 < 500 时,小数部分被舍去,时间保持 23:59:59。

原因解析

高并发环境下,程序生成的时间毫秒数在 0-999 范围内随机分布。由于≥500 毫秒和 <500 毫秒的概率各约 50%,导致数据库中约一半数据进位,出现 "一半对一半错" 的现象。

解决方案

治本方案:升级数据库精度

通过 ALTER TABLE 语句将时间字段类型修改为 DATETIME (3),使数据库支持毫秒级存储,从根本上避免四舍五入问题。优点是彻底解决且一劳永逸,缺点是需要 DBA 权限,流程可能较长。

治标方案:代码层清零毫秒

在代码中主动将日期的毫秒(或纳秒)部分清零,确保存入数据库的时间无小数部分。优点是快速可控,无需数据库变更;缺点是丢失毫秒级精度,属于技术妥协。

验证与总结

将字段改为 DATETIME (3) 后,数据库可完整记录毫秒数,所有数据均保持 23:59:59;代码层清零毫秒后,同样可避免进位问题。该问题根源是 Java 与 MySQL 的时间精度差异及 MySQL 的四舍五入机制,需根据实际场景选择合适的解决方案。

架构图解

MySQL幽灵一秒
问题原因
解决方案
Datetime精度
时区问题
闰秒处理
使用TIMESTAMP
统一时区
应用层处理
最佳实践
存储UTC时间
显示时转换时区
使用bigint存时间戳


第73题:Redis 面试实战场景与进阶追问

  • 电商商品详情页性能优化 项目中详情页每天有几千万请求,未加缓存时所有请求直接访问 MySQL,需关联 5 张表,单次查询耗时 180 毫秒,大促期间 MySQL CPU 使用率达 100%,接口响应时间超过 500 毫秒。

    解决方案是使用 Redis 缓存热点商品,以商品 ID 为 key,存储完整 JSON 作为 value,设置热点数据自动过期,并进行缓存预热:每天凌晨 3 点将 TOP10000 数据预热进 Redis,大促前 1 小时再补充一次。

    效果显著,预热后 MySQL 的 QPS 从 2000 降至 160,Redis 的 QPS 从 500 飙升至 8000,大量请求通过 Redis 处理。

  • 秒杀超卖场景 系统部署 4 台机器做负载均衡,秒杀活动中因单机锁在分布式环境下失效,导致库存 100 件商品超卖至 130 件,引发用户投诉。

    通过 Redis 分布式锁解决,保证分布式场景下扣减库存的原子性,防止超卖。需进一步考虑分布式锁可能出现的误删他人锁、死锁等问题并提前准备应对方案。

  • Session 共享解决分布式登录问题 系统上线初期,用户登录后频繁跳转至登录页,原因是 Session 存储在服务器内存和客户端 Cookie 中,用户首次访问服务器 B 登录成功,第二次访问其他服务器时因该服务器无对应 Session 判定未登录。

    解决方案是使用 Redis 集中存储 Session:用户登录成功后生成 UUID 作为 Token 存入 Redis,后续请求通过 Token 查询 Redis 验证登录状态,查到则为登录状态,未查到需重新登录。

Redis 进阶追问

缓存与数据库数据不一致问题 常见场景如商品价格更新后,数据库已更新但 Redis 仍为旧数据,导致用户看到错误价格。

项目中采用先更新数据库再删除缓存的策略,原因是数据更新频繁时更新缓存计算开销大,且先删缓存再更新数据库可能导致并发请求写入旧数据。

为进一步避免问题,增加延迟双删策略:更新数据库后删除一次缓存,延迟 500 毫秒再删除一次。对于账户余额等要求高一致性的场景,直接查询数据库。

缓存穿透、击穿、雪崩的解决 缓存穿透指查询不存在的数据,可使用布隆过滤器拦截 99% 的无效请求;缓存击穿是热点数据过期导致大量请求直达数据库,核心商品可设置永不过期;缓存雪崩是大量缓存同一时间失效,可通过为每个缓存设置随机过期时间,并结合本地缓存兜底解决。

Redis 单线程为什么快 Redis 快的原因包括纯内存操作、单线程避免上下文切换、高效的数据结构,其性能瓶颈通常在网络 IO。

架构图解

Redis实战场景
缓存穿透
缓存击穿
缓存雪崩
布隆过滤器
缓存空值
热点数据永不过期
互斥锁重建
随机过期时间
多级缓存
进阶追问
数据一致性
并发竞争
热点Key处理


第74题:Redis 集群方案

主从复制是 Redis 集群的基础方案,核心为一主多从架构与读写分离。

主节点负责所有写操作(如存商品、改价格),从节点从主节点同步数据并仅处理读操作(如用户刷商品列表、查库存)。

数据同步分为两步:全量同步(首次复制整个数据集)和增量同步(仅同步修改记录)。

  • 优点包括数据备份(主节点故障时从节点有完整数据)和读写分离(主节点扛写 QPS,多个从节点分担读 QPS,适合读多写少场景)。
  • 缺点是主节点故障后需手动切换从节点为新主。适用场景如区域生鲜电商(5000 个 SKU,每天 2 万单,读请求是写的 20 倍),采用一主两从架构,主节点处理商品上架 / 改价格,从节点对接 APP 和小程序查询。

哨兵模式

哨兵模式是主从复制的升级方案,在主从架构基础上增加哨兵节点实现自动故障转移。需部署奇数个哨兵(通常 3 个,避免投票平票),哨兵持续监控主从节点。

当主节点故障时,流程为:

  • 首先多个哨兵确认主节点状态(主观下线→客观下线,避免误判);
  • 然后从从节点中选举最优节点升级为新主;
  • 最后通知其他从节点同步新主数据,并告知客户端连接新主。

核心优势是高可用,主节点故障后可自动切换,无需人工干预。

缺点是仍为单主架构,写操作集中在单个主节点,数据量过大(如超 50GB)或写 QPS 过高(如超 5 万)时单机无法承载。

适用场景如在线教育平台用户 Session(每天 10 万活跃用户),采用一主两从 + 三哨兵架构,确保主节点故障时用户无需重新登录。

Cluster 集群

  • Cluster 集群是 Redis 的分布式集群方案,核心为数据分片机制,可解决海量数据和高并发问题。 将所有数据映射到 16384 个哈希槽,每个主节点负责一部分槽位(如 3 个主节点可分别管理 0-5460、5461-10922、10923-16383 槽),数据按 Key 哈希值分配到对应槽位,分散存储在不同主节点。 每个主节点配有从节点,主节点故障时从节点自动接替。
  • 优势是支持水平扩展(数据增多时增加主节点并重新分配槽位,QPS 过高时多主节点共同承载)。
  • 缺点是不支持多 Key 跨节点操作(同时修改不同主节点的 Key 会报错),部署和维护复杂度高于前两种方案。适用场景如头部电商大促(三百万单数据,超100GB,写 QPS 峰值 8 万),采用七主七从架构,按用户 ID 分片,峰值可扛 15 万 QPS。

选型口诀

小项目测试环境(需数据备份或读写分离)选主从复制,简单省钱;

中型生产系统(数据量单机能承载但需高可用,如筛选、秒杀、库存)选哨兵模式,自动容错;

海量数据高写并发场景(如大电商订单、社交平台,单机无法承载)选 Cluster 集群,支持分片与扩容。

架构图解

Redis集群方案
主从复制
哨兵模式
Cluster集群
一主多从
读写分离
手动故障转移
自动监控
自动故障转移
数据分片
去中心化
16384槽位
选型建议
小规模: 主从
中等规模: 哨兵
大规模: Cluster


第75题:微信扫码登录原理详解

面试官主要考察两点:

  • 一是能否透过现象看本质,理解扫码登录是登录设备向未登录设备传递信任的过程;
  • 二是是否具备安全设计意识,如扫码加确认两步操作、二维码过期设置的原因,这些都是防盗扫、防误扫的关键设计。

扫码登录的三个核心角色

待登录设备:如 PC 端,需要获取登录权限

服务端:微信后台,负责身份判断与信息验证

可信设备:手机端,已登录状态,用于授权验证

第一阶段:生成二维码

第一步:PC 端请求二维码

PC 端向服务器发送请求,申请生成二维码,同时带上设备型号、IP、操作系统等信息,用于防止二维码被截取。

第二步:服务器生成唯一凭证

服务器生成一次性的唯一 ID,将二维码 ID、PC 设备信息、过期时间(2 分钟)、当前状态(未扫码)存储到 Redis 中(Redis 读写快且支持自动过期删除)。

第三步:PC 端轮询等待

PC 端每隔三秒向服务器发送请求,询问二维码是否被扫描。

第二阶段:手机扫码

第一步:解析二维码信息

手机扫码后解析出二维码 ID,并带上手机端已登录的 token(相当于手机的 "身份证",由服务器在手机登录时颁发)。

第二步:手机发送验证请求

手机向服务器发送包含二维码 ID、手机 token、手机设备信息的请求,服务器校验 Redis 中二维码 ID 是否存在、未过期、状态为未扫码,同时验证 token 有效性、用户真实性及设备信息匹配度。

第三步:更新状态与二次确认

服务器将状态更新为 "已扫码、待确认",手机端弹出确认框,此为第二次保险,防止误扫他人二维码。

第三阶段:确认登录

第一步:手机发送确认请求

用户在手机点击确认登录后,手机再次向服务器发送包含二维码 ID 和手机 token 的请求,防止中途 token 失效。

第二步:服务器最终验证

服务器确认 token 有效、二维码未过期、状态为待确认、PC 端设备信息未变后,将状态改为 "已确认、登录成功",并删除二维码 ID 避免重复使用。

第三步:PC 端完成登录

PC 端通过轮询获取登录结果,服务器生成 PC 端专属 token(有效期 2 小时),PC 端使用该 token 请求用户信息完成登录。

异常场景处理

二维码过期处理

Redis 设置二维码 2 分钟自动过期,PC 端轮询时收到过期提示后重新生成二维码。2 分钟是安全与用户体验的平衡点:太短用户来不及操作,太长会增加盗扫风险。

误扫他人二维码

服务器检查微信账号与 PC 端是否有历史关联,若无则发出异常登录提醒,PC 端需补充验证(如输入微信支付密码)。

手机扫码后断网未确认

二维码状态保持为 "待确认",直到 2 分钟过期;手机恢复网络后可继续确认,过期则提示重新扫码。

架构图解

数据库 应用服务器 微信服务器 用户 数据库 应用服务器 微信服务器 用户 OAuth2.0授权流程 扫描二维码 回调通知扫码 记录扫码状态 返回确认 确认登录 回调授权码 获取AccessToken 返回Token 获取用户信息 返回用户信息 创建用户会话 登录成功


第76题:Token 续期方案

原理:用户发送请求时携带 token,服务端校验有效后返回新 token(有效期 30 分钟),用户持续活跃则不断刷新。

优点是实现简单;缺点包括安全风险(token 被盗后黑客可持续获取新 token)和并发问题(多次请求可能返回多个 token,导致客户端使用冲突),因此不推荐使用。

刷新令牌机制(Refresh Token)

业界主流且安全的推荐方案,核心是双令牌解耦:access token(访问权,短期)和 refresh token(续期权,长期)。流程:用户登录通过验证后,服务端返回双令牌;日常访问使用 access token 获取业务数据;当 access token 过期(如 1 小时),客户端携带 refresh token 请求新的 access token,整个过程对用户透明。

服务端自动续期

用户请求时服务端先校验 token 有效性,无效则返回 401 错误;有效时检查有效期,若大于 5 分钟直接返回业务数据,若小于等于 5 分钟则返回业务数据并附带新 token,客户端替换旧 token 即可。

Token 续期方案总结

三种方案包括滑动窗口续期、refresh token 和服务端自动续期,推荐使用 refresh token 机制。实际应用中通常结合 JWT(access token 采用 JWT 格式,refresh token 使用普通 token 格式)。

架构图解



Token即将过期
RefreshToken有效?
发送续期请求
验证RefreshToken
生成新AccessToken
返回新Token
需要重新登录
续期策略
滑动过期
固定过期
无感续期
每次使用延长有效期
固定时间后过期
后台自动续期
安全措施
RefreshToken单次使用
IP绑定校验
设备指纹校验


第77题:消息队列在项目中的应用

项目中使用消息队列是典型的项目经验题,需结合具体场景回答,避免仅罗列概念。应说明项目痛点(如接口响应慢、服务依赖复杂、流量峰值高)、解决方案(引入 MQ)及优化效果(数据对比),体现实战应用价值。

消息队列的三大核心作用及实战场景

解耦:避免服务依赖导致全链路崩溃

电商订单系统未用 MQ 时,下单流程需同步调用库存、支付、短信服务。存在问题:新增服务(如物流)需修改订单服务代码;某服务故障(如短信服务卡顿 5 秒)会阻塞主流程,导致订单存档但库存未扣等数据不一致问题,需人工对账。引入 MQ 后,订单服务只需将消息发送至 MQ,下游服务(库存、支付、短信、物流)订阅消息即可。新增服务无需修改订单服务代码,下游服务故障不影响主下单流程,实现服务解耦。

异步:优化接口响应时间

未用 MQ 时,订单服务响应慢:存订单(50ms)+ 调库存(100ms)+ 调支付(200ms)+ 调短信(800ms),总耗时 1.2 秒,用户体验差。通过 MQ 将非核心服务(如短信)异步化后,主流程仅处理存订单、库存、支付(共 350ms),用户下单后立即返回,短信服务异步执行。接口响应时间从 1.2 秒优化至 350ms,提升用户体验和系统吞吐量。

削峰:应对秒杀流量洪峰

秒杀场景下,流量洪峰(3000QPS)直接冲击数据库会导致数据库崩溃。引入 MQ 后,消息可暂时积压,数据库端按自身处理能力匀速消费(如 500QPS),将瞬间流量洪峰转为平缓水流,保护下游数据库不被高并发冲垮。

面试答题模板

结合项目场景(如电商订单系统),说明痛点:未用 MQ 时接口响应慢(用户等待 1 秒以上)。解决方案:引入 MQ(如 RabbitMQ),将非实时需求(如短信通知)异步处理。优化结果:接口响应时间从 1 秒降至 100ms,体现消息队列解决实际问题的价值。回答需包含痛点、方案、结果,突出实战经验。

架构图解

应用场景
MQ核心作用
生产者
消息队列
消费者
解耦
异步
削峰
订单处理
日志收集
通知推送
数据同步
可靠性保障
消息持久化
确认机制
重试策略


第78题:业务线程池的共享与独享选择

线程池的共享和独享本质上是资源互用和风险隔离的权衡。

共享线程池的特点

优点:资源利用率高(线程可反复利用,适合大量零散短任务)、管理简单(仅维护一个池子,配置和监控方便)。缺点:缺乏隔离性,一个慢任务或 bug 可能拖垮整个业务,导致业务雪崩和系统瘫痪。

独享线程池的特点

优点:强隔离性(各业务互不影响)、可针对业务特性(CPU 密集型 / IO 密集型)精细化调优参数、问题定位清晰(监控可明确业务责任)。缺点:资源开销大(总线程数多,内存和 CPU 切换开销增加)、管理复杂(需维护多个线程池实例)。

线程池选择的决策维度

业务性质

CPU 密集型任务(如复杂计算):需独享,避免长期霸占 CPU 影响其他任务。

长耗时 IO 任务(如调用第三方接口):需独享,第三方服务稳定性不可控,防止响应慢导致线程阻塞拖垮主营业务。

短耗时 IO 任务(如 Redis 查询、简单数据库查询):适合共享,任务执行快、线程周转快,可最大化资源互用。

业务优先级

核心业务(如下单、支付):需独享,确保绝对稳定,不能出问题。

非核心业务(如发短信、记录用户行为日志):适合共享,对实时性要求低,可容忍轻微延迟。

业务体量

高流量洪峰业务(如秒杀场景):需独享,且需配置动态扩容和拒绝策略,避免请求量突增时影响其他业务。

常规 / 零星任务(并发量平稳、无突刺):适合共享,资源利用率高。

实战中的混合模式设计

实战中极少采用单一模式,混合模式是主流。设计示例:
核心流程(下单、支付):配置各自的独享线程池,保证绝对稳定。

秒杀等洪峰业务:配置可动态扩容的独享线程池,应对流量波动。

非核心辅助功能(发短信、记录日志):统一使用共享线程池,节约资源。

总结

面试官考察的核心是对资源复用与风险隔离矛盾的理解和权衡。选择需结合任务性质、优先级和体量:核心 / 高频业务用独享确保稳定隔离,常规 / 非核心业务用共享提高资源利用率。

架构图解

线程池选择
共享线程池
独享线程池
资源利用率高
任务相互影响
适合非核心业务
隔离性好
资源独立
适合核心业务
舱壁隔离
核心业务独立池
非核心业务共享池
最佳实践
按业务重要性划分
动态调整参数


第79题:Redis CPU 飙升问题排查与解决

第一步:定位问题(slow log 慢查询)

打开 Redis 客户端,使用 slow log 慢查询命令找出执行耗时较长的操作,优先定位可能导致 CPU 飙升的命令。
第二步:找出热点 key(redis-cli hotkeys)

通过 redis-cli hotkeys 命令识别被频繁访问的热点 key,这些 key 可能导致单机流量集中,引发 CPU 瓶颈。
第三步:开启监控(业务低峰期)

在业务低峰期(如凌晨)开启 Redis 监控功能,辅助分析实时操作,但需注意监控会影响性能,需谨慎使用。

常见问题定位

复杂命令导致 CPU 飙升

如 keys、hgetall 等命令会遍历所有 key,大量执行时直接占用 CPU 资源,是常见的 CPU 飙升诱因。

大 key 问题

单个 key 存储几十兆数据时,序列化和网络传输开销会显著占用 CPU,导致负载过高。

热点 key 问题

某一 key 被高频访问会形成单机瓶颈,导致该节点 CPU 资源被占满。

解决方案

复杂命令替换

将 keys 命令替换为 scan,hgetall 替换为 hscan,将一次性全量遍历拆分为多次小批量操作,减少 CPU 占用。

大 key 拆分

将大哈希拆分为多个小哈希,大字符串分段存储,降低单个 key 的处理开销。

热点 key 流量分摊

通过多级缓存(如本地缓存)、读写分离或增加副本节点分担读操作,分散热点 key 的访问压力。

面试回答总结

工具排查:使用 slow log、hotkeys 命令及监控定位问题;2. 定位原因:判断是复杂命令、大 key 还是热点 key 导致;3. 针对性解决:复杂命令替换为增量遍历,大 key 拆分存储,热点 key 通过多级缓存或副本分摊流量。

架构图解

Redis CPU飙升
排查步骤
慢查询分析
大Key排查
连接数检查
SLOWLOG命令
复杂度高的命令
MEMORY USAGE
拆分大Key
CLIENT LIST
空闲连接释放
优化方案
避免KEYS命令
使用Pipeline
合理设置过期

第80题:G1 垃圾收集器大对象判断机制

G1 不再将堆内存划分为连续的年轻代和老年代,而是将其划分成 2048 个大小相等的 region。

region 数量与大小计算

在堆内存为 8GB 时,每个 region 的大小可通过计算得出:8GB(8192MB)除以 2048 个 region,结果为 4MB。

region 大小调整限制

region 大小可以调整,但需满足两个条件:必须是 2 的幂次方,且范围在 1M 到 32M 之间。

G1 大对象判断标准

在 G1 中,任何大小超过单个 region 容量 50% 的对象会被判定为大对象。基于单个 region 4MB 的大小,50% 即为 2MB,因此超过 2MB 的对象就是大对象。

大对象分配位置

大对象与普通对象不同,会被直接分配在老年代的连续 region 中。例如一个 9MB 的对象,需要 3 个 4MB 的 region(其中两个写满 8MB,一个占用 1MB),剩余 3MB 会成为内存碎片。大对象过多会导致内存碎片增加和频繁 FullGC。

region 大小调整参数

region 的大小可通过参数进行调整,调整区间需在 1-32M 之间。

面试题解答总结

在 8GB 堆内存下,G1 的大对象阈值通常是 2MB。该值并非固定,会随 G1 region 大小动态计算,但判定标准是超过单个 region 大小的 50%。计算过程为:8GB(8192MB)除以 2048 个 region 得到单个 region 4MB,4MB 的 50% 即为 2MB。

架构图解

G1大对象判断
Region大小
对象分配策略
可配置1-32MB
默认2048个Region
对象>Region一半
判定为大对象
直接进Humongous区
大对象影响
复制效率低
内存碎片
优化建议
增大Region大小
减少大对象创建


第81题:高并发接口线程池设计与机器数量估算

线上接口需求:响应时间 500 毫秒,目标 QPS 1 万,需设计线程池参数及估算所需机器数量。面试者常因仅凭经验(如 CPU 核数 ×2、随意设定最大线程数)无法解释原理而失败。

与面试官确认前提条件

需明确业务目标与环境:1 万 QPS 即每秒处理 1 万请求;500 毫秒响应时间需确认任务类型(IO/CPU 密集型),默认分解为混合任务(50 毫秒 CPU 密集型:参数校验、权限校验等;450 毫秒 IO 密集型:数据库查询、远程微服务调用);服务器配置假设为 8 核 16G。

估算系统总并发数

公式:总并发线程数 = QPS× 平均响应时间。代入数据:10000×0.5(秒)=5000 个并发线程,即系统需同时处理 5000 个线程。

单机线程池配置

核心线程数:与 CPU 核心数一致(8 核配置为 8),确保低负载时 CPU 跑满,减少上下文切换。最大线程数:原则是通过更多线程填补 IO 等待时间,使 CPU 不空闲。计算方式:CPU 核心数 ×(总响应时间 / CPU 密集型时间)=8×(500/50)=80。

机器数量计算

单机 QPS 估算:最大线程数 / 平均响应时间 = 80/0.5=160。理论机器数 = 目标 QPS / 单机 QPS=10000/160≈63 台。考虑服务器 70% 负载冗余(避免峰值过载),实际机器数 = 63/0.7≈90 台。

补充策略与优化

队列容量配置 500 以缓冲突发流量;拒绝策略采用自定义实现,返回 429 状态码实现优雅降级;核心业务需对线程池进行监控,并支持动态调整参数(可借助美团开源 dynamic tp 框架)。实际生产中还可通过缓存、MQ、接口响应时间优化(降低 500ms)进一步减少机器数量。

架构图解

线程池设计
核心参数
队列选择
拒绝策略
corePoolSize
maximumPoolSize
keepAliveTime
有界队列
无界队列
同步队列
AbortPolicy
CallerRunsPolicy
机器估算
QPS / 单机处理能力
考虑冗余系数


第82题:RocketMQ 顺序消息吞吐量优化

为保证消息顺序(如订单创建、支付、发货),消息需进入同一队列并由单个消费者处理,导致该消费者负载过高,其他消费者资源浪费,形成顺序与并发的天然矛盾。

吞吐量优化三招

第一招:加队列、巧路由

不为单个队列死磕,为 Topic 增加多个队列,通过统一规则(如订单 ID 哈希)路由消息至不同队列。不同订单自然分流后,可由多个消费者并行处理,提升吞吐量。

第二招:异步化,释放主线程

消费逻辑中若存在耗时操作(如外部 API 调用),避免消费线程阻塞等待,将其异步化并提交至线程池处理,主线程可立即返回处理下一条消息,实现核心业务不阻塞、耗时任务旁处理。

第三招:压测调优,找到黄金拐点

消费线程数量并非越多越好,过多会导致 CPU 频繁切换,反而降低性能。需通过压力测试确定性能最优的线程数(黄金拐点),使系统效率最大化。

高级优化玩法

批量发送:将多个小消息打包成一个大消息发送,减少网络传输开销。

服务端消息过滤:利用 RocketMQ 特性在服务端过滤消息,使消费者仅接收关心的内容(如仅处理 A 类订单,过滤 B、C 类),降低网络压力和计算资源消耗。

消费组隔离:不同业务组(如订单处理组、数据分析组)可独立消费同一订单数据,互不干扰。

实战注意事项

需关注全局唯一 ID、跨分片查询、分布式事务等问题,对应解决方案包括:使用雪花算法生成全局唯一 ID、通过全局表解决跨分片查询、利用 RocketMQ 自带的事务消息保证分布式事务一致性。

架构图解

顺序消息优化
问题分析
单Queue限制并发
吞吐量瓶颈
优化方案
分区顺序
并行处理
按业务Key分Queue
同Key保证顺序
多消费者实例
Queue数量匹配
架构设计
订单ID为Sharding Key
同一订单进同一Queue


第83题:消息队列消息不丢失保障体系

消息的生命周期包括生产、存储、消费三个阶段,需保证全链路不丢失消息。

生产端保障措施

ACK 确认机制与重试

生产端需开启 ACK 确认机制,失败时进行重试。通常将 ACK 参数配置为 all,确保所有副本都收到消息才算发送成功。

本地消息表

将发消息动作与业务操作放在同一本地事务中:先写业务表,再写本地消息表记录待发送状态。事务成功后,通过定时任务将消息推送至 MQ,避免数据库操作成功但 MQ 发送失败导致的消息丢失。

存储端保障措施

副本机制

开启副本机制,副本数量至少设置为 3(一主两从),并跨机架部署实现物理高可用。需确保最少同步副本数大于 1,保证至少一个副本写入成功。

禁止不干净选举

禁止让数据落后的副本成为新主,这是保证不丢数据的最后防线。

消费端保障措施

关闭自动提交位移,采用手动 ACK。在完整处理完业务后,手动通知 broker 消息处理完成。即使中途宕机,重启后仍能从上次位置继续消费。

额外关键点:幂等性处理

手动 ACK 会带来 at least once(至少一次)语义,因此消费端业务逻辑必须考虑幂等性,避免重复处理消息导致的问题。

架构图解

消费者端
拉取消息
业务处理
发送ACK
删除消息
Broker端
接收消息
写入磁盘
主从同步
返回确认
生产者端
发送消息
事务消息/确认机制
消息持久化
消息丢失场景
生产者发送失败
Broker宕机
消费者处理失败
解决方案
事务消息
同步刷盘
手动ACK


第84题:秒杀库存超卖解决方案

给数据库库存字段设为无符号类型(如 unsigned int)。当库存为 0 时,若尝试扣减为负数,数据库会直接报错,阻止 SQL 执行。此方法能防止最终库存变负,属于事后补救措施,但高并发场景下,多个请求同时查询到库存为 1 并通过检查,最终扣减时会有大量失败,影响用户体验。

第二层:悲观锁控制并发

核心是先锁定数据行再进行操作,例如执行 SQL 时添加 for update。其他请求需排队等待锁释放,优点是实现简单,适合入门者;缺点是并发能力差,秒杀时大量请求会堵塞、超时或事务堆积,仅适用于中小厂低业务量场景,大厂高并发下无法承受。

第三层:乐观锁

高并发常用方案,思路是先不锁定数据,修改时再检查。给库存表添加 version 字段,查询时获取版本号,更新时判断版本是否匹配。需注意高并发下重试次数问题,应设置重试上限(如最多 3 次),失败时提示用户 "当前人多,稍等再试",避免死循环。

第四层:Redis 队列削峰填谷

将库存数量(如 1000 件)作为 1000 个 1 存入 Redis 的 list 中。用户下单时先从队列取 1,取到则继续下单流程,未取到则提示无库存。该方案曾扛住 5 万并发,但仅支持单个商品购买 1 件,且需同步 Redis 与 MySQL 库存,否则可能出现数据不一致。

第五层:Lua 脚本解决 Redis 原子性

单独使用 Redis 扣库存时,查库存和扣库存是两步操作,高并发下存在空档。通过 Lua 脚本将两步写成原子操作,Redis 执行时不会被其他命令打断(因 Redis 单线程执行),保证原子性。需注意 Redis 持久化,防止宕机导致数据丢失。

第六层:一锁二判三更新

大厂常考方案,流程固定:第一步加分布式锁(如 Redisson),第二步查询并判断库存是否充足,第三步扣减库存后释放锁。能完全防止超卖,但为串行处理,并发高时速度慢,适合对一致性要求极高的场景(如奢侈品秒杀),宁愿慢也不允许超卖。

第七层:分布式锁加分段缓存

高阶方案,将库存分片存储(如 1 万件分 10 片,每片 1000 件存入 Redis 的 10 个 key)。用户下单时,用 userid 取模定位分片,通过 Lua 脚本扣分片库存,一片不足则找下一片。优点是并发能力提升(如翻 10 倍)、分散热点;难点在于跨分片扣减时需准确统计总库存,避免用户因分片无货误以为全平台售罄。

方案选择总结

中小厂业务量小,可使用乐观锁 + Redis 队列;大厂高并发场景适合 Lua 脚本 + 分段缓存;对一致性要求极高的场景(如奢侈品秒杀)选用一锁二判三更新。关键是根据实际场景选择合适方案,而非死记硬背。

架构图解

秒杀库存防超卖
第一层: 数据库乐观锁
第二层: Redis预扣库存
第三层: Lua脚本原子操作
第四层: 分布式锁
version字段
CAS更新
库存预热到Redis
Redis原子扣减
查库存+扣库存原子化
避免并发竞争


第85题:Redis 查找特定前缀 key 的方法

不能使用 keys 命令。因为在 1 亿 key 的 Redis 实例上执行 keys 命令会遍历整个数据库,可能导致数十秒甚至数分钟的卡顿,使依赖 Redis 的服务超时,甚至引发服务雪崩和生产事故。keys 命令仅适用于调试或 key 总量很少的场景。

标准答案

应使用 scan 命令。scan 是非阻塞、渐进式的安全可靠方案,不会一次性返回所有结果,而是通过游标分页获取数据(如每次取 100 条),对业务影响可忽略不计,每次 scan 操作相当于一次 IO,不会长时间阻塞 Redis。

加分答案

可使用集合 set 或哈希构建索引。写入数据时,除存储原始 key-value 外,额外维护一个索引:将前缀作为 key,通过 sadd 命令把相关 key 写入 set 集合。查询时使用 smembers 直接获取该前缀对应的所有 key,无需扫描全库,效率更高。删除时只需删除对应前缀的索引即可。但该方案会增加写入和删除的复杂度,并需要额外内存存储索引,建议在从节点操作以避免影响主节点。

总结

面试中不应回答使用 keys 命令(会锁死 Redis),标准答案是使用 scan 命令进行安全迭代,加分方案是建立前缀索引(如用 set 存储索引)以提高查询效率。


第86题:亿级用户实时排行榜架构设计

排行榜最大的挑战是写入量巨大,早晚高峰期上亿用户同时上传步数,并发量如数据海啸。核心思想是异步消峰,使用消息队列(如 Kafka、RocketMQ)驯服写入海啸。流程为:用户 APP 上报步数(含用户 ID、步数、时间戳)到业务服务器,业务服务器仅做转发,将请求丢给 MQ,立即响应 APP(用户无感知延迟),由独立消费服务从 MQ 消费数据进行后续处理。此方式可将瞬时洪峰转为平稳细流,确保后端优雅处理,避免服务器被冲垮。

第二板斧:Redis 分片存储

数据存储需高写入性能和排序能力,选择 Redis ZSet(有序集合),用户 ID 为 member,步数为分数实现实时排名。因单 Redis 实例无法存储亿级数据,采用哈希取模分片:消费服务获取用户数据后,对用户 ID 哈希取模(如分成 1024 个分片),将数据分散到不同 Redis 分片实例(key 格式如 "rank:20250917: 分片 ID")。每个分片存储部分用户数据,降低单实例压力,支持系统水平扩展。

第三板斧:动静分离实现秒级查询

用户查询好友排行榜时,应用服务器执行以下步骤:1. 获取好友列表(静数据):优先从 Redis 缓存查询,缓存未命中则查 MySQL 并回写缓存(用户关系数据不常变,适合缓存);2. 批量查分数(动数据):将好友 ID 按分片分组,并发向各分片批量查询(借助 pipeline 将同一分片多次查询打包为一个网络请求提升效率);3. 服务端排序取 TopN:对获取的好友步数数据排序,取出 TopN(如 20 条)返回 APP,APP 组装头像、昵称等信息展示。通过静态关系缓存、动态分数聚合、内存计算组合,实现秒级响应。

深度追问解答

大 V 用户排行榜处理

百万好友大 V 查询时,实时计算压力大。解决方案:启动定时任务(如每 5 分钟)为大 V 计算完整排行榜并缓存,查询时直接返回缓存结果,无需实时计算。

Redis 数据安全保障

为防止 Redis 数据丢失,有三重保险:1. MQ 数据保留一段时间(如 24 小时),Redis 数据丢失可重新消费恢复;2. Redis 主从集群:主节点负责写,从节点负责读和备份,主节点宕机从节点立即顶上,服务不中断;3. 离线数据仓库:数据除写入 Redis 外,同步写入离线数据仓库(如 ClickHouse),用于永久备份和离线分析。

为何不在客户端排序

客户端排序存在三方面问题:1. 安全与公平:用户可破解伪造排名,结果不可靠;2. 流量成本:好友数量多(如 500+)时,返回全量数据流量大,服务端排序后仅返回 TopN(如 20 条),流量消耗小;3. 功能限制:全局排名(如 "超过全国 95% 用户")需全量数据,客户端无法实现,需服务端处理。

架构图解

亿级用户排行榜
MQ异步消峰
Kafka/RocketMQ
消费服务
Redis ZSet存储
分片存储
rank:20240101:分片1
rank:20240101:分片2
rank:20240101:分片N
查询流程
获取好友列表
批量查各分片分数
服务端排序TopN
返回结果
大V优化
定时预计算
缓存完整排行榜


第87题:第三方服务高可用防护层设计

传统系统接入第三方服务(如阿里云短信、微信登录、支付宝支付等)时,各模块与业务高度耦合,存在高耦合、难治理、监控黑盒等问题,任一服务故障可能导致核心业务雪崩。

解决方案:引入统一防卫层

通过统一防卫层实现第三方服务隔离,作为所有第三方服务调用的唯一出口,解耦核心业务与第三方服务,便于治理和扩展。

统一防卫层的四大核心职责

统一接口抽象层

统一第三方服务调用入口,如短信服务统一为 SMS.send,屏蔽不同供应商接口差异,无论接入阿里云或腾讯云短信服务,均通过该入口发起调用。

客户端治理

包括限流器(保护第三方服务不被过载)、熔断器(第三方服务故障时快速熔断,避免业务拖垮)、重试机制(应对网络抖动自动重试)、适配器(统一调用适配)。

监控与 Mock 服务

监控需采集调用指标(请求量、成功率、延迟)和日志,异常时告警;Mock 服务通过开关切换真实调用与开发测试,提升研发效率。

容错方案

异步降级(非核心业务调用失败时,不阻塞返回,转为 MQ 异步重试,如优惠券发放);供应商故障自动切换(如阿里云短信故障时,自动熔断并切换至腾讯云,用户无感知)。

总结

设计核心思想是告别混乱拥抱隔离(降低系统复杂度,隔离核心业务与第三方风险)、夯实基础履行四大职责(统一接口、客户端治理、监控 Mock、容错)、迈向高可用(通过异步降级和自动切换提升系统弹性)。系统稳定性不应依赖第三方,需通过架构设计掌控不确定性。

架构图解

防卫层职责
业务服务
统一防卫层
短信服务
支付服务
登录服务
统一接口抽象
客户端治理
监控与Mock
容错方案
限流器
熔断器
重试机制
异步降级
供应商切换
第三方服务故障
熔断保护
降级处理
异步重试


第88题:50 亿级订单表毫秒级查询优化方案

针对用户 ID、订单状态、时间字段建立联合索引,避免单一字段索引导致的大量数据排序和 filesort 磁盘排序问题。同时优化 SELECT * 为只查询必要字段,减少回表操作,提升查询性能。

冷热分离策略

将已完成、已取消的订单作为冷数据存入冷库,待付款、待收货等高频访问的热数据保留在热表中。通过分离可将 50 亿级数据量压缩至 1 亿级热数据,大幅降低目标数据集规模。

分库分表架构

对热表按用户 ID 进行水平拆分,例如分散到 1024 个库中。通过用户 ID 路由定位数据所在分库,将单库数据量降至万级,实现海量查询的毫秒级响应。

优化思路总结

从索引层(联合索引 + 减少回表)、数据层(冷热分离削减数据集)、架构层(水平分库分表降低单库压力)三个维度协同优化,解决 50 亿级订单表查询性能问题。

架构图解

50亿订单表
三层优化策略
第一层: 索引优化
联合索引
覆盖索引
避免SELECT *
第二层: 冷热分离
热数据: 近3个月
冷数据: 历史归档
热表控制在1亿内
第三层: 分库分表
按用户ID分片
1024个分片
单库万级数据
查询优化
精准索引定位
冷热数据路由
分片键路由


第89题:Redis Cluster 分片与高可用机制

Redis Cluster 内置 16384 个哈希槽,通过 CRC16 算法对 key 进行哈希计算后,对 16384 取模得到槽位编号,该槽位由对应的主节点负责,实现数据在不同节点的分片存储,解决海量数据存放问题。

高可用机制

主从备份

每个主节点配置从节点,从节点实时复制主节点数据,确保数据有备份。

故障检测

节点间通过心跳机制通信,若某节点无法联系其他节点,通过 gossip 协议询问集群中其他主节点,当超过半数主节点认为该节点宕机时,标记其为宕机状态。

自动故障转移

主节点确认宕机后,其从节点发起选举,向所有主节点请求投票,获得超过半数选票后赢得选举,晋升为新主节点,接管原主节点的数据和服务,实现服务自动恢复。

总结

Redis Cluster 通过哈希槽分片解决数据存储问题,通过主从备份、故障检测(gossip 协议)和自动故障转移(选举机制)实现高可用,是去中心化的分布式解决方案。

架构图解

Redis Cluster
16384个哈希槽
CRC16计算槽位
数据分片存储
高可用机制
主从复制
故障检测
自动故障转移
Gossip协议
半数以上节点确认
从节点选举
获得多数票后晋升主节点

相关推荐
indexsunny17 小时前
互联网大厂Java求职面试实战:微服务与Spring生态全攻略
java·数据库·spring boot·安全·微服务·面试·消息队列
zb2006412018 小时前
CVE-2024-38819:Spring 框架路径遍历 PoC 漏洞复现
java·后端·spring
uzong18 小时前
AI Agent 是什么,如何理解它,未来挑战和思考
人工智能·后端·架构
追逐时光者18 小时前
DotNetGuide突破了10K + Star,一份全面且免费的C#/.NET/.NET Core学习、工作、面试指南知识库!
后端·.net
yuweiade19 小时前
springboot和springframework版本依赖关系
java·spring boot·后端
ywf121519 小时前
springboot设置多环境配置文件
java·spring boot·后端
小马爱打代码19 小时前
SpringBoot + 消息生产链路追踪 + 耗时分析:从创建到发送,全链路性能可视化
java·spring boot·后端
心软小念19 小时前
金三银四,全网最详细的软件测试面试题总结
软件测试·面试·职场和发展
小码哥_常19 小时前
MyBatis批量插入:从5分钟到3秒的逆袭之路
后端
Wilber的技术分享20 小时前
【LeetCode高频手撕题 2】面试中常见的手撕算法题(小红书)
笔记·算法·leetcode·面试