高并发面试4

第90题:Redis 过期键删除机制

惰性删除指只有当读取某个 key 时才检查其是否过期,过期则删除并返回 null,未过期则返回真实值。优点是节省 CPU 资源,缺点是若 key 永远不被访问,会导致内存泄露,需配合其他机制防止泄漏。

定期删除

定期删除通过定时任务每隔一段时间抽查过期 key 并删除。优点是能清理大部分长期未访问的过期 key,缺点是抽查具有随机性,可能存在漏网之鱼,且过于频繁会消耗过多 CPU 资源。

内存淘汰机制

当内存使用达到 maxmemory 阈值时,Redis 触发内存淘汰机制(自我保护机制),牺牲部分 key 为新数据腾空间,而非直接 OOM。默认策略是不淘汰新写入操作并返回错误;常用策略包括 LRU(最近最少使用)、针对所有 key 的 LRU、随机淘汰等。

总结:过期键处理的完整机制

Redis 通过惰性删除与定期删除结合处理过期键,二者协同解决大部分过期键问题。当内存压力达到极限时,触发内存淘汰策略(如 LRU 等),牺牲部分数据以保证服务整体可用性。

架构图解

Redis过期键
三种删除策略
惰性删除
访问时检查
CPU友好
可能内存泄漏
定期删除
定时抽查删除
平衡CPU和内存
可能遗漏
内存淘汰
达到maxmemory
LRU/LFU策略
随机淘汰
最佳实践
惰性+定期组合
合理设置过期时间
配置淘汰策略


第91题:每秒 10 万并发写的线程安全保证方案

典型的写多读少场景,如滴滴打车中司机上报位置。假设有 100 万司机,每 5 秒上报一次位置,每秒约 20 万写入请求。需避免出现经纬度被不同线程覆盖导致的数据不一致问题。

全局锁方案

思路:使用全局读写锁保护一个存储司机信息的大 Map(Key 为司机 ID,Value 为位置信息)。问题:所有线程竞争同一把锁,性能极差,吞吐量无法满足每秒 10 万并发写需求,会导致系统瘫痪。

分段锁优化

思路:降低锁粒度,将大 Map 拆分为多个小 Map(如 16 个、32 个),通过司机 ID 哈希路由到不同小 Map,每个小 Map 独立加锁(分段锁思想,类似 Java 的 ConcurrentHashMap)。优化效果:减少锁竞争,提升性能,但仍存在锁竞争,对高并发写仍有性能影响。

无锁方案(CAS + 数组)

思路:借助无锁的 CAS(Compare-And-Swap)机制和数组实现。具体实现:定义 AtomicReferenceArray 存储司机位置信息,数组下标通过司机 ID 对数组长度取模确定。更新位置时,创建新的位置对象,通过 CAS 操作原子性替换数组中对应下标的元素,避免经纬度被部分覆盖的不一致问题,实现无锁并发安全。

架构图解

10万并发写
线程安全方案
全局锁
性能极差
不推荐
分段锁
ConcurrentHashMap思想
减少锁竞争
仍有性能瓶颈
CAS无锁
AtomicReferenceArray
原子性替换
极致性能
实现方案
数组存储位置
用户ID取模定位
CAS原子更新


第92题:美团一面:系统 QPS 评估与部署方案

面试官询问项目核心数据时,模糊回答(如 "大概十几万用户""没统计过日活""拍脑袋说 QPS 500")会暴露对业务不熟悉、缺乏实战经验。日活是评估流量的基础,QPS 需基于计算而非猜测,否则会让面试官认为没有转化业务流量为技术方案的能力。

QPS 计算

核心是将业务流量转化为技术指标,公式:QPS = 总请求数 / 有效时间 × 高峰系数 × 安全系数。总请求数:题目中为每天 5000 万;有效时间:排除低流量时段(如凌晨),通常按 8 小时计算;高峰系数:高峰流量是平均流量的倍数(业务不同取 2-3 倍,外卖等高峰业务取 3);安全系数:应对突发流量(如促销),取 1.2-1.5 倍。计算示例:日均有效时间 QPS = 5000 万 / (8×3600 秒) ≈ 1736,高峰 QPS = 1736×3(高峰系数)≈ 5208,最终 QPS = 5208×1.5(安全系数)≈ 7812。

分层部署

接入层

作用:扛入口流量、分发流量、防攻击、缓存静态资源。组件:LVS(扛四层 TCP,10 万 QPS 级)+ Nginx(扛七层 HTTP,1-2 万 QPS / 台)。部署:LVS 2 台(一主一从,Keeplive 高可用),Nginx 3 台(每台 2-3 万 QPS,3 台可扛 6-9 万 QPS,覆盖 7800 高峰 QPS)。

应用层

作用:处理业务逻辑(如下单、查询)。性能:4G/8G 机器优化后单台可扛 1500-2000 QPS。部署:4 台(无状态设计,不存本地缓存,Docker+K8s 管理,促销时可扩容至 6 台),异步处理非核心逻辑(如短信,借助 MQ)。

缓存层

作用:扛 80% 读请求,减轻数据库压力(数据库单台仅 1000-2000 QPS,Redis 可扛 10 万 QPS)。组件:Redis 集群(避免单机故障)。部署:三主三从(主写从读,按用户 ID 哈希分片至 3 主节点),缓存热点数据(如 TOP1000 商品,30 分钟过期),防缓存穿透。

数据层

作用:存储核心数据(订单、用户),处理写请求。组件:MySQL 集群 + 分库分表。部署:一主两从(组复制 / InnoDB Cluster,故障自动转移),分库分表(2 库 4 表共 8 张,订单 ID 哈希分库(对 2 取模)、分表(对 4 取模),中间件用 Sharding-JDBC)。

消息队列

作用:削峰填谷(如秒杀请求先入队列,应用层慢消费)、解耦(如短信 / 日志服务)。组件:Kafka。部署:3 节点集群(高可用),按业务拆分主题(短信、日志、秒杀),每个主题多消费组。

架构验证

通过 JMeter 压测模拟 7800 QPS,层层验证各层性能:先压 Nginx 验证接入层,再压应用层验证服务处理能力,最后全链路压测。若响应时间过长则扩容(如应用层从 4 台增至 6 台)。

总结

回答思路:先计算 QPS(5000 万日请求→8 小时有效时间→1736 平均 QPS→3 倍高峰→5208→1.5 安全系数→7800),再分层部署(接入层 LVS+3 台 Nginx,应用层 4 台无状态服务,缓存层 Redis 三主三从,数据层 MySQL 主从 + 分库分表,Kafka 解耦削峰),最后 JMeter 压测验证,不满足则扩容(如应用层加 2 台)。<|FCResponseEnd|>

架构图解

QPS计算
总请求数: 5000万/天
有效时间: 8小时
平均QPS: 1736
高峰系数: 3倍
高峰QPS: 5208
安全系数: 1.5倍
最终QPS: 7812
分层部署
接入层: LVS+Nginx
应用层: 4台服务
缓存层: Redis三主三从
数据层: MySQL主从+分库分表
消息队列: Kafka集群
扛6-9万QPS
扛6000-8000 QPS


第93题:数据库千万级数据分页查询优化

假设订单表有 1000 万数据,直接使用分页(如查询 100 万条以后的 10 条数据),会导致全表扫描,需要取出大量数据。排序时内存不足,会使用磁盘文件排序(user fastort),导致查询速度极慢。

子查询 + 覆盖索引优化

通过子查询定位起始记录的 id,结合覆盖索引优化。例如查询 100 万零 10 条数据时,先找到第 100 万零 1 条记录的 id,再通过查询。需建立 id 和 create time 的二级索引,适用于 100 万到 5000 万级数据量。

游标分页方案

阿里采用的方案,基于有序字段(如 id 或时间)进行分页。例如按时间降序查询时,第一页查询条件为,记录最后一条记录的 create_time;第二页查询条件为,再取 10 条。需前端传递上一页最后记录的时间或 id,适合无法跳跃分页的场景(如 APP)。

大数据量下的分库分表方案

当数据量达到 "一级"(超过 5000 万)时,采用水平分片 + 全局索引表。单独维护一张存储用户 id 和创建时间关系的全局索引表,查询时先从全局索引表获取目标数据的所有 id,再根据 id 到各个分片查询,支持分批次查询。

总结

百万级以下数据:可直接使用。

百万到 5000 万级数据:推荐子查询 + 覆盖索引优化或游标分页。

超过 5000 万级数据:采用分库分表方案,结合全局索引表查询。

架构图解

<100万
100万-5000万
>5000万
千万级分页查询
数据量
直接LIMIT OFFSET
子查询+覆盖索引
定位起始ID
索引覆盖查询
游标分页/分库分表
基于有序字段
记录上一页最后值
优化对比
原方案: 全表扫描
优化后: 索引定位
性能提升
从秒级到毫秒级


第94题:千万级别大表快速删除大量数据的六种方案

传统 DELETE 删除大量数据会导致锁表,影响其他查询操作;产生大量事务日志占用存储空间;消耗大量 CPU 资源,因此一般不直接使用 DELETE。

分批次删除方案

分批次删除通过降低操作力度,将删除分成多个批次,减少锁表时间,避免长时间阻塞,控制事务日志大小,降低单次删除对资源的消耗。

TRUNCATE TABLE 方案

若需删除整张表,TRUNCATE TABLE 比 DELETE 更高效,直接删除表数据且不生成事务日志,同时释放表占用空间。前提是删除整张表且不能带条件。

分区表删除方案

如果表已分区,可直接删除对应分区,操作速度快且不影响其他分区,前提是表采用分区表设计。

创建新表并删除旧表方案

创建新表并插入需要保留的数据,删除旧表后将新表重命名为旧表。适合保留少量数据、大量删除的场景,需保证新表与旧表结构一致,并重命名时处理所有约束问题。

EXPORT 导出数据方案

适合需要备份删除数据的场景,通过 EXPORT 语句在删除数据的同时进行备份。

