第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),通过任务调度确保消息可靠发送。
总结:征服面试官的闭环思维
回答此类问题需展示闭环思维:
- 明确高并发下追求最终一致性而非强一致性;
- 引入事务日志表及状态机解决回查误判;
- 消费端实现幂等性避免重复处理;
- 考虑兜底策略(死信告警、批量重投工具)。
清晰阐述这四点可体现生产环境架构思维。
架构图解
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 库时,步骤为:
- 数据迁移(仅迁移 50% 数据,用 DataX/Canal 工具 3-5 天完成);
- 双写 7 天(新订单同时写入老库和新库,MQ 保证不丢数据);
- 灰度切流(5%→30%→100%);
- 对账补偿。
缓存一致性策略
采用延迟双删策略: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协议
半数以上节点确认
从节点选举
获得多数票后晋升主节点