数据库工具辅助删除方案

利用数据库提供的工具进行删除,可分批次删除数据并清除删除后的空间,适合通过脚本实现高效自动化处理。

六种方案的适用场景总结

分批次删除适合保留部分数据的场景;TRUNCATE TABLE 适合删除整张表的场景;分区表删除适合表已分区的场景;创建新表适合保留少量数据的场景;EXPORT 导出数据适合需要备份删除数据的场景;数据库工具适合高效自动化处理场景。


第95题:优化 MySQL 频繁全表扫描的查询性能

全表扫描会导致大量磁盘 IO,占用内存资源,降低系统响应速度,进而影响用户体验。

优化方案

索引层面优化

通过索引优化提升数据查找效率,可考虑使用散列索引、覆盖索引、复合索引。避免单表建立过多单列索引,针对多条件查询可改用复合索引,并注意避免索引失效(如避免在索引字段上使用函数)。

使用分区表

通过分区表减少扫描范围,例如按月度、季度等时间维度分区,或使用哈希分区使数据集中在特定分片,尤其适用于报表查询场景。

减少查询数据范围

通过减少查询字段、使用分页减少传输数据包,同时考虑对查询结果进行缓存。

数据归档与清理

将历史数据归档至历史库,定期清理无用数据,以加速查询性能。

硬件配置优化

通过增加内存提高 buffer pool 命中率,调整数据库缓冲区(如排序缓冲区)大小,使用固态硬盘替代机械硬盘。

架构层面优化

采用读写分离架构,读请求使用缓存;针对海量数据进行分表,将数据分散到多个数据库或表中。

优化实例:电商订单查询

对于积累千万条数据的电商订单表,若需频繁查询近一个月订单,可按月创建分区表,结合覆盖索引(避免回表),并对查询结果进行分页处理。

优化总结

优化需从索引、分区、查询范围、数据管理、硬件配置、架构设计等多维度综合入手,性能优化是逐步积累的过程,需结合慢查询分析持续调整。


第96题:集群节点重启时避免剩余节点过载的解决方案

当集群中部分节点(如 A 节点)因磁盘故障、内存问题等原因异常重启时,客户端会断开连接并尝试重连。此时负载均衡器会将流量倾斜到存活节点(如 B 集群),可能导致 B 集群 CPU 飙升甚至雪崩。而当 A 节点重启完成后,可能因流量未重新分配导致 B 集群过载而 A 节点零连接,形成流量分配不均问题。

解决方案四步法

负载均衡器动态引流

负载均衡器需具备类似智能交通系统的流量精准控制能力。待重启节点需停止接收新连接,处理完现有连接后拒绝新连接,将流量迁移到健康节点;节点重启完成后,需逐渐提升其流量权重。关键技术包括:连接耗尽后拒绝新连接(如 Nginx 企业版的相关属性,阿里云等云厂商服务也支持,开源版本需替代方案);间接式权重迁移(上线节点初始权重较低,逐步增加;存活节点 CPU 超过 80% 时降低权重,低于 30% 时提升权重,防止过载雪崩)。

客户端推理策略

通过随机分散扰动将客户端重连风暴打散,避免十万级客户端同时重连单个节点。采用指数级退避重连策略(如第一次重连间隔 1 倍时间,第二次 2 倍,第三次 4 倍,设置重连时间上限),实现有序排队重连。

服务端优雅处理

发送重启指令时,服务端需主动通知客户端即将重启,引导其迁移至其他节点。具体可通过配置响应头实现,阻断新请求并引导客户端连接其他健康节点。

连接再平衡

通过控制中心监控节点负载,当某节点(如 B 节点)连接量超过 90% 时,将部分流量(如 10%)逐步迁移至闲置节点(如 A 节点),例如每秒迁移 50 个连接。大厂通常通过中心线实现此功能,K8s 也有相关的负载均衡配置。

架构图解

节点重启过载
问题分析
解决方案
流量重新分配
缓存未预热
连接池未建立
优雅启动
预热机制
限流保护
预热策略
提前加载缓存
建立数据库连接
逐步放开流量


第97题:MySQL 千万级订单表新增字段方案

MySQL 8 之后新增字段可使用 Instant 算法,实现亿级数据秒级优化,实测一亿数据仅需 0.1 秒到 0.6 秒。该算法使用需满足以下条件:MySQL 版本需大于等于 8.0.12,新增字段必须位于表所有字段的最后,且表使用非压缩格式。通常新增字段无需插在中间,因此 MySQL 8 环境下可优先使用 Instant 算法,实现无锁表风险的秒级添加。

在线 DDL 工具方案

当不满足 Instant 算法条件时,可借助在线 DDL 工具。其原理是创建影子表,将新字段添加到影子表中,同步数据后清理旧表。该方案无版本限制,支持高频写入表,且无锁表风险,操作时间可达到分钟级。

临时表重建方案

传统方案通过影子表重建,需在业务低峰期创建临时表并新增字段,导数据后切换表。该方案操作时间通常为小时级别,期间可能出现数据问题,一般用于需要修改字段顺序的场景。

其他方案及操作建议

除上述方案外,还有切换瞬间锁表方案(会锁表)和分区表增压操作(适合超十亿级数据,需停机维护,此类数据更建议使用分布式数据库)。实际操作时,建议不在主库直接操作,可先在备用库跑通流程后再应用到主库,且避免在业务高峰期操作,选择凌晨等低峰时段进行。

架构图解

大表新增字段
方案选择
直接ALTER
锁表时间长
影响业务
pt-osc工具
在线变更
创建新表
数据迁移
gh-ost工具
无触发器
更安全
最佳实践
低峰期执行
分批处理


第98题:服务器最大并发 TCP 连接数解析

错误观点:认为单台服务器并发 TCP 连接受端口范围(1024-65535)限制,最多支持约 6 万多连接。实际服务器只需监听一个端口(如 8080)即可处理多个客户端连接,端口并非限制因素。

影响并发连接的关键限制因素

操作系统文件描述符:默认用户级别限制为 1024(千级并发),系统级限制约 3.8 万,需手动修改配置提升。

内存消耗:空闲连接约占用 3.44KB 内存,活跃连接(开启读写缓冲)消耗 4KB-6MB;8GB 内存服务器理论支持约 233 万连接,64GB 约 1600 万,但实际受业务进程内存占用影响,8GB 服务器通常支持 5000-2 万并发,64GB 支持 10 万 - 50 万并发。

其他因素:带宽大小、内核性能等。

优化与扩展方案

内核调优:可提升 30%-50% 性能。

内核旁路技术:支持千万级别并发(需 4GB/128GB 内存配合优化)。

分布式架构:通过多服务器分摊压力,应对海量用户场景。

面试回答要点

服务器实际并发 TCP 连接数取决于文件描述符限制、内存大小、带宽等因素,而非端口数量。普通 8G-32G 内存服务器优化后可支持 5 万 - 30 万并发,更高需求需结合内存升级、内核优化及分布式部署。

架构图解

TCP最大连接数
限制因素
文件描述符限制
端口号限制
内存限制
优化方案
调整ulimit
扩大端口范围
增加内存
软限制/硬限制
net.ipv4.ip_local_port_range
理论值
单机约6万连接
优化后可达百万


第99题:订单服务调用时间飙升问题排查

618 大促期间,网关因请求过多对订单服务触发限流(429 错误),导致用户无法下单,持续 15 分钟。监控显示订单服务接口调用时间从 200ms 飙升至 1.5s,但期间 CPU 和内存使用率均正常(未超过 72%),排除资源瓶颈问题。

排查思路:五步定位法

检查线程状态

通过 ps 命令确定订单服务进程,使用 jstack 查看线程状态,重点关注是否存在 waiting(等待)或 blocked(阻塞)状态的线程。案例:某平台曾因 Redis 线程池资源耗尽,导致 80% 线程处于条件等待状态,可通过此步骤快速定位。

分析服务依赖性能

排查数据库是否存在慢查询、锁等待及锁等待时间;检查依赖服务(如库存服务、支付服务)的响应时间,确认是否存在第三方接口调用超时;分析 Redis 缓存命中率,排查是否出现缓存血崩。

检查 JVM 垃圾回收情况

通过 jstat 命令监控 JVM GC 情况,判断是否存在频繁 Full GC。例如:订单服务若因缓存大对象未清理导致频繁 Full GC,每次 GC 暂停 1.2 秒,会直接导致响应时间过长。

检查网络及连接池情况

诊断数据库连接池是否因配置过小导致大量连接等待,排查网络延迟或连接池资源耗尽问题。

分析超时与重试日志

查看数据库慢查询日志(如执行时间 1200ms 的 SQL)、第三方接口超时日志及重试机制日志,定位具体超时环节。

可能原因及解决方案

数据库锁竞争:优化事务粒度,减少锁持有时间。

下游服务雪崩:添加熔断器(如 Sentinel),防止级联故障。

连接池资源耗尽:扩容连接池,或采用异步调用减少阻塞。

案例:实际问题原因为更新库存时未命中索引,导致 100 + 线程阻塞,优化索引后响应时间恢复至 150ms。

架构图解

调用时间飙升
排查步骤
链路追踪
日志分析
监控指标
SkyWalking/Jaeger
定位慢节点
异常日志
超时日志
CPU使用率
GC频率
常见原因
数据库慢查询
网络延迟
资源竞争


第100题:布隆过滤器与缓存穿透预防

布隆过滤器是一种数据结构,用于查询元素是否存在于集合中,结果有两种情况:元素可能存在,或元素一定不存在。其应用场景包括网页爬虫去重、垃圾邮件检测、大数据重复元素判断及缓存穿透预防等。

缓存穿透定义

缓存穿透指大量请求查询不存在的数据时,因缓存中无该数据,请求绕过缓存直接访问数据库,导致数据库压力过大甚至崩溃。例如电商网站在双十一期间,大量查询不存在商品 ID 的请求导致数据库崩溃。

布隆过滤器预防缓存穿透方案

定义容量足够大的布隆过滤器,将数据库中所有商品 ID 缓存到布隆过滤器中。

用户请求商品信息时,先经布隆过滤器拦截,判断商品 ID 是否存在。

若 ID 不存在,直接拦截返回;若存在,再查询缓存,从而避免缓存穿透。

布隆过滤器工作原理

布隆过滤器核心是 m 位的位数组(初始全为 0),新增元素时通过多个哈希函数对元素哈希并取模,将对应位置设为 1。查询元素时,通过相同哈希函数判断对应位置是否全为 1:若有位置为 0,则元素一定不存在;若全为 1,则元素可能存在(存在误判)。

布隆过滤器误判率及降低方法

布隆过滤器存在误判现象(不存在一定准确,存在可能误判),降低误判率的两种方案:

增加位数组长度:位数组越长,容纳元素越多,误判概率越低。

增加哈希函数个数:哈希函数越多,需同时满足多个位置为 1,误判概率越低。

Java 中布隆过滤器实现

可基于 Spring Boot 实现布隆过滤器,具体代码可自行运行测试,核心是定义位数组和哈希函数,实现元素的添加与判断逻辑。

架构图解

可能存在
一定不存在
命中
未命中
存在
不存在
查询请求
布隆过滤器
查询缓存
直接返回空
返回数据
查询数据库
回写缓存
返回空
布隆过滤器原理
位数组
多个哈希函数
空间效率高
特点
可能有误判
一定不会漏判
不支持删除


第101题:订单超时处理解决方案

以淘宝为例,订单超时场景包括:买家超时未付款(如超过 15 分钟自动取消)、商家超时未发货(如超过一个月自动取消)、买家超时未收货(14 天内未确认则系统默认收货)。这些场景因买卖双方非面对面交易,需通过超时处理自动关闭订单或完成操作。

解决方案概述

订单超时处理有 4 种方案:JDK 自带的延时队列、基于 MQ 的延时消息、Redis 的过期监听、分布式定时任务批处理。其中基于 MQ 的延时消息和分布式定时任务批处理方案使用最多。

JDK 自带的延时队列

利用 JDK 阻塞队列中的延时队列,本质是封装优先级队列对元素排序。用户新增订单时按超时时间排序写入队列,通过线程轮询队列,超时订单出队后更新数据库。为防机器重启数据丢失,重启时需从数据库初始化未结束订单到队列。优点:简单,无需第三方组件,成本低。缺点:内存占用大,不支持分布式处理,不适合订单量大的场景。

基于 MQ 的延时消息

RabbitMQ 方案

RabbitMQ 可对队列和消息设置 TTL(存活时间),结合死信交换机实现延时消息。用户下单后,通过延时交换机将消息写入对应 TTL 的队列,消息超时后进入死信交换机,再转发到业务队列进行处理。优点:支持海量延时消息,支持分布式处理。缺点:不够灵活,只支持固定延迟等级,需配置多个延时队列,使用复杂。

RocketMQ 方案

RocketMQ 支持任意秒级定时消息,基于时间轮算法,用户下单时发送定时消息即可更新订单状态。优点:使用简单,支持分布式,精度高(秒级),支持任意时刻。缺点:定时时长最大 24 小时,无法处理卖家一个月未发货等超 24 小时的业务场景。

Redis 的过期监听

通过给 Redis 的 key 设置过期时间,实现过期监听处理订单超时。但 Redis 采用定期删除(默认 100ms 随机抽取过期 key 检测删除)和惰性删除(调用 key 时才检测删除)策略,导致过期删除不精准,可能延迟通知;且 Redis 重启可能导致过期通知丢失,需额外定时任务查库补偿。此外,订单量大时 Redis 存储占用高,维护成本高,不建议生产使用。

定时任务分布式批处理

阿里使用的方案,利用分布式任务调度框架(如 SchedulerX)定时轮询订单库,捞出超时订单分发给不同机器跑批处理。优势:稳定性强,不用担心事件丢失,任务丢失或重启后可从订单库重新捞取继续执行;效率高,一次跑批可批量更新订单状态,减少数据库 TPS;运维简单,跑批失败后可直接修改订单库状态,无需部署第三方组件。缺点:精度依赖调度周期,周期过小会增加数据库压力。阿里通过抽离超时中心和超时库优化,响应时间可做到 30 秒以内,适用于海量订单处理。

方案总结

JDK 延时队列适用于订单量不大的场景;Redis 过期监听方案不建议生产使用;RocketMQ 定时消息适用于超时精度高(秒级)、超时间在 24 小时以内、无峰值压力的场景;定时任务跑批方案适用于超时间在 24 小时以上、对精度不敏感、有海量订单处理需求的电商业务场景。

架构图解

订单超时处理
方案选择
定时轮询
简单易实现
效率低
数据库压力大
Redis延迟队列
ZSet存储
精准高效
推荐方案
时间轮
纯内存操作
极致性能
适合海量数据
MQ延迟消息
RocketMQ支持
解耦可靠
完整方案
延迟队列+定时补偿


第102题:MySQL 到 ES 的数据一致性方案

本文主要讲解如何保证 MySQL 数据库到 ES(Elasticsearch)的数据一致性,结合业务场景分析业界常用的技术方案及其优缺点。

业务场景

某在线旅游平台在春季促销前推出新功能:用户可通过目的地、酒店名称、房型、价格范围搜索优惠酒店。需将现有酒店数据同步到高效搜索引擎,支持高频搜索需求,保证数据一致性和实时响应。

需求分析

功能性需求:用户需输入目的地、酒店名称、房型、价格等信息快速找到优惠酒店。非功能性需求:高效响应(响应时间控制在 500 毫秒内)、支持高频搜索、保证数据一致性(实时反映最新酒店信息),预计促销期间 QPS 达 1000。

技术方案

假设数据存储在 MySQL 中,需同步到搜索引擎(选型为 ES)。核心问题是如何保证 MySQL 到 ES 的数据一致性,尤其是实时数据变更(如酒店价格更新)。将系统讲解四种方案的优缺点及应用场景,最终选择监听 binlog 方案(大厂常用方案)。

业界常见方案

同步双写

同步双写指在写入 MySQL 的同时直接写入 ES。优点:实现思路简单,若处理好事务可保证强一致性。缺点:存在数据一致性风险(如 MySQL 写入成功但 ES 写入失败)、性能开销大(同步操作导致响应延迟)、代码复杂(需手动实现分布式事务补偿机制)。适用场景:老旧系统、用户量小(如内部后台系统)、对实时性要求高且引入中间件成本高的场景。

MQ 异步双写

写入 MySQL 的同时向 MQ 发送消息,由消费服务(如酒店搜索服务)消费 MQ 消息并写入 ES。需保证 MQ 消息可靠性(写入成功、消费成功、失败重试)和幂等性(避免重复数据)。优点:解耦 MySQL 和 ES 操作,ES 故障不影响 MySQL 写入,性能较好(异步处理)。缺点:存在延迟(尤其消息积压时),引入 MQ 增加系统复杂性(需处理消息丢失、重试等)。适用场景:已有 MQ 环境、用户体量较大、允许秒级延迟、对接口 TPS 有要求的场景(如面向用户的移动 / Web 应用)。

扫表定期同步

在非业务高峰期(如半夜)通过定时任务扫描 MySQL 增量数据(借助触发器或记录增量标识),批量同步到 ES。优点:业务侵入性低(无需修改原有代码)、适合大批量数据迁移、可批量写入 ES 减少负载、对业务影响小(低峰期执行)。缺点:实时性差(延迟取决于同步周期),同步窗口内数据修改可能导致不一致。适用场景:老旧系统(改造成本高)、用户量小、对实时性要求低(如统计报表系统)、需大批量数据迁移的场景。

监听 binlog

通过监听 MySQL 的 binlog 变更(如插入、删除、更新),实时感知数据变化并同步到 ES。可模拟从节点拉取 binlog,通过 Canal 等工具解析 binlog 事件,高频场景下可引入 Kafka 作为缓冲(避免 ES 负载过高)。优点:业务无侵入(无需修改业务代码)、准实时(秒级同步)、数据一致性高。缺点:构建 binlog 监听系统复杂(需配置 MySQL binlog、部署 Canal、Kafka 等中间件),依赖专业运维支持。适用场景:大型系统、高并发场景、对实时性和一致性要求高的场景(如电商、在线旅游平台),是大厂优先选择的方案。

业务场景方案选择

在线旅游平台场景需满足:无需改动现有代码、保证数据一致性和实时性(反映最新酒店信息)。监听 binlog 方案符合需求:业务无侵入、准实时同步、支持高并发,因此是该场景的最优选择。

架构图解

MySQL数据变更
Canal监听Binlog
发送MQ消息
消费者处理
更新ES索引
同步策略
全量同步
增量同步
首次初始化
实时更新
一致性保障
消息重试
幂等处理
定期对账


第103题:MySQL 查询大表是否导致 OOM 问题分析

当执行全表查询(如 select*from 表)时,MySQL 不会一次性将 200G 数据加载到内存再返回给客户端,而是通过 "边读边发" 机制处理。关键涉及两个缓冲区:net buffer 和 socket send buffer。net buffer 默认大小 16K,MySQL 读取数据行填满 net buffer 后,会将数据发送到 socket send buffer(本地网络栈);若 socket send buffer 写满,则需等待数据发送到客户端后才能继续写入。因此,200G 数据会分批次传输,不会一次性占用大量内存。

InnoDB 数据读取与缓存机制

InnoDB 从磁盘读取数据时使用 buffer pool 作为缓存,可能缓存热点数据。全表扫描可能影响 buffer pool 中的热点数据,需通过优化 LRU 算法缓解:将 buffer pool 的 LRU 链表分为 new 区(热点数据)和 old 区(临时数据)。新读取的数据先放入 old 区,若在 old 区的访问时间超过 1 秒才移动到 new 区,否则留在 old 区。由于全表扫描时数据读取间隔通常小于 1 秒,这些数据会留在 old 区,避免挤占 new 区的热点数据。

结论:是否会 OOM

100G 内存下 MySQL 查询 200G 大表不会导致 OOM。因为数据通过 net buffer 和 socket send buffer 分批次传输,且通过优化 buffer pool 的 LRU 算法,可控制全表扫描对缓存的影响,避免大量占用内存。

线上操作建议

全表扫描虽不会导致 OOM,但会消耗大量 IO 资源,线上应尽量避免。若需执行(如数据迁移),需错开业务高峰期(如半夜执行),避免影响主库性能。

架构图解

MySQL大表查询OOM
原因分析
解决方案
结果集过大
内存不足
流式查询
分批获取
游标查询
流式查询
设置fetchSize
逐行处理
预防措施
限制查询条数
使用WHERE条件


第104题:RocketMQ 消息 0 丢失实现方案

消息丢失主要发生在三个阶段:生产者发送消息期间(网络抖动等)、Broker 集群存储消息期间(异步落盘等)、消费者消费消息期间(消费失败)。

生产者阶段保障措施

采用同步发送方案,确保消息发送成功后再继续执行。配置重试机制(如重试 10 次)应对发送失败。使用本地消息表记录消息状态,发送前状态为待发送,成功后更新为已发送,定时任务扫描并重新发送待发送消息,确保 100% 投递。

Broker 阶段保障措施

使用同步刷盘策略,消息立即刷新到磁盘。若有副本则采用同步复制,确保主从节点都存储成功后再响应,避免因刷盘策略不当导致消息丢失。

消费者阶段保障措施

可使用同步消费,消费成功后手动发送 consumer success 确认。基于性能考虑,也可采用异步消费 + 本地消息表,记录消息状态,定时扫描未确认消息并重试。

事务消息补充机制

RocketMQ 的事务消息机制可辅助保证消息可靠性,电商平台在双十一期间常结合同步发送、同步刷盘、同步复制及本地消息表等方案实现消息零丢失。

架构图解

消费者端
拉取消息
业务处理
手动ACK
Broker端
同步刷盘
主从同步
返回确认
生产者端
成功
失败
发送半消息
执行本地事务
事务结果
Commit消息
Rollback消息
0丢失保障
事务消息
同步刷盘
主从同步
手动ACK


第105题:分库分表后查询 id 性能问题解决

分库分表后按订单 ID 查询性能暴跌的原因:订单表按 user_id 分片(如 1024 个分片,2 的 n 次方),可通过 user_id 取模精准定位分片;但按 order_id 查询时,需遍历所有分片(底层用 union all 查询),导致性能问题,即使 order_id 是主键也存在该问题。

解决方案

解决思路是让 order_id 也能定位分片,常见方案有三种:

冗余全表法

分别按 user_id 和 order_id 分片存储两张表,查询时按需选择。问题:存储翻倍,存在数据一致性风险(同时写两份数据可能失败)。

索引表法

对冗余表改进,只存 order_id 和 user_id 的对应关系。查询 order_id 时,先查索引表获取 user_id,再定位分片。优势:存储量下降;缺点:查询延迟翻倍(查两张表),索引表随数据增加需分片,增加二次扩容难度。

基因分片法

在订单 ID 中增加路由信息(分片基因),借助 user_id 和雪花算法实现。原理:订单 ID 由雪花算法生成部分与 user_id 分片基因拼接而成,利用二进制特性(数字对 2 的 n 次方取模结果等于二进制最后 n 位的值),通过 order_id 取模定位分片。实现关键:生成算法需保证基因独立拼接,不破坏雪花算法序列,确保 ID 唯一性。

三种方案对比

冗余全表法:存储开销大,一般不用;索引表法:适合中等数据量,无需大改动;基因分片法:适合海量数据,存储开销低、扩容难度低、查询性能快。注意点:分片数需为 2 的 n 次方;雪花算法改造时保证基因独立拼接,不破坏序列。

避坑场景

热点用户数据倾斜(如大卖家订单集中同一分片)解决方案:设计二级分片(一级按时间分,如年月;二级按基因分表),结合冷热数据分离(历史数据归档到 ClinkHouse)。

适用业务

基因分片法适用于电商订单系统(双维度高频查询)、社交用户帖子、金融账户流水等;物流订单号、弱关联业务推荐用一致性哈希。

架构图解

分库分表查询ID
问题分析
解决方案
无法确定分片
全库扫描
基因法
映射表
全局索引
基因法
ID中嵌入分片信息
提取基因定位分片
映射表
维护ID到分片的映射
先查映射再查分片


第106题:CAP 理论与 BASE 理论

分布式事务的基础理论之一,一个分布式系统在保证分区容错性(P)的前提下,只能同时保证一致性(C)和可用性(A)中的一个,因此通常需要在 CP 和 AP 之间权衡。例如,某些系统是 CP 架构(保证一致性),如持久节点;有些是 AP 架构(保证可用性),如支持临时节点的注册中心。

一致性(Consistency)

指更新操作成功并返回后,所有节点的数据在同一时间完全一致(强一致性)。例如,客户端将值 x=1 写入节点 A,需确保节点 B、C 也同步更新为 x=1 后才返回成功,此时后续所有读取操作都能获取到 x=1。若仅写入部分节点就返回,可能出现部分节点数据不一致(如 x=0 和 x=1 共存),即弱一致性或最终一致性。

可用性(Availability)

指系统任何时候都能正常提供服务,读写操作能成功响应。若因节点故障导致读写失败(如强一致性要求下某节点宕机无法同步数据,导致写操作超时失败),则系统不可用。AP 架构会放弃强一致性以保证可用性,允许数据暂时不一致,但服务始终可用。

分区容错性(Partition Tolerance)

分布式系统中,部分节点故障或网络分区时,剩余节点仍能正常工作。这是分布式系统必须保证的基本特性,因此实际架构中主要关注 CP(保证一致性和分区容错)或 AP(保证可用性和分区容错)的选择。

CAP 理论结论

分布式系统必须保证分区容错性(P),因此只能在一致性(C)和可用性(A)中选择其一:CP 架构追求强一致性,可能牺牲可用性;AP 架构追求高可用性,可能牺牲强一致性(采用最终一致性)。大多数互联网应用为保证服务可用,会选择 AP 架构,通过最终一致性折中。

BASE 理论概述

基于 CAP 理论发展的分布式系统设计思想,核心是放弃强一致性,追求最终一致性,以保证系统可用性和性能。BASE 是 Basic Availability(基本可用)、Soft State(软状态)、Eventually Consistent(最终一致性)的缩写。

BASE 理论核心要素

基本可用:系统在高负载或故障时,允许部分功能降级(如双十一秒杀时提示 "人数过多,请稍后再试"),但核心服务仍可用。软状态:允许数据存在中间状态(如部分节点未同步完成),不影响系统整体可用性。最终一致性:数据在经过一段时间同步后,最终达到所有节点一致,而非实时强一致。例如,MongoDB 写入多数节点成功即返回,通过异步同步保证最终一致性,提升性能。

CAP 与 BASE 的关系

BASE 理论是 CAP 理论的延伸,针对 AP 架构的进一步优化。CAP 指出分布式系统需在 C 和 A 间取舍,BASE 则通过基本可用、软状态和最终一致性,在保证可用性的同时,实现数据的最终一致,适用于多数互联网场景(如日志系统、秒杀活动),平衡性能与数据一致性需求。

架构图解

CAP理论
一致性 C
三者最多同时满足两个
可用性 A
分区容错 P
BASE理论
基本可用 Basically Available
软状态 Soft State
最终一致性 Eventually Consistent
CP系统
强一致性, 可能牺牲可用性
AP系统
高可用, 最终一致性


第107题:接口幂等性保障方法

幂等性是数学概念,在接口中指同一请求多次发起时,需确保只执行一次。例如下单场景中,用户重复点击按钮可能导致多次提交,若不保证幂等性,会产生多订单记录(商家需多次发货)、支付接口重复扣钱(如本应扣 15 元却扣 30 元)等问题,因此接口开发必须考虑幂等性。

接口幂等性问题产生的原因

主要源于重复请求,具体包括:网络波动导致请求未及时响应,用户再次提交使服务端接收多次请求;框架或业务层重试机制,如 Nginx 重试、RPC 重试及业务层重试;页面操作,如重复刷新页面、浏览器跳转后退回重复操作、按钮未做幂等性处理导致多次点击。

前端保障接口幂等性的方法

页面控制按钮:点击按钮后立即置灰,防止用户重复点击。2. PRG 模式(Post Redirect Get):表单提交后重定向到新页面(如添加用户后跳转至用户列表),避免重复提交。3. Token 机制:客户端请求时,服务端生成唯一 Token 返回;客户端后续请求携带 Token,服务端验证 Token 有效则执行操作,之后将 Token 置为无效,后续重复携带该 Token 的请求直接拒绝,实现一次有效。

后端保障接口幂等性的方法

唯一标识符:客户端生成唯一标识符(如 UUID),作为请求参数或放在请求头发送至服务端;服务端检查标识符是否存在(如在 Redis 或内存中),存在则为重复请求拒绝处理,不存在则处理并存储标识符。2. 请求参数校验:通过时间戳作为请求参数,服务端根据时间窗判断请求是否在有效时间范围内(如与系统当前时间差值过大则视为重复请求),忽略超出范围的请求。3. 状态检查:处理请求前检查业务状态是否满足条件,例如消息队列消费时,将消费成功的消息 ID 记录到日志表;再次消费时,若日志表中存在该消息 ID,则判定为重复消费,不再处理。

架构图解

成功
失败
接口请求
生成唯一ID
Redis SETNX
执行业务
返回重复请求
业务处理
返回结果
删除锁
幂等方案
唯一ID
Token机制
数据库唯一索引
状态机
一锁二判三更新
加分布式锁
判断业务状态
执行更新操作


第108题:百万级 Excel 数据秒级导入数据库方案

传统 Excel 导入方式常面临内存溢出(OOM)问题,因将整个文件加载到内存,且逐行读取效率低,百万级数据需数分钟。同时缺乏并发处理,未充分利用多核 CPU 和线程池提升吞吐量。

技术选型

Excel 解析:使用 EasyExcel(阿里开源工具),基于 Java,专为大文件处理设计,内存消耗极低,支持并发读写,官网称其为 "快速简洁解决大文件内存溢出的 Excel 处理工具"。

数据库访问:采用 MyBatis-Plus,支持批量插入操作。

并发处理:使用 Java 线程池,结合计数器(如 CountDownLatch)控制任务完成,确保所有线程处理完毕。

数据库优化:开启批量写功能,减少 IO 次数。

实现方案

多线程并行处理:将 Excel 分为多个工作表(如 20 个工作表,每个 5 万数据),使用 20 个线程并行处理,每个线程对应一个工作表。

事件监听与分批写入:通过 EasyExcel 监听器监听读取事件,每读取 1000 条数据触发批量写入(调用 SalariesService 的 insertBatch 方法),避免内存堆积。

批量插入策略:工作表数据按预设阈值(如 1 万条 / 批)分成多批,通过 MyBatis-Plus 批量插入数据库,提升写入效率。

任务同步:利用 CountDownLatch 确保 20 个工作表的所有数据都处理完成后再结束任务,避免数据丢失。

关键优化点

内存优化:基于 EasyExcel 的事件驱动模型,逐行读取数据,不加载整个文件到内存,解决 OOM 问题。

并发提升:多线程并行处理多个工作表,充分利用 CPU 资源;线程池管理任务,控制并发数。

数据库性能:批量写入减少 SQL 执行次数,降低数据库连接开销;MyBatis-Plus 批量插入接口优化底层 SQL 操作。

效果验证

通过 Apifox 测试接口,100 万条数据导入耗时 14 秒,最终数据库成功写入 1000001 条数据,验证了方案的高效性和准确性。

架构图解

百万Excel数据
文件解析
流式读取
避免OOM
分批处理
每批1000条
批量插入数据库
事务提交
优化策略
异步处理
进度反馈
失败重试
技术选型
EasyExcel流式读取
MyBatis批量插入
线程池并行处理


第109题:快速判断 40 亿无符号整数中是否存在某数的方法

给定 40 亿个不重复的 unsigned int 整数(未排序),内存限制 1GB,需快速判断一个数是否在其中。

两种解决思路

核心思路为位图法和布隆过滤器,空间复杂度均为 O (1)。位图法内存需求约 512MB,无误差;布隆过滤器内存可调,但存在误判风险。

位图法

基于 bit 数组实现,每个 bit 表示一个数是否存在(1 为存在,0 为不存在)。无符号 int 的取值范围是 0~2^32-1,共需 2^32 位空间。计算内存占用:2^32 bit = 2^32 / 8 byte = 536870912 byte ≈ 512MB,符合 1GB 限制。插入时通过计算数对应的 bit 位置并置 1,查询时检查对应 bit 是否为 1 即可准确判断。

布隆过滤器

底层基于位图,通过多个哈希函数将一个数映射到位图的多个 bit 位。插入时将这些 bit 位置 1,查询时需所有映射 bit 位均为 1 才认为 "可能存在"。存在误判(不同数可能映射到相同 bit 位),误判率与哈希函数个数、位图大小相关。内存占用可调整(需平衡空间与误判率),适用于可接受误判的场景。

方法对比与总结

位图法:准确无误差,内存固定 512MB,适用于要求精确判断的场景。布隆过滤器:存在误判,内存可进一步优化,适用于对误判不敏感的场景。两种方法均满足 1GB 内存限制,空间效率高。

架构图解

40亿整数判断存在
方案选择
HashSet
内存消耗大
约16GB内存
位图
512MB内存
适合稠密数据
布隆过滤器
内存更小
可能有误判
最佳方案
布隆过滤器
适合海量数据


第110题:Redis 热点数据存储方案

MySQL 中存在 2000 万数据,Redis 内存有限仅能存储 20 万数据,需确保 Redis 中始终保留热点数据。核心原因包括内存成本较高,且根据二八法则,80% 请求通常集中在 20% 数据上;同时需应对突发热点(如新闻、秒杀商品)的动态变化。

Redis 层优化配置

需配置 Redis 存储容量及淘汰策略。稳定热点场景(如商品详情)推荐使用 LFU(最近最少使用)淘汰策略,其特点是淘汰访问频率最低的数据。为应对热点动态变化,需实现实时热点探测,可借助 Flink 等大数据中间件处理 Top 20 万数据,并上报至配置中心进行统计。

二级缓存治理体系

构建多级缓存架构,请求优先访问本地缓存,命中则直接返回以降低 Redis 压力;未命中再请求 Redis,仍未命中则限流访问数据库。热点探测可通过框架实现:获取 key 后统计访问次数(如超过 1000 次),结合配置中心数据标记为热点,热点数据取消过期时间并上报监控。冷热数据需分开存储,MySQL 表可增加热度值字段并建立索引;Redis 存储采用 hash 结构压缩数据,同时通过 Python 脚本在低峰期智能预热当天 20 万热点数据。

额外优化策略

缓存雪崩预防:设置随机过期时间,避免大量缓存同时失效。热点 key(如秒杀商品)处理:采用分 key 策略(如分成 10 个分片)分散压力。多级缓存降级策略:本地缓存命中返回;未命中则查询 Redis,命中后回填本地缓存;均未命中则限流查询 MySQL。优化后缓存命中率可达 98.5%,显著降低数据库负载。

架构图解

热点Key问题
识别热点
监控QPS
分析访问模式
解决方案
本地缓存
分片拆分
多级缓存
JVM缓存
减少Redis压力
Key加后缀
分散到多节点
L1: 本地缓存
L2: Redis缓存
架构优化
客户端分担压力
服务端分散压力


第111题:大模型无缝切换方案

当 deepseek 调用失败时,可降级调用 qwq32b 实现兜底方案。qwq32b 是阿里通义千问最新发布的推理模型,参数量仅为 deepseek 的 1/20,性能与 deepseek 满血模型相当,但成本显著降低。个人用户可本地运行,企业用户可降低 API 调用成本。

大模型无缝切换的业务需求

很多公司之前基于 deepseek 的项目需要接入 qwq32b,如何实现无缝切换成为业务需求,可借助集成 AI 功能的阿里开源 API 网关 Higress 实现。

Higress 网关实现多模型切换的方案

在客户端和大模型之间接入 Higress AI 网关,可配置模型服务提供者(如 deepseek、阿里云百炼、百度千帆等)。请求通过网关后,网关会识别模型参数并调用对应服务,若调用失败可配置降级策略(如 deepseek 失败时调用 qwq32b)。Higress 控制台提供 AI 流量入口管理,可管理 AI 服务提供者(配置 APIKEY 等),并在 AI 路由中创建路由(如前缀匹配、精确匹配模型名),还可配置降级和网关认证。

测试演示:通过 Higress 网关调用不同模型

测试时调用 deepseek 对话模型,使用 Higress AI 网关地址及认证 token,而非 deepseek 的 URL 和 APIKEY。修改模型为千问 max 后,调用会切换到通义千问模型,验证了通过 Higress 网关可实现多个大模型的无缝切换。

架构图解

大模型无缝切换
方案设计
抽象层
配置中心
灰度发布
统一接口
适配器模式
动态配置
实时切换
流量比例控制
A/B测试
切换流程
新模型部署
小流量验证
逐步扩大
全量切换


第112题:处理过期订单的方案选择

Redis 过期监听

与 Redis 过期删除策略有关。Redis 的 key 过期时不会立即触发删除,存在延迟,采用随机删除部分 key 或查询时才删除的方式,导致不可靠,不符合订单处理场景需求。

RabbitMQ 死信队列

可能存在问题,一般尽量避免使用死信机制,且不能保证投递时间。

时间轮方案

纯内存实现,若进程崩溃会导致所有定时任务丢失,存在风险。通常需结合任务调度框架进行持久化处理。

推荐方案

消息队列方案

常用如 RocketMQ,可靠性高,支持高并发,但需要公司已有 MQ 中间件环境及团队维护支持。

延迟队列方案

包括 JDK 提供的延迟队列,以及 Redission 借助 Redis 的 zset 数据结构实现的延迟队列。使用时需注意进行补偿机制以应对可能出现的问题。

架构图解

过期订单处理
方案对比
定时任务
实现简单
效率低
延迟队列
精准高效
推荐方案
时间轮
极致性能
适合海量
选型建议
小规模: 定时任务
中等规模: 延迟队列
大规模: 时间轮


第113题:统计王者荣耀在线用户的方案

腾讯面试题:如何统计王者荣耀 7 亿用户中的在线人数。王者荣耀日活超亿,在 7 周年庆期间被问及此问题,需要高效处理海量用户在线状态统计。

为什么不使用 MySQL

对于几千几百用户的小规模场景,MySQL 可胜任用户状态统计。但面对 7 亿用户的海量数据,MySQL 不适合,无法高效处理如此大规模的实时状态统计需求。

Redis bitmap 数据结构

Redis 的 bitmap 是一种位数组数据结构,适用于统计只有两种状态(在线 / 不在线)的场景。每一位(bit)映射一个用户,1 表示在线,0 表示不在线,可高效存储和查询用户状态。

bitmap 的内存计算

7 亿用户需要 7 亿个 bit 位,换算成内存:7×108bit=7×1088×1024×1024≈83.3MB7 \times 10^8 \text{bit} = \frac{7 \times 10^8}{8 \times 1024 \times 1024} \approx 83.3 \text{MB}7×108bit=8×1024×10247×108≈83.3MB,不到 100M,内存占用低,完全满足统计需求。

bitmap 的操作命令

setbit:设置用户在线状态,语法为 setbit [key] [用户 ID] [状态值],如 setbit online_users 123456 1(用户 123456 在线),setbit online_users 78 0(用户 78 不在线)。

getbit:获取用户在线状态,语法为 getbit [key] [用户 ID],如 getbit online_users 123456 可查询用户 123456 的在线状态。

bitcount:统计在线总人数,语法为 bitcount [key],直接返回指定 key 中值为 1 的 bit 位数量,即在线用户总数。

bitmap 的适用场景

除用户在线状态统计外,bitmap 适用于所有只有两种状态的场景,如签到记录(签到 / 未签到)、活动参与(参与 / 未参与)等,相比分片并行处理等常规思路,效率更高。

架构图解

在线用户统计
方案选择
Redis Set
存储在线用户ID
SCARD统计数量
Redis Bitmap
位图标记在线状态
BITCOUNT统计
HyperLogLog
估算值
内存极小
心跳机制
定时上报
更新在线状态
过期自动离线


第114题:十亿非法 Key 攻击下的缓存穿透防御策略

用于快速判断 Key 是否存在,拦截大量非法请求。可使用 Redis 布隆过滤器模块避免单点瓶颈,动态调整容量和误判率(如设置 1% 误判率),若有删除需求可结合布谷鸟过滤器。若 Key 不存在,请求直接拦截。

缓存空值

对不存在的 Key 缓存空值,需设置较短过期时间,限制空值缓存最大数量(就近淘汰防止内存耗尽),针对高频 Key 可设置更短过期时间。

请求校验和清洗

在接入层(网关层)过滤非法格式 Key,请求需带签名(无签名则拦截),基于 IP 或用户 ID 限流(如使用令牌桶算法拦截高频请求)。

异步加载与预热

缓存未命中时通过消息队列异步加载数据,避免瞬时数据库压力。热点数据(如秒杀商品数据)可提前加载缓存。

熔断降级

当 QPS 超过阈值时熔断,缓存穿透期间默认返回空值直接跳过数据库(降级策略)。可增加 Guava Cache 等本地缓存拦截非法请求,分布式缓存在 Redis 中缓存空值和热点数据。

数据库层压力分散

采用读写分离(读请求走从库保护主库),通过数据库中间件对 SQL 进行限流。

多层防御策略总结

接入层:签名校验 + 数据参数格式过滤 + IP 限流;缓存层:布隆过滤器 + 空值缓存 + 本地缓存;数据库层:熔断降级 + 异步队列回源 + 业务对接回源 + 分库分表。通过组合策略可有效拦截 99% 以上非法请求,确保高并发攻击下数据库稳定,可根据业务场景调整布隆过滤器容量、误判率及缓存时间等参数。

架构图解

判定不存在
判定可能存在
命中
未命中
不存在
存在
非法Key攻击
布隆过滤器
直接返回空
查询缓存
返回空
查询数据库
返回空
返回数据
防御策略
布隆过滤器前置
缓存空值
限流保护
多层防护
第一层: 布隆过滤器
第二层: 缓存空值
第三层: 数据库查询


第115题:从 100GB 日志中高效提取热门 IP 地址(内存限制 1G)

百度面试题:如何从 100GB 日志中高效提取热门 IP 地址,内存限制为 1G。100GB 无法全部加载到内存,需解决海量数据处理问题。

处理思路:分片与并行

核心思路是分片 + 并行处理。将大文件拆分为小文件(如 1000 个 100M 文件),确保相同 IP 落在同一文件,并行处理每个小文件后合并结果。可利用并行框架(如 Java 的 ForkJoinPool)提高效率。

第一步:分片策略

分片关键是确保相同 IP 落在同一文件,避免统计不准确。通过对 IP 地址进行 hash 运算(如 hash 后分成 1024 个文件),使相同 IP 映射到同一小文件,解决分片问题。

第二步:并行处理

利用 ForkJoinPool 并行处理各小文件,读取每行数据,借助 ConcurrentHashMap 统计每个 IP 的访问数量,提高处理效率。

第三步:合并结果

通过 ForkJoin 处理 1024 个文件后,提交任务并合并各文件的统计结果,最终得到访问次数最多的热门 IP。

方法优缺点

优点:分片后内存不超出 1G 限制,并行处理提升性能,代码逻辑清晰易维护。缺点:多文件场景下,若日志频繁更新,统计过程可能受影响。

面试解题套路总结

遇到类似海量数据处理问题,固定套路为:先分片切分大文件(确保同类数据在同一分片),再并行处理各分片,最后合并结果。

架构图解

100GB日志提取热门IP
内存限制1G
分治策略
第一遍: 哈希分片
IP哈希取模
分散到小文件
第二遍: 统计频率
每个文件单独统计
HashMap计数
第三遍: 归并TopN
各文件TopN
合并得最终结果
时间复杂度
O N 三次遍历


第116题:限流场景 ReentrantLock 公平锁 vs 非公平锁取舍

ReentrantLock 是 Java 中的可重入锁,默认是非公平锁。通过构造参数传 true 可指定为公平锁。

公平锁与非公平锁的核心区别

公平锁:秩序至上,严格按照请求的先后顺序获取锁(先来先服务)。非公平锁:效率优先,允许线程尝试插队获取锁,插队失败后再排队。

性能与饥饿问题对比

公平锁加锁解锁开销较大,性能较低,但无线程饥饿问题;非公平锁加锁速度快,性能较高,但可能存在线程饥饿问题(如排队线程长时间等待,被新线程插队)。

适用场景分析

公平锁适用于公平性优先场景:如支付系统(需保证交易公平)、任务调度系统(需按提交顺序执行)、精细化限流组件(要求按顺序执行)。非公平锁适用于性能优先的高频场景:如秒杀抢购(追求高吞吐量)、高并发缓存系统(注重性能)、核心日志模块(关注日志写入吞吐量而非顺序)。

限流场景的锁选择

限流场景追求性能,应选择非公平锁。

非公平锁避免饥饿的优化策略

主要有以下优化策略:限流削峰,通过令牌桶算法等降低并发请求量,减少锁竞争;分担锁设计,将资源分段(如 1 万库存分为 10 段,每段 1000 库存),降低单一锁的竞争;动态调整锁策略,队列短时用公平锁保证公平性,队列长时切换非公平锁提高吞吐量;(Java 中不推荐)配置线程优先级缓解饥饿。

总结

公平锁强调公平性(先来先服务),适用于支付系统、任务调度、细粒度限流等场景;非公平锁突出性能优先(允许插队抢锁),适用于高并发限流、缓存读写、日志写入等场景。

架构图解

公平锁vs非公平锁
公平锁
非公平锁
FIFO排队
无饥饿
吞吐量低
允许插队
可能饥饿
吞吐量高
限流场景
公平锁: 严格限流
非公平锁: 高吞吐
选择建议
业务公平性要求高: 公平锁
性能优先: 非公平锁


第117题:Kafka 消息积压如何破局

核心思路是分离 Kafka 消息消费与日志处理流程。设计独立的 Kafka 消费线程负责从 Kafka 拉取日志并放入 BlockingQueue 缓冲队列,同时使用专门的日志处理线程池从队列中并发取数据处理。关键配置包括:BlockingQueue 大小需适中(如 1 万条,避免过小易满或过大占内存);线程池大小根据任务类型调整(IO 密集型可设为核心数两倍,CPU 密集型可设为核心数,建议通过压测确定最优值);Kafka 消费速度需与队列容量匹配(如每次拉取 1000 条,避免拉取过快导致队列溢出)。

消息积压与避免消息丢失方案

流控机制:动态调整 Kafka 消费速率,当 BlockingQueue 接近满时暂停拉取(如休眠 100 毫秒),或调整单次拉取数量。

持久化处理:当队列满导致 offer 操作返回 false 时,将消息持久化到磁盘,避免内存中丢失。

死信队列:利用 Kafka 死信队列,将无法写入缓冲队列的消息转发至死信队列暂存,待后续处理。

提升处理能力:增加日志处理线程池规模;优化处理逻辑(如将同步操作改为异步、批量处理);采用多队列分流(如配置多个 BlockingQueue,每个队列独立处理,提升整体缓冲容量)。

架构图解

Kafka消息积压
分析原因
消费速度慢
生产速度过快
消费者故障
解决方案
增加分区数
增加消费者
优化消费逻辑
提高并行度
注意分区分配
批量消费
异步处理
临时扩容
临时消费者
转发到新Topic
多消费者并行


第118题:从 100 亿条搜索日志中找出最热门 100 个搜索词的方法

每天需分析 1T 用户日志(约 100 亿条),找出最热门的 100 个搜索词,内存限制为 4G。此类问题在搜索引擎、社交媒体、电商平台的推荐系统中广泛应用。

解决步骤

数据分片

将海量数据分成小份文件,确保每个分片在处理时不超出内存限制。

词频统计

针对每个分片数据,统计其中搜索词的出现频率(词频)。

合并结果

合并各分片的词频统计结果,找出全局 TOP100 搜索词。关键技术为小顶堆(或优先级队列),可高效实现 TopK 查找,通过维护大小为 100 的小顶堆,快速筛选出频率最高的 100 个词。

核心思想:分治算法

通过数据并行分片处理,先将大数据分解为可内存处理的小数据块,并行统计词频,再利用小顶堆合并结果,最终得到全局 TOP100。这是大数据领域处理海量数据的常规方法,适用于内存受限场景。

架构图解

100亿日志热门词
分治策略
第一遍: 哈希分片
搜索词哈希
分散到小文件
第二遍: 统计频率
HashMap计数
每个文件独立统计
第三遍: 归并Top100
各文件Top100
堆排序合并
优化
使用Trie树
并行处理


第119题:二十亿用户登录状态统计方案设计

面试题:有二十亿用户,如何快速统计当前登录用户数量?传统方案存在性能或内存开销问题,需选择更优数据结构。

传统方案的问题

若使用 MySQL 存储用户登录状态(如 userid 字段 + is_login 状态字段),二十亿海量数据统计时性能极低。若使用 Redis 的 String 类型存储(key 为 userid,value 为 0/1 表示下线 / 登录),在高用户量场景下内存开销过大,不适合大规模应用。

Redis String 类型的内存开销分析

Redis 的 String 类型基于简单动态字符串(SDS)存储,包含:1. buff:存储实际字符串内容;2. length:4 字节,记录已用长度;3. alloc:4 字节,记录分配长度。额外开销共 8 字节。此外,Redis 还需对象结构体(如最后访问时间、引用计数等)管理元素,进一步增加内存占用。对于二十亿用户,String 类型的内存开销不可接受。

Bitmap 方案推荐

登录状态是二值判断(登录 / 未登录),适合使用 Redis 的 Bitmap 数据结构。Bitmap 用每一位(bit)表示一个用户的登录状态:1 表示登录,0 表示未登录。其结构简单,仅需记录位偏移和长度(1 字节),大幅降低内存开销。

Bitmap 内存占用计算

Bitmap 内存占用公式:用户数 / 8(1 字节 = 8 位)。例如,一亿用户仅需约 12MB(1 亿 / 8=12.5MB),二十亿用户约 250MB,内存效率远高于 String 类型。

Bitmap 使用示例

记录登录状态:使用 SETBIT 命令,如(用户 10086 登录);(用户 88 未登录)。2. 统计登录用户数:使用 BITCOUNT 命令,如直接返回登录用户总数。示例中用户 10086、87、89 登录,BITCOUNT 结果为 3,高效便捷。

Bitmap 的扩展应用

除登录状态统计外,Bitmap 还可用于:1. 用户签到次数统计;2. 连续签到判断(如统计七天内连续签到的用户总数);3. 社交 APP 中的用户行为标记等二值状态场景。

架构图解

20亿用户登录统计
方案选择
Redis Bitmap
约2.5GB内存
精确统计
HyperLogLog
固定12KB
估算值
分片Bitmap
分散存储
并行统计
统计维度
日活
周活
月活


第120题:幂等性解决方案:一锁二判三更新

最系统的幂等性方案为 "一锁二判三更新",因近期阿里、网易等面试中高频出现幂等性相关问题(如订单业务幂等设计、前端重复提交处理),而多数回答不全面导致面试失败,故总结此方案。

幂等性的定义

幂等性是指一次操作和多次操作同一个资源所产生的影响均与第一次操作相同,源自数学和计算机概念。接口幂等性可理解为:无论接口调用多少次,只要参数不变,结果就不变。

为什么需要幂等性

若不保证幂等性,会出现重复调用导致的问题,如创建订单时生成多笔订单、扣减库存时多扣、重复转账 / 扣款 / 付款,以及游戏中重复增加金币、积分、优惠券等。

导致幂等性问题的原因

主要有两个原因:一是底层网络阻塞和延迟,导致客户端重试、RPC 框架超时重试、消息重复投递;二是用户层面的重复操作,如按钮未做控制,用户点击下单后未跳转而多次点击。

幂等性的两大场景

可分为两大类场景:第一类是单数据并发操作的幂等性保证方案;第二类是微服务之间调用的幂等性保证,如下单链路中涉及扣款、调库存、调优惠券等多步骤操作。

单数据 CRUD 操作

新增操作天然不具备幂等性,除非设置唯一约束,否则可能插入多条记录;查询操作天然具备幂等性(基于数据库可重复读隔离级别);更新操作中,计算式(如 set number=number-1)不具备幂等性,直接设置值(如 set number=8 where id=1)具备幂等性,带条件的 update 可能不具备;删除操作中,基于主键的 DELETE 具备幂等性(删除一次后,再次删除无影响),逻辑删除(如 update 状态字段)也具备幂等性。需保证幂等性的场景包括计算式 update、带条件查询的 update 以及新增类动作。

多数据并发操作

多应用调用多微服务、异步操作等场景需要考虑幂等性,例如高并发抢红包(防止第一次没抢到,第二次却抢到)、高并发下单(防止因超时生成多个订单)、高并发支付(防止重复扣款)。需保证资源唯一性,防止重复消费。

幂等性的解决方案

常见方案包括:全局唯一 ID(根据业务生成唯一 ID 判断是否存在数据)、数据库唯一索引(防止重复插入)、服务层锁(如分布式锁)、状态机(根据状态判断是否更新)、先查询再插入(存在则更新)、多版本并发控制(通过版本号控制更新)。

综合性方案:一锁二判三更新

这是双十一双十二活动中支付团队摸索出的综合性方案。一锁:先锁单据,高并发场景使用分布式锁,避免使用性能低的锁,可引入锁分担机制(如拆分库存提升性能);二判:判断单据状态,基于状态机、流水表、唯一索引等技术方案判断是否更新过;三更新:若未更新则执行更新完成业务逻辑,若已更新则不操作。通过此方案可避免重复操作,确保幂等性。

架构图解



幂等性保障
一锁
二判
三更新
分布式锁
Redis SETNX
查询业务状态
判断是否已处理
执行业务逻辑
更新状态
释放锁
完整流程
获取锁
查询状态
已处理?
返回结果
执行业务
更新状态
释放锁


第121题:MySQL 数据同步到 ES 的方案分析

同步双写是指在酒店管理服务插入 MySQL 的同时往 ES 插入数据。优点是强一致性保证,数据变更后 ES 能实时查询,代码改造简单(仅需在原有 MySQL 插入逻辑中增加 ES 插入代码),无需额外中间件。但需处理 MySQL 写成功而 ES 写失败的情况,由于 ES 非关系型数据库,无法使用 Seata 的 XA/AT 事务方案,通常需用 TCC 保证全局事务,实现复杂度较高。适合用户量少、偏向后台管理的系统。

MQ 异步双写方案

通过酒店管理服务写入 MySQL 后,同步发送消息到 MQ,由酒店搜索服务从 MQ 拉取消息并写入 ES。优点是实现业务解耦,适合高并发(如移动 APP、互联网应用)、数据相对稳定(新增多修改少)、能接受秒级延迟的场景。缺点是 MQ 可能出现消息积压导致数据更新延迟,无法保证实时性。此方案在互联网公司使用较多。

扫表定时同步方案

通过定时任务定期扫描 MySQL 表(如按 ID 分批拉取数据)并同步到 ES。优点是实现简单,适合批量数据迁移,对业务影响小。缺点是同步期间若 MySQL 数据发生变化会导致数据不一致,实时性差。适合用户体量小、偏向报表统计类业务,且对实时性要求不高的场景,与同步双写方案均适用于老旧系统。

监听 binlog 同步方案

通过监听 MySQL 的 binlog 日志实现数据同步,可借助 canal 等中间件解析 binlog,将变更事件同步到 ES。为应对大量数据,可引入 Kafka 作为缓冲层存储 binlog 事件,避免 canal 宕机导致数据丢失。优点是业务无侵入、低耦合,无需修改原有业务代码。缺点是需确保 MySQL 开启 binlog(主从架构中主节点通常已开启,建议从节点同步以减少主节点压力)。适合流量较大的互联网公司,允许业务有一定延迟。在线旅游平台案例中,该方案被用于春节促销期间的酒店数据同步,结合 ES 索引优化(目的地、酒店名称、房型、价格区间等字段建索引)实现高效搜索。

架构图解

MySQL同步到ES
方案选择
双写
代码侵入
一致性难保证
Canal监听Binlog
解耦
实时同步
定时任务同步
延迟高
适合冷数据
推荐方案
Canal+MQ
高可靠
最终一致性


第122题:防止恶意刷接口的措施

防火墙可过滤和控制网络流量,防止未经授权的网络访问及攻击,如 DDOS 攻击、病毒蠕虫攻击、钓鱼网站欺骗性攻击等,保障网络安全。

验证码

在设计接口时使用,如秒杀接口下单前需输入验证码。早期为图形验证码,现在常用拖动滑块填充图案的验证码,能有效防止用户恶意刷接口,例如防止恶意注册、下单等行为。

鉴权

部分接口需登录后访问,登录会将用户信息存到上下文,接口会校验上下文是否有用户登录信息。可通过注解对接口访问权限进行控制,也可在网关层做鉴权,如商品服务允许放行,order 开头的订单服务需登录鉴权。

IP 黑白名单

将恶意刷接口的 IP 加入黑名单,仅允许白名单中的 IP 访问接口。例如开通会员的接口,可将其 IP 加入白名单,白名单可存在配置中心、数据库或布隆过滤器(少量误判可容忍)。第三方接口平台可借助 IP 白名单保证接口安全,微服内部通过 openFeign 访问且不对外暴露时可不用设置白名单,还可增加 TOKEN 校验实现服务内部接口访问。

数据加密

HTTP 接口是明文传输,易被窃听、伪装、篡改,不安全。HTTPS 协议添加了加密机制,为保证安全性,接口应尽量使用 HTTPS 协议,特别是对外暴露的接口,大厂对外暴露接口通常都使用 HTTPS 协议。

限流

对接口请求进行限制,如短信接口。可设计短信发送表记录相关信息,通过判断查询最近一次发短信记录的时间(如 60 秒内不允许再发)和使用 Redis 保存手机号及发送次数(如一天只允许发 10 条)来实现限流。发送短信时,先判断时间是否超过 60 秒,再从 Redis 查询当天发送次数,若未超过限制则写入短信记录表并更新 Redis 发送次数。

监控

对接口突发流量进行监控,当监控到接口被刷次数异常(如达到 1 万次)时,通过发短信、钉钉、企业微信等方式通知相关人员,以便人工介入处理异常问题。

网关

作为统一流量入口,可在入口处实现过滤、鉴权、限流等功能。用户所有请求(无论通过手机还是电脑)都会经过网关,再由网关分发到对应的不同微服务,能有效应对接口防刷问题。

架构图解

防恶意刷接口
识别手段
防护策略
IP识别
设备指纹
行为分析
限流
验证码
黑名单
多层防护
网关层: IP限流
应用层: Token校验
数据层: 频率控制


第123题:Redis 实现高并发排行榜功能

使用 Zset 数据结构

可利用 Redis 的 Zset 数据结构实现排行榜功能。设置 key 为排行榜名称(如 "邀约有礼的排行榜"),score 存储排序依据(如邀约人数),value 存储用户标识(如手机号)。Zset 天然支持按 score 排序,能直接实现榜单功能。

Zset 排行榜的维护方式

无需从数据库获取所有数据,可通过定时任务(如每 60 秒或半小时执行一次)查询数据库中排序前 200 条的数据同步到 Redis 的 Zset 中(因前 10 名必定在前 200 名内)。当用户数据变化(如邀约新用户)时,通过写入口实时更新 Zset 的 score(加 1),同时更新数据库。

基础方案的缺点

基础方案性能尚可(Redis 写操作约 2 万 / 秒,读操作约 10 万 / 秒),但在超高并发场景下存在问题。Zset 对应一个 key,即使 Redis 集群,该 key 也只会落在单个节点,无法承受百万级 QPS 的热点 key 访问。

高并发场景下的优化方案

将大 key 拆分为多个小 key,存储在不同 Redis 节点,每个节点存储前 100 名的榜单数据。汇总各节点数据取出前 10 名榜单数据,并同步到本地缓存,提升性能以处理百万级热点 key 访问。

架构图解

渲染错误: Mermaid 渲染失败: Parse error on line 18: ...[跳表实现] P --> Q[O(logN)复杂度] ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'


第124题:Redis 集群处理百万级热点 key 方案

Redis 单机可处理约 10 万读 / 2 万写请求,但百万级热点 key 访问会导致单个节点过载。即使使用 Redis 集群,热点 key 仍可能集中在某个主节点,导致该节点被流量打挂。

热点 key 拆分方案

将单个热点 key 拆分为多个小 key,分散到不同 Redis 节点以分摊流量。例如:

三主架构可拆分为 3 个小 key,每个节点承载约 33 万流量

扩展至 10 个主节点时,每个节点仅需处理 10 万流量

CRC16 算法与插槽分配

Redis 集群通过 CRC16 算法对 key 进行哈希计算,结果对 16384(0-16383)取模,对应不同插槽。每个插槽关联特定节点,确保小 key 分布在不同节点需通过算法控制插槽分配。

手动拆分 key 实现

通过手动命名小 key(如 key_0、key_1),结合 CRC16 算法验证插槽归属,确保各小 key 落在不同节点。例如:

设置 key_0 使其插槽归属节点 0

调整 key_1 命名确保其插槽归属节点 1

按集群节点数量拆分对应数量的小 key

方案总结

采用分 key 方案将热点 key 拆分为多个小 key,分散到 Redis 集群不同节点。根据集群节点数量确定拆分数量,通过手动设置 key 结合 CRC16 算法确保分布均匀。适用于抢优惠券、实时榜单等超高并发场景。

架构图解

百万级热点Key
问题分析
单节点压力过大
网卡带宽打满
CPU使用率飙升
解决方案
本地缓存
Key拆分
请求分散
JVM内存缓存
减少Redis访问
Key加后缀0-N
分散到多节点
客户端随机选择
负载均衡
架构优化
多级缓存
L1: 本地缓存
L2: Redis分片


第125题:索引失效场景:破坏前缀匹配原则

破坏前缀匹配原则是导致索引失效的常见场景之一。

前缀匹配的定义

前缀匹配指在查询中匹配字符串的开头部分,能够有效提升查询性能。例如,对 name 字段使用 like 'John%'(百分号在右,左前缀匹配)时,可利用索引快速定位数据。

破坏前缀匹配的情况

当 like 语句的百分号出现在左边(后缀匹配,如 '% John')或中间(如 '% John%')时,会破坏前缀匹配原则,导致索引失效,此时查询可能进行全表扫描。

复合索引的前缀匹配要求

复合索引需满足最左匹配原则,即查询条件中需包含复合索引的第一个字段,否则无法使用索引;即使满足最左匹配,若对字段的查询破坏前缀匹配(如 like '% xxx'),仍会导致索引失效。

前缀匹配的优化建议

使用前缀匹配时,建议限制前缀长度(如匹配前 10 个字符),以提升查询效率。

架构图解

渲染错误: Mermaid 渲染失败: Parse error on line 10: ... C --> C1[WHERE YEAR(create_time)=2024] -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'


第126题:覆盖索引详解

覆盖索引是指查询所需的所有数据都可以直接从索引中获取,无需访问表本身的行。例如,当复合索引包含查询的所有字段时,就不需要访问表数据。

覆盖索引示例

以员工表(含 id、name、age 字段)为例,若创建复合索引(name, age, year),查询 name 和 age 时,由于复合索引已包含这两个字段,且符合最左匹配原则,可直接从索引获取数据,无需通过 id 回表查询。

覆盖索引性能优势

覆盖索引能减少 IO 操作,提高查询性能,同时减少数据访问量。因为无需回表(回表会增加 IO 操作),所以性能较高。

破坏覆盖索引的场景

使用 SELECT *:会查询所有字段,若索引未包含全部字段,需回表获取数据,破坏覆盖索引。

不符合最左匹配原则:例如复合索引为(id_card, user_age, user_name),查询时若 where 条件违背最左匹配原则,可能导致索引失效,无法使用覆盖索引。

Using index 含义

Using index 表示查询使用了覆盖索引,直接通过索引获取所需数据。但部分 where 条件可能需要在索引过滤后对结果集进一步过滤。

架构图解

覆盖索引
定义
查询字段都在索引中
无需回表
优势
减少IO操作
提升查询性能
降低CPU消耗
使用场景
SELECT指定字段
联合索引覆盖
示例
索引: (name, age)
查询: SELECT name, age FROM user WHERE name='xxx'
直接从索引获取
注意事项
避免SELECT *
可能破坏覆盖索引


第127题:MySQL 索引失效场景及解决

最左匹配原则是指复合索引需从最左字段开始,按顺序依次使用,不能跳过中间字段。以 test_user 表的联合索引(id_card, age, user_name)为例,分析索引有效与失效场景:有效场景包括查询 id_card、id_card+age、id_card+age+user_name,这些情况均符合从左到右顺序,explain 结果中 key 字段会显示使用联合索引,type 为 ref(非唯一索引匹配)。失效场景包括跳过第一列(如仅查 age 或 user_name)、中间跳列(如 id_card+user_name,此时仅 id_card 生效,user_name 不使用索引)、范围查询后续字段(如 id_card=1 and age>2 and user_name='a',此时仅 id_card 和 age 生效,user_name 不使用索引)。通过 explain 可观察 key 字段是否有索引,type 为 all 表示全表扫描,即索引失效。

架构图解

索引失效场景
字段类型不匹配
使用函数
计算操作
LIKE左模糊
OR条件
字符串用数字查询
WHERE YEAR date = 2024
WHERE id + 1 = 10
LIKE %abc
索引列OR非索引列
避免方法
保持类型一致
避免函数操作
使用覆盖索引


第129题:轻量级锁与重量级锁的自旋机制

轻量级锁加锁失败不存在自旋,只有重量级锁加锁失败才会自旋。

重量级锁自旋的原因

重量级锁涉及线程阻塞唤醒,需要用户态到内核态切换,开销大。为减少线程挂起次数,重量级锁会采用自旋优化:当线程申请锁失败时,自旋一段时间等待持有锁的线程释放锁,期间若成功获取锁则避免阻塞。

轻量级锁的加锁机制与失败处理

轻量级锁适用于多线程在不同时间段请求同一把锁(无竞争)的场景,通过一次 CAS 尝试加锁,无需创建重量级锁的 monitor 对象。轻量级锁加锁失败会直接膨胀,申请 monitor 对象(转为重量级锁),此过程中不存在自旋。

源码视角下的锁自旋逻辑

轻量级锁加锁时进行 CAS 判断,若成功则将 mark word 写入线程栈帧完成加锁;若失败则进入膨胀逻辑,创建 monitor 对象。重量级锁加锁时,若 CAS 失败会执行自适应自旋,多次尝试获取锁,自旋次数判断后仍失败才会挂起线程。Synchronized 的自旋逻辑是对重量级锁的优化,而非轻量级锁。

架构图解

Synchronized锁升级
无锁
偏向锁
轻量级锁
重量级锁
偏向锁
单线程访问
CAS修改Mark Word
轻量级锁
少量竞争
CAS尝试获取
失败则膨胀
重量级锁
竞争激烈
Monitor对象
线程阻塞
自旋优化
重量级锁阶段
自适应自旋
避免立即阻塞


相关推荐
精神小伙就是猛2 小时前
使用go-zero快速搭建一个微服务(一)
开发语言·后端·微服务·golang
丘比特惩罚陆2 小时前
【无标题】
后端·gitee
乐天_乐聊2 小时前
在 IM 项目里落地 Skill + MCP:我给 V-IM RPO 做了一套可被 AI 直接调用的消息能力
后端
四千岁2 小时前
WSL + OpenCode 最佳实践:环境一致、模型配置、GUI 远程使用
前端·javascript·后端
ssshooter3 小时前
Tauri 2 Linux 上 asset://localhost 访问返回 403 避坑指南
前端·后端·架构
kyriewen3 小时前
for...of 的秘密:迭代器与可迭代对象,你也能创造“可循环”的东西
前端·javascript·面试
book123_0_993 小时前
spring 跨域CORS Filter
java·后端·spring
空空潍3 小时前
Spring AI 实战教程(一)入门示例
java·后端·spring·ai
星辰_mya3 小时前
自定义注解 + AOP:打造企业级通用组件(日志、限流、幂等)
java·开发语言·spring·面试·架构师