第31题:Kafka 高性能原因解析
- 磁盘随机读写慢是因为磁头需要频繁寻址,而 Kafka 采用顺序追加写入方式,数据永远只在文件末尾添加。
- 每个文件提前申请连续磁盘空间,磁头无需来回寻址,效率可与内存随机读写媲美,成为许多中间件提升日志写入效率的参考思想。
页缓存:将写入任务交给操作系统
- Kafka 不直接等待数据写入磁盘,而是将消息先写入操作系统的页缓存(page cache),立即向生产者返回成功,由操作系统在合适时机(如数据积累到一定量或系统空闲时)将页缓存数据刷盘。
- 这缩短了写入响应时间,提升应用吞吐量,但存在服务器故障时缓存数据可能丢失的风险,同步刷盘虽能保证安全但会降低性能。
同步副本 ISR 机制:平衡速度与可靠性
- 为防止节点故障导致数据丢失,Kafka 设计了同步副本 ISR 机制。
- 当 ACK=-1(最高安全级别)时,生产者消息先到达 leader 副本,leader 需等待消息同步到 ISR 集合中所有 follower 副本并收到确认后,才向生产者返回成功。
- 该机制确保核心副本收到数据,避免因个别慢副本拖慢系统,实现可靠的高性能。
分段 + 索引:加速海量数据检索
- Kafka 将大 Topic 拆分为多个分区(partition),每个分区日志又分为多个小的 segment 段,同时为每个 segment 配备 index 索引文件(稀疏索引,隔段记录消息位置)和 time index 时间索引文件。
- 查找消息时,先通过索引定位 segment 范围,再在段内扫描,类似查字典通过目录定位页码,避免全量扫描,提升检索速度,同时稀疏索引设计避免索引文件过大。
零拷贝:减少 CPU 数据搬运
- 传统数据传输需四次拷贝(磁盘→页缓存→用户态内存→socket buffer→网卡)和四次上下文切换,CPU 负担重。
- Kafka 使用 sendfile 系统调用实现零拷贝,数据从磁盘加载到页缓存后,CPU 仅发指令,数据直接在内核态从页缓存传输到网卡,仅两次 DMA 拷贝和两次上下文切换,解放 CPU,专注计算,大幅提升数据传输效率。
架构图解
Kafka高性能
顺序读写
零拷贝
页缓存
批量发送
磁盘顺序写
避免随机IO
sendfile系统调用
减少用户态切换
OS页缓存
减少磁盘IO
消息批量压缩
减少网络请求
第32题:微服务熔断机制详解
- 服务熔断针对的是微服务架构中的服务雪崩问题。
- 当调用链中某个下游服务(如服务 C)因高负载或 bug 响应极慢甚至宕机时,上游服务(如服务 B)的线程会因等待超时被耗尽,进而导致服务 B 对其上游服务(如服务 A)不可用,故障沿调用链反向传播,最终引发整个系统崩溃。
- 熔断通过在调用链中设置熔断器,当检测到下游服务异常时 "跳闸",拦截后续请求并快速失败,避免线程耗尽,从而斩断雪崩传播,本质是牺牲局部、保全整体的故障隔离思想。
熔断的实现与常用框架
- 常用熔断框架包括 Hytrix、Resilience4j、Sentinel 等,核心思想是通过熔断器对象(类似 AOP 切面)包裹远程调用。
- 熔断器内部通过滑动窗口实时计算错误率和慢调用比例,当指标超过设定阈值时触发 "跳闸"。
- 跳闸后并非简单抛异常,而是执行降级逻辑:切断原网络调用路径,转而执行本地代码(如电商商品详情页调用推荐服务失败时,返回空列表或本地热销榜单),确保核心功能可用且响应迅速。
服务熔断后的恢复机制
- 直接恢复远程调用(硬恢复)可能导致服务抖动,需引入三态状态机构成完整恢复逻辑:
-
- 关闭状态:熔断器正常,请求全部通过;
-
- 打开状态:请求被拦截并降级,计时器到期后转入半开状态;
-
- 半开状态:放行少量探测请求访问下游服务,成功则关闭熔断器恢复正常,失败则退回打开状态并重置计时器。
通过试探性探测实现软恢复,避免流量二次冲击。
- 半开状态:放行少量探测请求访问下游服务,成功则关闭熔断器恢复正常,失败则退回打开状态并重置计时器。
熔断与微服务架构的联动治理
- 高级熔断方案需结合微服务整体架构,实现客户端与服务端联动治理。
- 当某服务实例(如库存服务 A)熔断器打开时,通过心跳机制主动向注册中心(如 Nacos)上报故障状态(out of service);
- 注册中心移除故障实例,客户端负载均衡器拉取实例时仅获取健康实例(如库存服务 B、C),从源头避免请求路由至故障节点,实现故障的主动隔离与治理,构建弹性系统。
架构图解
微服务熔断
熔断器状态
关闭 Closed
打开 Open
半开 Half-Open
正常请求
监控错误率
快速失败
拒绝请求
试探性请求
判断是否恢复
熔断策略
错误率阈值
超时时间
重试机制
第33题:消息队列(MQ)可靠性保障与选型指南
- 消息不丢不重是 MQ 的核心问题。
- 消息丢失在电商场景可能导致用户付款后未发货,金融场景可能导致转账指令丢失;
- 消息重复可能导致重复发货、重复扣款,均会造成严重业务后果。
- 面试官通过该问题考察从生产者、broker、消费者三个环节构建完整保障体系的技术深度。
RabbitMQ 可靠性保障与去重
- RabbitMQ 遵循 AMQP 协议,以严谨性著称。
- 防丢策略:生产者端使用 confirm 确认机制,确保消息进入 broker;broker 开启持久化(消息存硬盘)并配置镜像队列(备份);消费者需业务逻辑处理完成(如数据库操作)后手动发送 ACK 确认信号。
- 去重机制:RabbitMQ 本身不保证去重,需消费者端实现幂等性,例如用订单 ID 作为唯一键存入 Redis,消费前查询是否处理过。
Kafka 可靠性保障与去重
- Kafka 以高吞吐为设计目标,追求极致性能。
- 防丢策略:生产者确认级别调为 all(leader 及所有 ISR 副本均接收);broker 端通过 topic 分区及多副本机制保障数据安全;消费者通过手动提交 offset 控制进度。
- 去重机制:引入幂等性生产者(保证单分区单会话内消息不重复)和事务(实现跨分区原子性写入)。
RocketMQ 可靠性保障与核心亮点
- RocketMQ 为国产消息队列,整合 RabbitMQ 与 Kafka 优点,适合复杂电商、金融场景。
- 可靠性:采用同步刷盘机制,消息必须写入磁盘才算成功;
- 高可用通过 Dledger 集群、主从集群实现主机宕机后自动选举切换。
- 核心功能:支持事务消息、顺序消息、延迟消息、死信队列等高级功能,其中两阶段提交事务消息机制可解决分布式事务中本地事务执行与消息发送的原子性问题。
MQ 选型指南
- 中小型项目(业务简单、可靠性要求高、并发量小)选 RabbitMQ;
- 海量日志收集、大数据分析、实时计算(需极致吞吐量)选 Kafka;大型分布式系统(复杂电商、金融核心链路,需事务、顺序等高级功能)选 RocketMQ。
- 技术选型需结合业务场景,没有最好只有最合适。
架构图解
消息队列选型
RocketMQ
Kafka
RabbitMQ
事务消息
顺序消息
适合电商
高吞吐量
日志场景
大数据生态
灵活路由
延迟队列
企业应用
可靠性保障
消息持久化
确认机制
重试策略
第34题:Java 面试:用户密码处理方案详解
面试中被问及 "项目用户名密码如何保存" 时,回答 "直接保存到数据库" 会暴露小白本质。即使有多年工作经历,该回答也会影响面试结果。
明文存储的风险
- 明文存储是最基础但绝对禁用的方式:用户输入密码(如 1314520)直接存入数据库,无需算法,简单高效。
- 但存在严重安全问题:黑客可直接获取所有密码;内部有数据库操作权限的人员可能偷看用户关键密码。
- 真实项目中明文保存密码被列为绝对禁用。
哈希加密方案
- 哈希加密算法(如 MD5、SHA256)类似搅拌机,原始密码输入后生成乱码密文存入数据库。
- 即使黑客获取密文,也无法通过逆运算得到原始密码,提升安全性。
- 但存在彩虹表破解风险:黑客提前计算常用密码的密文制成彩虹表,通过比对密文破解原始密码。
- 为此,系统常要求用户设置复杂密码(含字母、数字、符号)并定时修改,但这属于治标不治本。
加盐哈希的终极防御
- 加盐是成熟的终极防御方案,"盐" 指随机字符串。
- 哈希计算前给密码加随机盐,使密文更混乱。
- 方案有高下之分:固定盐(整个系统共用)存在泄露风险;更严谨的是为每个用户设置不同盐(如用用户 ID 某几位,或每次生成密码时随机生成盐),bcrypt 算法即采用随机盐机制,可彻底断绝黑客破解念想。
密码设计核心原则
- 核心原则是 "只验证不窥探":应用系统本身不知道用户原始密码,登录时通过加密后的密文核对。
- 因此,用户忘记密码时,系统只能让用户重置新密码,而非找回原始密码。
- 该原则在复杂分布式场景中同样适用,密码设计需更复杂安全,但核心逻辑不变。
常用加密算法对比
实际项目中常用多种加密算法,各有优缺点。具体对比表格可截图保存,帮助理解不同算法的适用场景。
架构图解
用户密码处理
存储安全
传输安全
验证安全
加盐哈希
BCrypt算法
避免明文存储
HTTPS加密
前端加密传输
恒定时间比较
防时序攻击
安全原则
最小权限
定期更换
多因素认证
第35题:5 亿用户数据分库分表设计
多数面试者依赖背诵 "垂直分、水平分" 等八股文,但面试官实际考察的是思考和解决问题的能力,而非记忆力。需结合业务场景设计合理方案,而非机械套用理论。
常规分库分表步骤
- 垂直拆分(冷热分离):将用户表拆分为热表 t_user_auth(存储登录认证信息)和冷表 T_user_profile(存储不常用资料),减少登录时读取的数据量。
- 水平拆分:将热表 t_user_auth 按用户 ID 哈希取模分为 1024 张小表,分散数据压力。
- 但常规步骤对真实系统而言只是开始,存在后续问题。
核心问题:用户名登录的数据查询难题
按用户 ID 分片后,若用户通过用户名登录,无法直接定位数据所在分表,若轮询 1024 张表会导致登录接口性能崩溃,这是面试官的关键追问点。
基因分片法解决方案
定义和抽取分片基因
- 从 username 提取信息:对 username 哈希取模转为数字,与 1023(2^10-1)进行 "与" 操作,得到 0-1023 的数字(二进制最后 10 位),作为分片基因,决定数据分片位置。
- 改造 ID,注入基因
用雪花算法生成原始 ID,将分片基因注入 ID 尾部:原始 ID 左移 10 位留出空间,与分片基因做 "或" 操作,新 ID 尾部 10 位为分片基因,按此基因分片存储数据。
双路归一
- 用户名登录:实时计算 username 的分片基因(哈希取模后与 1023 "与"),直接定位唯一分表,无数据则用户不存在。
- 用户 ID 查询:从 ID 尾部提取 10 位基因,直接定位分表。两条查询路线均以 O (1) 复杂度指向目标表,确保高效。
高级工程师思维体现
初级工程师关注分库分表本身,高级工程师注重分表后的数据路由。基因分片法的核心是写入数据时为未来查询设计(主动设计数据而非被动接受),体现技术方案与业务场景深度绑定、预见问题并提前解决的思维,这是面试脱颖而出的关键。面试考察的是思考深度,而非背题能力。
架构图解
5亿用户分库分表
分片策略
分片键选择
用户ID
均匀分布
分片数量
1024个分片
单表500万数据
ID生成
雪花算法
号段模式
数据迁移
双写方案
增量同步
数据校验
第36题:分布式锁实现详解
实现分布式锁首先需理解锁的基本定义:一个线程获取锁后,其他线程无法获取,即锁的互斥性。
锁的互斥性
锁的核心特性是互斥性,即一个线程拿到锁后,其他线程无法获取,需等待锁释放。
Redis setnx 实现互斥
通过 Redis 的 setnx 命令实现互斥,setnx 是单个命令,Redis 单线程执行天然保证原子性,无需额外处理原子性问题。
解锁的原子性保障
加锁后需解锁,解锁时需判断是否为当前线程的锁以避免误释放,此判断和释放为两步操作,需通过 LUA 脚本保证原子性。
锁的过期时间与看门狗机制
- 为防止线程挂起导致锁无法释放,需给锁设置过期时间。
- 若业务未执行完锁已超时释放,可通过看门狗机制(守护线程)定时续期,确保业务执行期间锁不释放;业务线程挂起时,守护线程随 JVM 终止,避免无限续期。
可重入锁实现
- 可重入锁指同一线程可多次获取同一锁,需通过锁计数器实现。
- 常见方案:
-
- Redis 哈希结构(如 Redisson):key 为锁名称,field 为线程 id+uuid(集群环境线程 id 可能重复,uuid 保证唯一性),value 为锁计数器(重入次数),底层使用 hsetnx;
-
- 服务内部维护 ConcurrentHashMap,value 作为锁计数器。
阻塞锁实现
阻塞锁指未获取锁的线程等待重试而非直接失败。
实现方式:
- 自旋重试(部分公司方案);
- Redis 发布订阅机制(Redisson):未抢到锁的线程订阅消息并阻塞,锁释放时发布消息唤醒线程重新抢锁,需设置超时避免无限等待。
主从架构与锁丢失问题
- 主从架构下,主节点写入锁后未同步到从节点即宕机,新主节点无锁数据,导致其他线程可重复加锁(锁丢失)。
解决方案:
- 连锁:部署多主 / 多主多从,需所有主节点加锁成功才算成功,缺点是性能差、失败概率高;
- 红锁(Redis 官方):部署多个主节点,半数以上加锁成功即算成功,基于分布式一致性算法保障互斥性。
红锁的局限性与实际应用
- 红锁对系统时钟一致性要求高,可能因 JVM 暂停导致看门狗无法续期、锁过期;
- 需部署多个 Redis 主节点,实现复杂且数据一致性难保障,实际应用较少。
- 多数公司采用简单分布式锁手动实现或使用 Redisson。
架构图解
数据库 Redis 客户端 数据库 Redis 客户端 alt [加锁成功] [加锁失败] SETNX lock_key value (NX PX 30000) OK 执行业务操作 Lua脚本删除锁 (校验value) 删除成功 NULL 自旋重试或返回失败
第37题:RocketMQ 顺序消息吞吐量优化
为保证消息顺序(如订单创建、支付、发货),消息需进入同一队列并由单个消费者处理,导致该消费者负载过高,其他消费者资源浪费,形成顺序与并发的天然矛盾。
吞吐量优化三招
- 第一招:加队列、巧路由
不为单个队列死磕,为 Topic 增加多个队列,通过统一规则(如订单 ID 哈希)路由消息至不同队列。不同订单自然分流后,可由多个消费者并行处理,提升吞吐量。 - 第二招:异步化,释放主线程
消费逻辑中若存在耗时操作(如外部 API 调用),避免消费线程阻塞等待,将其异步化并提交至线程池处理,主线程可立即返回处理下一条消息,实现核心业务不阻塞、耗时任务旁处理。 - 第三招:压测调优,找到黄金拐点
消费线程数量并非越多越好,过多会导致 CPU 频繁切换,反而降低性能。需通过压力测试确定性能最优的线程数(黄金拐点),使系统效率最大化。
高级优化玩法
- 批量发送:将多个小消息打包成一个大消息发送,减少网络传输开销。
- 服务端消息过滤:利用 RocketMQ 特性在服务端过滤消息,使消费者仅接收关心的内容(如仅处理 A 类订单,过滤 B、C 类),降低网络压力和计算资源消耗。
- 消费组隔离:不同业务组(如订单处理组、数据分析组)可独立消费同一订单数据,互不干扰。
实战注意事项
需关注全局唯一 ID、跨分片查询、分布式事务等问题,对应解决方案包括:使用雪花算法生成全局唯一 ID、通过全局表解决跨分片查询、利用 RocketMQ 自带的事务消息保证分布式事务一致性。
架构图解
顺序消息优化
问题分析
单Queue限制并发
吞吐量瓶颈
优化方案
分区顺序
并行处理
按业务Key分Queue
同Key保证顺序
不同Key并行
多消费者实例
Queue数量匹配
架构设计
订单ID为Sharding Key
同一订单进同一Queue
第38题:大文件上传中断处理方案
业务方上传 5GB 4K 高清宣传片,传输至 99% 时网络中断,需解决重传效率与资源浪费问题。
直接重传的弊端
- 时间成本:以上行网速 10MB/s 计算,5GB 文件传输需 500 秒(8.3 分钟),重传将浪费大量时间。
- 资源浪费:服务器需处理 5GB 无效流量,高并发场景下可能导致带宽打满,引发线上事故。
核心解决方案:化整为零
利用 HTML5 的 Blob.prototype.slice 方法,将大文件切分为小分片传输,提升容错率。
文件切片与 MD5 校验
- 切片处理:将 5GB 文件切分为 1000 个 5MB 的小块,编号 0-999,单个分片传输失败仅需重传该 5MB 分片。
- 全量 MD5 计算:为整个文件生成唯一 MD5 值(文件 "身份证"),内容不变则 MD5 值不变,用于后续定位与校验。
秒传机制
前端将文件 MD5 发送至后端,查询数据库是否存在相同 MD5 文件。
若存在,直接返回成功,无需传输数据,进度条瞬间 100%,提升用户体验。
断点续传
网络恢复后,前端通过 MD5 查询 Redis 中已接收的分片序号(如 1-990 号分片)。
仅传输剩余分片(如 991-1000 号),避免全量重传,节省流量。
合并与校验
分片传输完成后,前端发送合并指令,后端按序号拼装分片。
拼装后重新计算 MD5,与原文件 MD5 对比:一致则存入 OSS;不一致则提示重传,确保数据完整性。
全链路架构
前端:负责文件切片、MD5 计算。
传输:通过负载均衡发送分片。
后端:异步接收分片,合并文件,使用 Redis 记录传输进度,最终将文件存入 OSS。
进阶优化:三大暗坑
前端防卡死:切片数量多时,需使用并发控制池限制同时传输数量(如最多 3 个),避免浏览器崩溃。
后端防阻塞:收到分片后立即返回,异步处理 I/O 写入,不阻塞请求。
防磁盘 OOM:定时任务清理超过 24 小时未合并的废弃分片,避免磁盘空间耗尽。
总结
核心策略:前端化整为零,后端异步合并,存储计算分离。技术面试需体现对带宽资源的敬畏及网络波动的防御能力。
架构图解
OSS Redis 后端 前端 OSS Redis 后端 前端 loop [每个分片] alt [秒传] [正常上传] 文件切片(5MB/片) 计算文件MD5 发送MD5查询 查询MD5是否存在 文件已存在 返回成功(秒传) 文件不存在 上传分片 记录已上传分片 返回成功 发送合并请求 按序号合并分片 校验MD5一致性 存储文件 上传完成
第39题:拼团系统设计:高并发下的坑位抢占与转化保障
面试官问题:平台爆款双人成团商品,用户 A 开团差 1 人,1 万用户同时点击参与拼单,如何保证仅 1 人成功补位老团,其余用户如何处理?
常见错误方案及问题
直接使用 Redis 分布式锁锁住 group id 的问题:1 万请求抢 1 把锁会导致 Tomcat 线程池卡死,CPU 飙到 100%,9999 人支付失败,转化率接近 0,电商架构中不可接受。
拼团核心哲学
付钱是第一位,跟谁拼是次要的。商业逻辑中报错等于用户流失,目标是用户点支付绝不报错,收钱即业务成功。
三级火箭架构解决方案
第一步:流量层内存分流(Redis Lua 纳秒级分流)
1 万请求到来时,不在数据库加锁,直接在 Redis 内存运行 Lua 脚本。第一个用户成功补位老团,其余 9999 人内存判定满员,但系统发放新团后补券,保证所有人进入支付环节,保住转化意向。
第二步:交易层异步撮合(支付回调后拉郎配)
引入 MQ 消息队列和撮合引擎。支付完成后,支付回调触发撮合:补位老团成功的用户直接入团;未成功的用户,撮合引擎平滑降级,原地为其开启新团(如用户 B、C 开启新团),前端视角显示拼单成功,实现逻辑上的 "原地成团",转化率 100% 保留。
第三步:兜底层机器补位(延迟队列监控与虚拟账户补位)
通过延迟队列监控订单状态,临近 24 小时超时未成团时,系统自动校验商家和用户质量,注入虚拟账户完成订单闭环,保障 GMV 不流失(商家发货,平台收钱)。
完整架构流程
从 index 网关到 Redis 内存分流,再到支付驱动、MQ 撮合,最后由延迟队列触发虚拟机器人服务。
面试回答满分模板
极速占坑:用 Redis Lua 原子判定,拒绝锁阻塞,先收钱;
异步撮合:支付回调驱动,物理失败转为逻辑新建,原地成团;
业务兜底:延迟队列监控,虚拟账户补位,保障 GMV。
架构图解
成功
是
否
是
1万用户参与拼团
Redis Lua分流
第1个用户
其余9999用户
成功补位老团
发放新团优惠券
进入支付
支付结果
MQ消息
撮合引擎
老团是否满员
加入老团成功
原地开启新团
延迟队列监控
24小时未成团
虚拟账户补位
第40题:千万级用户系统设计与数据迁移架构
- 避免使用自增 ID:自增 ID 会暴露业务体量(如用户数量),且不利于分库分表。
- 慎用 Snowflake 算法:强依赖 ZooKeeper 或 K8s 协调运维,初创团队成本高。
- 推荐 UID v7 或美团 Live 算法:基于时间有序生成,对 B + 树友好,写入性能优异,解决随机 UID 导致的 B + 树分裂问题。
数据领域拆分
单表无法支撑千万级数据,需进行领域拆分:
核心高频访问数据(OCD 核心域):仅保留精简字段(如 UID、Token、密码哈希、盐值),需保证 99.99% 高可用。
非核心扩展信息(扩展域):存储非高频访问数据,Schema 灵活。
两个域通过 UID 保持 1:1 强关联。
分布式事务处理
- 拒绝强一致性分布式事务(如 XA、RPC):避免性能问题。
- 采用最终一致性方案:用户注册请求仅保证核心域(AUTO DB)同步写成功,通过异步消息(如 RocketMQ)或本地消息表,由 Profile Service 异步消费补齐扩展域数据,既保性能又不丢数据。
老系统数据迁移与 ID 映射
建立物理隔离的 Mapping Domain:避免污染新系统,专门记录新老 ID 对应关系和来源,需对新 ID 和老 ID 建立双重唯一约束,防止脏数据。
数据迁移策略:
- 全量清洗:使用 DataX 或 Flink CDC 进行离线历史数据清洗,灌入新库。
- 增量双写:通过 Canal 或 Debezium 监听老库 Binlog,实时同步增量数据,实现无缝迁移。
敏感数据安全方案
盲索引(Blind Index):
- 存储层:用 AES 对称加密算法加密敏感数据(如手机号、身份证号),用于后续解密展示。
- 索引层:用 HMAC 算法对原文生成固定长度散列值(盲索引),存入 B + 树,查询时通过哈希匹配盲索引,兼顾安全与高效。
缓存与防穿透:
- Redis 缓存:存储高频查询的盲索引 - 新 ID 映射,减轻数据库压力。
- 布隆过滤器:前置拦截伪造请求,防止缓存穿透,确保底层数据库流量有效。
架构总结
- 整体架构:流量经 Redis 布隆过滤器和缓存集群拦截,服务层包含 Leaf 发号器、MQ 异步解耦、Mapping 服务盲索引逻辑,底层为物理隔离的核心域库、扩展域库、Mapping 库及 Canal 同步流水线,实现高可用、高安全。
- 架构师思维:从 CRUD 到架构师的关键在于对冗余的克制、对安全的敬畏、对生产环境落地的执着,拒绝纸上谈兵。
第41题:线程池在实际项目中的应用场景与面试要点
核心思路:
- 不依赖中间件,通过本地机制实现可靠异步。
- 主线程执行业务逻辑时,开启本地数据库事务,向任务表插入状态为 PENDING(待处理)的记录,再提交任务到异步线程池执行。
- 线程池工作线程完成任务(如发短信)后,更新数据库状态为 SUCCESS(成功)。
- 若发生宕机,通过 XXLJOB 定时任务捞取 PENDING 状态任务进行重试补偿,实现 0 中间件依赖的异步提速与数据可靠。
微服务 RPC 并发聚合
应用场景:电商首页需并行查询用户信息(T1)、Banner 图(T2)、推荐商品(T3)、未读消息(T4)等多个 RPC 接口,通过 CompletableFuture 配合自定义线程池并行聚合,总耗时优化为最慢接口耗时(如从 1000 毫秒降至 200 毫秒)。关键注意点:
- 上下文穿越丢失:使用阿里 TransmittableThreadLocal(TTL)或自定义 RunnableWrapper 透传 User Token、TracelD,避免下游服务 401 未授权。
- 木桶效应与局部降级:对非核心接口(如未读消息)使用 completeOnTimeout 设置超时默认值(如 500 毫秒超时返回 0),防止边缘接口拖垮主干链路。
系统防御与观测
- 舱壁隔离模式:核心服务(如订单服务)与边缘服务(如日志查询服务)使用独立线程池(Dedicated Pool A 和 Pool B),避免边缘业务突发流量影响核心交易,遵循 "牺牲边缘业务,保全核心交易" 原则。
- 动态调参:通过 Hippo4j 或 Nacos 动态调整线程池 corePoolSize 和 maxPoolSize,应对大促流量,无需发版重启。
- 优雅停机:发版时挂载 JVM Hook,调用 awaitTermination 确保队列任务执行完毕再下线,避免任务丢失。
面试答题模板
- 可靠异步:本地线程池 + DB 任务表 + XXLJOB 补偿,拒绝无脑使用 MQ 导致过度设计。
- 并发聚合:CompletableFuture 并行提速,结合 TTL 透传上下文,使用 completeOnTimeout 实现局部降级。
- 系统防御:独立线程池舱壁隔离,配合 Nacos/Hippo4j 动态调参与优雅停机机制。线程池是资源调配、性能优化与系统防御的战略级工具。
架构图解
舱壁隔离
订单服务
独立线程池A
日志服务
独立线程池B
并发聚合
首页请求
CompletableFuture
并行查询T1-T4
聚合结果
TTL透传上下文
UserToken/TraceID
可靠异步
主线程
DB事务写入任务表
提交线程池
异步执行
更新任务状态
XXL-Job定时任务
捞取PENDING任务
重试补偿
第42题:亿级流量秒杀系统设计
秒杀系统的核心是通过 "流量漏斗" 模型,在各层拦截无效请求,最终仅让少量有效请求到达数据库。目标是将 99.9% 的流量在途中拦截,每一层像筛子一样过滤无效请求。
反面教材:直连数据库的问题
- 新手常犯的错误是直连数据库。
- 当几百 QPS 请求直接访问数据库时,会导致 MySQL 加排他锁(X 锁),大量线程排队,连接瞬间打满 CPU(飙升至 100%),系统极易崩溃,这并非合理架构。
正确架构:流量漏斗模型
漏斗模型的核心不是让数据库扛一级流量,而是逐层拦截流量,最终仅留少量请求给数据库。
第一层:客户端与 CDN 拦截
- 静态资源处理:将所有 HTML、CSS、图片等静态资源推送到 CDN,不进入机房。
- 按钮控制:通过极小的 status.js 文件(缓存时间设为 1 秒)控制秒杀按钮状态,活动开始时 CDN 回源更新,实现全网按钮瞬间变亮。
- 前端限流:采用动态权重策略,基于用户 ID 哈希值区分用户类型(VIP 用户放行 50%、普通用户放行 10%、羊毛党直接拦截),配合验证码等手段,将集中在 1 秒内的请求物理拉长到 5 秒,实现错峰。
第二层:NG 网关限流与防刷
- IP 限流:利用 OpenResty 在 NG 网关层对单 IP 进行限流,每秒超过 5 次请求直接返回 503。
- 动态 URL 防刷:用户需先申请加密 token,后端校验签名通过后才返回秒杀地址,防止黑客抓包死刷。
第三层:JVM 本地缓存保护 Redis
关键作用:JVM 本地缓存是保护 Redis 的核心,存储 "库存已售罄" 标记(如 isOver)。
流量拦截:当 Redis 库存扣完后,通过 MQ 广播通知所有应用服务器将 isOver 标记设为 true,后续 99% 的请求在 JVM 层直接返回 "已抢光",无需访问 Redis。
第四层:Redis 集群原子扣减与热点隔离
原子扣减:使用 Lua 脚本在 Redis 集群中进行原子性库存扣减,确保不超卖。
热点隔离:将热点商品(如茅台)路由到单独的分片,避免热点数据影响整个集群。
第五层:MQ 削峰填谷与数据库写入
Redis 扣减库存后,发送 MQ 消息,让数据库以约 2000 QPS 的速度异步写入,实现 "削峰填谷",将百万级并发转为低流量写入数据库。
Redis 故障处理:弃车保帅
若 Redis 响应变慢,触发熔断机制,直接拒绝服务,优先保证系统存活(系统存活大于业务可用是铁律)。
数据一致性保障:事务消息与离线对账
事务消息:使用 RocketMQ 事务消息,先发 half 消息,Redis 扣减成功后再 commit。
离线对账:若出现数据差异,通过离线对账机制确保库存准确。
总结:流量层层稀释的架构艺术
一级流量架构的本质是风险层层稀释:前端拦截 80%、网关拦截 10%、JVM 拦截 9%、Redis 拦截 0.9%,最终仅 0.1% 的请求到达数据库,实现系统高可用。
架构图解
拦截80%
拦截50%
拦截90%
拦截99%
100万QPS流量
第一层: 客户端+CDN
20万QPS
第二层: Nginx网关限流
10万QPS
第三层: JVM本地缓存
1万QPS
第四层: Redis原子扣减
100 QPS
第五层: MQ削峰
数据库写入
流量漏斗模型
层层拦截
最终仅少量请求到达DB
第43题:千万级订单架构的状态流转体系设计
- 面试官常问千万级订单架构如何设计永不跳变的状态流转体系。
- 传统直接修改状态(如 update 操作)存在严重隐患,需通过系统化方案解决。
真实事故案例
- 某跨境电商平台因并发竞态问题,已取消订单被仓库发货,导致货发款退,直接资损十几万。
- 根源是代码中存在大量散落在各处的手动状态检查(如满屏 if status == 1 else if status == 2),在高并发下极易出错。
传统 if-else 状态管理的三大死穴
- 逻辑分散难维护:业务逻辑散落在 controller、service 甚至 MQ 中,难以统一管理。
- 状态非法跳变:缺少状态机约束,可能出现跳过中间状态(如未支付直接变为已发货)的幽灵跳变。
- 并发踩踏:多线程同时操作(如线程 A 处理支付回调,线程 B 处理用户取消)导致数据错乱、逻辑死循环。
解决方案:状态机 + 编排引擎
核心理念是将状态变迁从业务逻辑中抽离,采用声明式管理(类似表格化清晰定义),替代命令式硬编码。
状态机核心四要素
- 当前状态(Current State):如 unpaid(未支付)。
- 目标状态(Target State):如 paid(已支付)。
- 触发事件(Events):如 pay success(支付成功)。
- 执行动作(Action):状态变迁时的具体逻辑。
- 推荐工具:阿里 COLA State Machine(轻量级),避免使用 Spring State Machine(过重),即 "不为切苹果考挖掘机证"。
并发处理:数据库版本号防御式更新
原理:通过版本号(version)控制,更新时需满足 "当前版本等于读取版本" 条件。例如线程 A 读取 version=1 并更新成功后 version 变为 2,线程 B 因仍持有 version=1 而更新失败。
SQL 示例:。
- 关键原则:"永远不要相信内存里的状态,数据库才是最后的防线"。
幂等性保障:业务指纹 ID
方案:使用 source + orderId + event + amount 生成 MD5 作为业务指纹 ID,配合 Redis 的 SETNX 锁防止重复消费,避免 UUID 难以查询的问题。
性能优化:同步流转 + 异步副作用
- 核心状态流转:通过状态机同步更新数据库状态,保证一致性。
- 副作用处理:将发短信、通知仓库、积分等耗时操作通过 MQ 异步处理,提升性能。
兜底机制:分片扫表 + T+N 补单
实现:使用 XXL-Job 作为清扫器(sweeper),采用 T+N 补单机制。通过 alt_id 模 N 分片,将任务广播给不同机器,仅扫描 10 分钟前数据(update_time < 十分钟前),避免拖垮数据库。
状态机防异常流转示例
当订单处于 cancelled(已取消)状态时,若仓库触发 ship goods(发货)事件,状态机查询无此变迁路径,会抛出 transition exception 异常,直接拦截发货操作,依赖系统规则而非人工细心。
面试总结
架构:使用 COLA 轻量级状态机。
并发:乐观锁版本控制。
幂等:业务指纹 MD5。
兜底:异步 MQ + 分片扫表。
架构本质是管理复杂度,if else 将复杂度藏在迷宫里,状态机则放在表格里,需从程序员升级为能设计永不跳变体系的架构师。
架构图解
支付成功
超时/用户取消
商家确认
仓库发货
用户确认
订单结束
未支付
已支付
已取消
待发货
已发货
已收货
已完成
状态机约束:
禁止非法跳变
如: 未支付→已发货
第44题:美团外卖订单表查询优化方案
问题:美团外卖订单表达 8000 万行,用户查询订单经常超时。
限制条件:工期仅一周,禁止引入分布式事务。
误区:直接采用分库分表不可行(需数据迁移、代码重构,耗时远超一周,且可能导致全库扫描、CPU 飙升)。
性能瓶颈分析
- 根本原因:B + 树高度超过四层,导致磁盘 IO 激增;大量历史冷数据(僵尸数据)占用 Buffer Pool,挤出热数据,内存不足。
核心思路:给数据库瘦身,只存储热数据(存储治理 + 异构解耦)。
异构解耦方案(Canal+ES) - 职责划分:MySQL 负责高频写入和简单 ID 查询;复杂历史查询(如多维度搜索半年前订单)由 ES 处理。
- 实现方式:通过 Canal 监听 MySQL 的 Binlog,实时同步数据到 ES。
- 延迟处理(5 分钟原则):订单生成后 0-5 分钟内强制走 MySQL 主库查询,确保用户无感知;超过 5 分钟的历史订单路由至 ES 查询,平衡体验与压力。
冷热分离方案(pt-archiver)
- 目标:将 MySQL 热表控制在 500 万行以内(B + 树保持 3 层,全内存命中,查询高效)。
- 冷数据处理:7500 万行冷数据迁移至 HBase 或冷库。
- 迁移工具:使用 Percona 的 pt-archiver,按 "蚕食" 方式(如每天凌晨每秒搬运 500 条)迁移,避免主从延迟和磁盘 IO 波动。
数据一致性保障
- 防御性编程:归档逻辑需先将数据写入冷库,Verify 校验成功后再删除热库数据,形成 ACK 闭环,防止数据丢失。
- 兜底核对:每天凌晨通过 Spark 任务全量核对热库 Count + 冷库 Count 是否等于 ES 总数,确保异构传输中数据零丢失。
总结
核心方案:存储层面用 pt-archiver 轻量化治理(热表≤500 万行);查询层面构建 Canal+ES 异构架构(解决历史复杂查询);稳定性通过离线核对 + ACK 机制保障数据一致。
优势:不重构业务代码,不引入分布式事务,性价比高。
口诀:大表查询别慌张,先看冷热分没分,再看 ES 强不强。核心能力是对系统复杂度的控制力,保持核心数据库轻盈纯粹。
架构图解
<5分钟
>5分钟
8000万订单表
性能瓶颈分析
B+树高度>4层
冷数据占用Buffer Pool
解决方案
冷热分离
异构解耦
pt-archiver迁移
热表≤500万行
B+树保持3层
Canal监听Binlog
同步到ES
历史查询走ES
查询路由
订单时间
MySQL主库
ES索引
第45题:分布式事务解决方案解析
- 传统单体架构中,通过注解结合数据库 ACID 特性即可保证事务一致性。
- 但在微服务架构下,订单库、积分库、优惠券库物理隔离,本地事务管理器无法跨服务控制,因此需引入分布式事务。
Seata AT 模式
- 特点:零侵入,通过添加注解,Seata 自动生成 Undo Log 回滚日志;业务成功时删除日志,失败时基于日志回滚数据。
- 脏读问题:第一阶段释放本地数据库锁后,若其他未加 Seata 锁的线程读取中间状态数据,全局事务回滚后会导致脏读。
- 解决方案:读操作需添加注解,并配合强制检查全局锁。
- 局限性:依赖全局锁,高并发场景(如双 11 秒杀)性能瓶颈明显。
TCC 模式
核心流程:Try(资源预留)、Confirm(确认执行)、Cancel(取消操作)三步,完全依赖业务代码逻辑控制,不依赖数据库事务。
Schema 侵入:需改造数据库表,如积分表需增加(冻结金额)字段,,老系统改造风险高。
三大问题及解决:
- 空回滚:因网络丢包导致 Try 未执行,Cancel 却触发。解决方案:创建事务控制表,Cancel 执行前检查 Try 记录,无则返回成功。
- 悬挂:网络拥堵导致 Cancel 先于 Try 执行,Try 后续执行冻结资源造成僵尸锁。解决方案:Try 执行前检查 Cancel 记录,存在则拒绝执行。
- 幂等性:网络抖动导致 Confirm/Cancel 重试,重复扣款。解决方案:执行前通过全局事务 ID 查表,若已处理则拦截返回成功。
Saga 模式
- 适用场景:积分服务为第三方接口(如银行接口、SaaS 服务),无法修改数据库时使用。
- 流程:直接执行扣款操作,若后续步骤失败,调用补偿接口(如退款接口)回滚。
- 定位:面对老系统和第三方接口的 "无奈之选",相当于 "拐杖",保证最终一致性。
分布式事务方案选择标准
从五个维度评估:
- 一致性:TCC 最强(Try 阶段锁定资源,准强一致);Seata、MQ 为最终一致。
- 性能:MQ(异步解耦)最快,TCC(锁粒度小、无全局锁)次之,Seata AT(依赖全局锁,高并发阻塞)最低。
- 侵入性:Seata AT 几乎无侵入(注解即可);TCC 强侵入(需编写 Try/Confirm/Cancel 方法,代码量翻倍)。
- 表结构改造:TCC 需改表(加冻结字段);Saga 和 MQ 无需改表。
- 适用场景:Seata 适合低并发后台业务;TCC 适合核心扣减场景;MQ 适合充值、发货、通知等允许延迟的场景。
总结
- Seata AT:开发便捷("糖"),但核心系统需谨慎使用。
- TCC:开发复杂("苦药"),但能解决高并发核心场景问题。
- Saga:适用于第三方接口或老系统("拐杖")。
- 技术选择需结合场景,无 "银弹",只有取舍。
架构图解
分布式事务方案
2PC两阶段提交
TCC补偿事务
本地消息表
事务消息
准备阶段
提交阶段
缺点: 同步阻塞
半消息机制
事务回查
优点: 异步解耦
第46题:京东零售 T7 架构题:购物车 "最省钱组合" 解决方案
购物车有 50 件商品,账户有 100 张优惠券(满减、折扣、跨店等),需在 200 毫秒内计算最省钱组合,要求计算准确且高效。
数据规模与业务筛选
架构第一步需认清数据规模,无需对 100 张券全量计算,先通过业务筛选排除无效券:过期、类目不匹配、未到使用时间的券直接过滤。
最终有效且关系复杂的券约 20 张,为核心计算对象。
动态规划(DP)的局限性
- 普通背包问题状态仅包含容量和价值,电商场景下状态复杂:券互斥关系、满减门槛、店铺券与平台券叠加规则等。
- 若使用 DP,状态数组需定义为多维(券索引、当前总价、店铺金额、互斥组 ID 等),导致内存溢出且代码难以维护,无法满足 200 毫秒要求。
核心解决方案:图论降维与强力剪枝
图论降维
构建券 - 商品关系图,发现券可分裂为多个 "连通孤岛"(如手机券与零食券无关),将 2^20 的指数级问题拆分为多个 2^5 的小问题,计算量从指数级降至可控级。
支配剪枝
预处理阶段移除被支配的劣质券,如 A 券(满 100 减 20)与 B 券(满 100 减 10)作用范围相同,A 更优则 B 直接丢弃,避免浪费 CPU 资源。
工程落地关键策略
零 GC 优化
拒绝 Young GC:全程使用 long 类型(精确到分),避免对象创建;拒绝 iterate 和 sublist 操作,采用原始索引遍历,追求极致 FastAccess。
线程策略
小任务(少量券)主线程串行计算,避免多线程导致 CPU 资源耗尽;巨型任务(几十张券)使用 ForkJoinPool 并行计算,实现 "有的放矢"。
动静分离与高效互斥判断
- 动静分离:规则引擎(如 LiteFlow、Drools)预处理业务规则,核心计算引擎接收纯净数字,禁止核心循环中反射操作。
- 互斥判断:使用 BitMap 位图将互斥组 ID 转为 long 类型二进制位,通过位运算 AND 操作(O (1) 复杂度)快速判断券兼容性。
总结
算法层面:放弃 DP,采用回溯法。
策略层面:图论降维拆分问题,支配剪枝减少计算量。
工程层面:死磕零 GC 优化、合理线程策略、位运算提升效率。
核心价值:体现对底层(JVM)、业务的理解,实现精准计算提升 GMV,高效计算节省服务器成本。
架构图解
购物车商品
计算最省钱组合
优惠券类型
满减券
折扣券
无门槛券
动态规划计算
生成所有组合
计算每种组合价格
选择最优方案
优化策略
剪枝: 排除明显非最优
缓存: 热门商品组合
返回最优价格
第47题:拼多多 "砍一刀" 核心算法与架构拆解
核心逻辑围绕获客成本(CAC)。若手机成本 2000 元,公司愿意承担的获客成本为 200 元,则算法目标是当用户拉取的新用户价值达到 200 元时,才允许砍下最后一刀。
算法设计:用户画像权重与芝诺悖论
根据用户画像计算权重:新用户权重高(如 10.0,砍 20 元),老用户权重低(如 0.1,砍 0.2 元),黑产号权重极低(如 0.001,砍 0.01 元)。利用芝诺悖论,当进度条接近 100% 时,剩余金额拆分为无限小量(如 0.5、0.2、0.1...),用户永远逼近成功但难以完成。
架构设计:存储与高并发处理
- 存储方面,避免使用浮点数(精度问题),采用去小数点化,将 1 元换算为 100 万微,用 Redis 的 Long 类型存储整数,配合 Lua 脚本实现原子减扣,确保精度和速度。
- 高并发处理:针对热 Key 问题,使用本地缓存(JMM 层)拦截 90% 无效查询,剩余流量通过 MQ(消息队列)削峰填谷,前端提示 "砍价中",后端异步处理,防止数据库崩溃。
风控措施:生物探针与验证码
- 针对黑产刷单,采用生物探针识别真人操作(如手机陀螺仪抖动、手指按压面积变化),机器人识别后风控降权(前端提示成功,后台扣减 0 元)。
- 高级脚本通过强制图灵测试(Captcha),在关键节点(如差 1 元或 0.01 元时)弹出滑动拼图或选字验证码,有效拦截自动化脚本。
产品层处理:单位置换解决精度问题
因数据库精度已到 1 微(0.000001 元),前端无法显示过多小数位,采用单位置换:如差 0.01 元时,转为金币(100 金币 = 0.01 元),金币不足时送碎片,通过贬值货币单位拉长进度条,让用户感觉 "只差一点点"。
面试总结
- 算法层:CAC 模型 + 芝诺悖论;存储层:Redis Long 类型存微单位;风控层:生物探针 + 验证码;产品层:单位置换解决精度耗尽。
- 本质是技术保证平台不亏,心理学保证用户参与感。
架构图解
是
否
用户点击砍价
计算砍价金额
砍价算法
基础金额
随机波动
用户权重
剩余金额/剩余次数
随机因子
新老用户差异
最终砍价金额
是否最后一刀
精确控制到目标价
记录砍价记录
防作弊
设备指纹
行为分析
频率限制
第48题:用户积分系统架构设计
- 积分系统不是简单计数器,而是准金融系统,涉及流水追溯、效期管理、对账核算。
- 单字段存储总数会丢失时间维度,无法处理多笔积分的过期和扣减问题(如用户多笔不同时间获取的积分,消费时无法确定扣减哪笔,过期时间也无法准确计算)。
数据库设计
-至少需要三张表:
- 积分总表(User_Point_Wallet):存储用户积分总数,用于快速查询展示;
- 积分流水表(Point_Flow_Log):记录每笔增减操作,不可修改,用于对账;
- 积分明细分桶表(Point_Detail_Bucket):记录每笔入账积分的余额和过期时间,决定积分的生死。
积分扣减策略
按过期时间升序(expire_time ASC)排列,采用 FIFO(先进先出)原则扣减,先扣快过期的积分,不够则依次扣减后续积分。
过期提醒与清理机制
- 高效提醒采用离线计算 + 惰性清理。
- 利用大数据平台 T+1 离线计算快过期用户清单,通过 MQ 削峰填谷发送通知;
- 清理采用惰性清理,读写时过滤过期积分,避免每日全表扫描。
关键问题解答
- Q1:大量小分桶合并问题,需引入定期合并(Compaction)机制,后台 Job 合并小分桶为大分桶,减少锁冲突;
- Q2:积分作为资产需强一致性,Redis 只能作读缓存,不可异步落库,避免数据丢失导致资损;
- Q3:退款时原路退回,记录消费明细逆向恢复,未过期分桶恢复,过期分桶通常不退或延期。
核心思路总结
- 总分分离 + FIFO 扣减 + 离线提醒。
- 数据库拆分总额表、流水表、明细表;扣减遵循 FIFO 并定期合并分桶;资产安全坚持强一致性,拒绝 Redis 异步写;过期提醒采用 T+1 离线计算配合 MQ 削峰。
- 积分系统核心是资产管理,需把控数据一致性和海量数据成本。
架构图解
用户积分系统
积分获取
积分消费
积分过期
签到奖励
消费返积分
活动奖励
积分抵扣
积分兑换
积分抽奖
定时任务扫描
批量过期处理
数据一致性
MQ异步处理
幂等性校验
事务消息保障
高并发优化
Redis预扣积分
批量异步入库
第49题:Elasticsearch 集群架构与资源规划
- 以日增 1TB 日志场景为例,存储需求需考虑副本、段合并及水位线。
- 原始数据计算:1TB (日增)×7 天 ×2 副本 = 14TB。
- 实际存储需求需用经验公式:原始数据 ÷0.6,即 14TB÷0.6≈23.3TB。
- 规划 10 台 Data 节点,每台挂 4TB,总容量 40TB,水位线 60%,既不浪费资源又留扩容缓冲。
分片大小的黄金法则
- 单个主分片过小将导致 Master 节点元数据压力过大,过大则故障恢复慢、查询卡顿。
- 业内黄金法则:单个主分片控制在 30-50GB。
- 日增 1TB 数据(1024GB)建议分 24 个主分片,每个分片约 42GB,符合最佳区间。
物理拓扑与角色分离
- Master 节点:3 台专有节点,4 核 8G 配置,跨可用区(ZoneA、B、C 各一台)部署,实现机房级容灾。
- Data 节点:10 台高配节点(16 核 64G+NVMe SSD),负责数据写入和聚合查询。JVM 堆内存设为 31GB(利用 Java 指针压缩优化性能),剩余 33GB 留给操作系统 Page Cache 提升查询速度。
- 协调节点:业务增长后独立部署 2 台,仅负责分发、汇聚请求,不存数据,隔离聚合计算风险与存储节点,保障核心写入不受影响。
- 磁盘配置:采用 RAID0,利用 ES 应用层副本机制保证数据安全,硬件层追求极致 I/O 速度和 100% 磁盘容量利用。
场景化调优
不同场景调优策略不同:
- 日志场景(写多读少):将 refresh_interval 从 1 秒改为 30 秒,减少段合并开销;translog 设为异步,提升写入吞吐量。
- 电商搜索场景(读多写少):refresh_interval 调回 1-5 秒保证实时性,分片缩小至 20-30GB 提高并发查询速度。
面试模板
- 容量与分片:日增 1TB 日志业务,10 台 Data 节点(每台 4TB NVMe SSD 组 RAID0),按 30-50GB / 分片原则设 24 个主分片(单片约 42GB)。
- 物理架构:13+2 节点角色分离,3 台 Master 跨可用区防脑裂,10 台 Data 节点 JVM 堆锁定 31GB,剩余内存给 Page Cache。
- 风险隔离:部署 2 台独立协调节点处理深度聚合,分离计算与存储,防止 OOM。
- 场景调优:日志场景 refresh_interval=30 秒,开启 Translog 异步,以极小延迟换写入性能翻倍。
- 架构设计核心:无标准答案,需结合业务需求,从容量计算、分片规划到角色分离、风险隔离,决策逻辑是关键。
架构图解
ES集群架构
节点类型
Master节点
Data节点
Coordinating节点
集群管理
索引创建删除
数据存储
查询执行
请求路由
结果聚合
资源规划
内存: 堆内存<32GB
磁盘: SSD优先
网络: 万兆网卡
第50题:Elasticsearch 分片架构解析
- 分片的核心目的是突破单机物理极限,实现分布式存储和横向扩展的最小工作单元。
- 当数据量超过单台服务器的存储或处理能力时,通过分片将数据切分存储在不同节点,解决单机资源限制问题。
分片的物理本质
每个分片(Shard)本质上是一个完整独立的 Apache Lucene 索引,具备独立的倒排索引和缓存,并非简单文件夹,而是功能齐全的微型搜索引擎。
分片的类型
- 主分片(Primary Shard):数据的源头,所有写操作(增、删、改)必须先在主分片完成。主分片数量在创建索引时确定后不可修改。
- 副本分片(Replica Shard):主分片的完全拷贝,主要作用有两点:一是主分片故障时可替代主分片保证高可用;二是分担查询请求,提升查询吞吐量。
主分片数量不可修改的原因
ES 通过路由算法确定数据存储的分片,公式为:。若主分片数量修改,哈希取余结果变化,导致 ES 无法找到原数据位置,造成数据 "失联"。
例如,原主分片数量 5,某数据 hash 值 6,存储在 Shard1(6%5=1);若改为 10,6%10=6,ES 会去 Shard6 查找,导致数据找不到。
分片设计大小建议
单个分片大小建议保持在 30GB 到 50GB 之间。太小会导致资源内耗严重,太大则会延长故障恢复时间,此范围能平衡存储容量与系统性能。
架构图解
ES分片架构
主分片
副本分片
数据写入
分片路由
数据复制
查询负载均衡
高可用保障
分片策略
分片数量
单分片<50GB
避免过多分片
路由算法
G
第51题:Elasticsearch 性能调优
- 堆内存设置:Java 的 Compressed Oops 指针压缩在堆内存超过 32G 时失效,导致对象内存占用变大、性能腰斩,因此堆内存永远不要超过 32G。
- 内存分配黄金比例:50% 内存给堆内存用于聚合排序,50% 留给操作系统作为 Filesystem Cache,供 Lucene 缓存 Segment 文件。
架构层优化
- 分片大小控制:单个分片控制在 30G 到 50G,避免分片过大导致迁移恢复慢,或分片过多拖垮 Master 节点的元数据管理。
- 索引滚动:配合 Rollover 机制,当索引满 50G 或超过 7 天自动滚动生成新索引。
- 冷热分离架构:Hot 节点使用 SSD 存储最新热数据,负责写入和查询;Cold 节点使用大容量 HDD 存储 7 天前数据,可将副本数降为 1 仅做归档,实现资源高效利用。
写入层优化
- 批量写入:使用 Bulk API 替代单条 insert,一次写入 5MB 到 15MB 为最佳大小。
- Refresh Interval 调整:默认 1 秒生成新 Segment 文件,可调大至 30s 或在 Bulk 期间设为 - 1,减少 Segment 生成频率。
- Translog 设置:将 Translog 改为 Async 模式,以安全性换取写入速度。
查询层优化
- 深分页问题(Deep Paging):业务上禁止跳页(如 from=10000,size=10 场景),技术上使用 Search After 替代,避免内存爆炸,提升效率。
- 左模糊查询优化:Term Index 基于前缀树,左模糊查询会导致全表扫描,可在写入时通过 reverse 翻转字符串或使用 ngram 分词解决。
架构图解
ES性能调优
写入优化
查询优化
内存优化
批量写入
调整refresh_interval
禁用副本临时
合理使用filter
避免深度分页
字段映射优化
Filesystem Cache
堆内存设置
监控指标
索引速度
查询延迟
JVM使用率
第52题:短信接口防刷实战方案
黑产主要分为两类:
- 一是短信轰炸机,通过单一手机号进行高频发送,属于报复性攻击;
- 二是薅羊毛注册机,利用大量廉价手机号和动态 IP 批量注册,旨在消耗平台资源。
常见防御误区
新手常采用前端倒计时(如点击发送按钮后限制 60 秒),但黑产可直接调用后端 API 接口绕过前端限制,此类防御形同虚设。
黄金防御三道防线
强制滑块验证
重点在于后端需二次验证滑块的 token,验证失败直接拦截,可拦截 90% 的基础脚本攻击。
Redis 多维限流
使用 Redis Lua 脚本保证原子性,同时限制两个维度:同一手机号的发送频次(防轰炸)和同一 IP 的发送频次(防羊毛党),应对廉价代理 IP 的绕过。
接口签名机制
通过时间戳、随机数和签名校验,防止黑客抓取正常请求包后进行重放攻击,过滤过期或重复请求。
业务逻辑层防御(王者级操作)
数据校验埋雷
例如在找回密码场景,先查询数据库,若手机号不存在则直接报错;或假装发送成功,消耗攻击者资源。
蜜罐参数
在页面中嵌入隐藏输入框(正常人不可见),若脚本自动填充该参数,则直接封禁 IP。
兜底限流措施
为短信接口设置总请求阈值(如每秒最多 100 个请求),即使防线被突破,也能控制损失,避免瞬间高额欠费,为报警和处理争取时间。
防御核心原则
防刷本质是成本博弈,需组合使用滑块验证、Redis 限流、接口签名、蜜罐参数及兜底限流,形成完整防御体系,提升黑产攻击成本。
架构图解
是
否
否
是
短信发送请求
风控校验
IP限流
手机号限流
设备指纹校验
超过阈值?
拦截请求
图形验证码
验证通过?
发送短信
防刷策略
滑动窗口限流
令牌桶算法
黑名单机制
记录发送日志
更新限流计数
第53题:微服务架构中网关的核心作用与实践
网关的本质是微服务架构中的统一门面(facade pattern),是整个系统的咽喉,负责整合和管理所有客户端与后端微服务的交互。
无网关的问题:强耦合
若没有网关,客户端(如 APP、小程序、网页)需直接连接后端数百个微服务,需知道每个服务的 IP 和端口。一旦后端服务扩容、迁移导致 IP 变化,客户端需同步修改代码,形成强耦合,易出现扩容即崩溃的问题。
网关的核心价值:解耦
有了网关后,客户端只需识别唯一入口,后端服务的任何变化(如扩容、迁移)均由网关中间层处理,实现客户端与后端服务的解耦,这是网关存在的最大意义。
统一认证与关注点分离
- 网关作为 "保安队长",统一处理认证逻辑:请求进入时先验证 token,验证通过后解析出 user id 并传递给下游服务。
- 下游服务(如 service A、service B)无需关注认证细节,实现关注点分离,避免在每个微服务中重复编写认证代码,减少后期 token 规则变更时的维护成本。
高可用防线:限流与熔断
网关是系统的 "大坝",在流量高峰期(如双十一)可利用 Sentinel 或 Redis 令牌桶算法,在入口处拦截多余请求,直接熔断保护后端服务,防止数据库等资源被压垮,保障系统高可用。
协议转换:内外接口适配
网关可作为 "翻译官",对外暴露友好的 RESTful HTTP/JSON 接口,对内将外部 HTTP 请求转换为内部高性能 RPC 调用(如 Dubbo、gRPC),实现内外协议的适配。
BFF:为前端服务的后端
针对不同设备(如手机屏幕小需 3 个字段,PC 屏幕大需 10 个字段),网关可通过 BFF(Backend for Frontend)模式进行数据聚合与裁剪。
例如,将服务 A 和服务 B 的数据拼接后,仅返回手机端所需的 3 个字段,避免手机端浪费流量,同时减少后端接口开发量。
网关选型与黄金搭档
传统网关(如 Zuul)基于阻塞 IO,性能较差,已逐渐被淘汰。
Nginx:性能王炸,适合作为最外层的流量网关,处理高并发流量。
Spring Cloud Gateway(SCG):Java 生态最强,适合处理复杂业务逻辑的业务网关。
云原生时代黄金搭档:Nginx 抗流量 + SCG 处理业务,兼顾性能与业务灵活性。
架构图解
网关核心功能
统一入口
协议转换
请求过滤
响应聚合
客户端
API网关
路由转发
负载均衡
限流熔断
认证鉴权
日志监控
用户服务
订单服务
商品服务
第54题:ElasticSearch 底层核心揭秘:从 HashMap 到 FST 的倒排索引优化
- MySQL 使用 B + 索引,其本质类似书的目录,按页码(如 ID)查找文档时效率高,但无法高效支持内容搜索。
- 例如搜索内容包含 "Apple" 的文章时,MySQL 需进行全表扫描(Full Table Scan),即使数据量达几千万也需耗时很久,可能导致 CPU 负载过高。
ES 倒排索引的结构
ES 的倒排索引(Inverted Index)逻辑与 B + 索引相反,是 "词找文档" 而非 "文档找词"。其物理结构包含三部分:
- Term Index(词项索引):存放于内存,用于快速定位 Term 在 Term Dictionary 中的位置。
- Term Dictionary(词项字典):存放于磁盘,存储所有关键词 Term。
- Posting List(倒排表):存放于磁盘,存储每个 Term 对应的文档 ID 列表。
- 三者配合类似查字典:Term Index 如同拼音索引,帮助快速定位到 Term Dictionary 中的具体 Term,Posting List 则是 Term 对应的文档 ID 列表。
FST 解决 Term Index 内存瓶颈
若使用 HashMap 存储 10 亿个 Term,因 String 对象头、entry overhead 等开销,内存占用可达几十个 G,易导致内存溢出。
ES 采用FST(Finite State Transducer) 数据结构优化:通过共享相同前缀和后缀(如 "mmop""m 负" 共享前缀 "mm","pop""top" 共享后缀 "op"),将线性单词列表折叠为立体图,大幅节省内存。
原本 10G 的索引可压缩至 200MB,实现 Term Index 常驻内存,达成 "空间换时间"。
- Frame of Reference 压缩优化 Posting List
Posting List 存储文档 ID 列表,若直接存储 int 类型(4 字节 / 个),一亿个 ID 需 400MB,磁盘 I/O 压力大。ES 使用Frame of Reference 算法,通过存储差值(delta)而非原值压缩数据。例如原始数据 1000000、1000001、1000002,存储为 1000000、1、1,后续小差值可用 1bit 存储,将 400MB 数据压缩至几 MB,显著降低磁盘 I/O 负载。 - Roaring Bitmap 优化文档 ID 交集计算
多条件搜索(如 "sex = 女 AND age=18")需对多个 Posting List 求交集。
直接双重循环效率低,Bitmap 对稀疏数据(如 ID 为 1 和 1 亿)内存浪费大
。ES 采用Roaring Bitmap:将 32 位 ID 切分为高 16 位和低 16 位,根据容器(container)数据量动态选择存储方式 ------ 数据量<4096 时用 ArrayContainer(节省空间),≥4096 时用 BitmapContainer(计算更快),兼顾空间效率与计算速度。
ES 搜索完整流程
请求进入后,FST 在内存中毫秒级定位 Term 在磁盘的位置;
磁盘通过 Frame of Reference 算法解压数据,加载 Posting List(文档 ID 列表);
CPU 使用 Roaring Bitmap 对多个 ID 列表进行位运算求交集,最终返回结果。
核心优化点:FST 解决内存瓶颈,Frame of Reference 解决磁盘 I/O 瓶颈,Roaring Bitmap 解决算力瓶颈。
架构图解
ES倒排索引
Term Dictionary
Posting List
FST压缩
排序词典
二分查找
文档ID列表
位置信息
有限状态机
前缀压缩
内存高效
查询流程
Term查找
获取Posting List
求交集返回
第55题:慢 SQL 优化:全链路思维与高阶打法
建立全链路监控是发现慢 SQL 的关键,包含两个基建。
- 第一,慢查询日志(Slow Query Log),生产环境阈值设 1 秒,配合 pt-query-digest 每天生成报告,识别拖后腿的 SQL。
- 第二,实时监控,关注仪表盘上 QPS 的抖动、锁等待(Lock Wait)及 I/O 飙升,在报警时及时抓现场。
诊断工具的三个层级
- Level1:读懂 EXPLAIN 的 "X 光片",重点关注 type 和 Extra 字段。type 为 all(全表扫描)或 index(全索引扫描)需警惕,目标至少是 ref 或 range;Extra 中 Using index 表示索引覆盖,Using filesort(文件排序)需注意,内存足够时在内存排序较快,内存不足时磁盘归并排序才是大忌。
- Level2 和 Level3:掌握 Performance Schema 或 sys,如 sys.statement_analysis 可查看全实例哪类 SQL 最耗 I/O,替代已废弃的 SHOW PROFILE。
实战避坑
- MySQL 8.0 的索引跳跃扫描:当联合索引中左侧列区分度低(如性别状态),优化器会触发索引跳跃扫描,将查询拆分成左侧列不同值的多个查询,仍能使用索引。
- 深分页痛点解决:LIMIT 1000000,10 慢是因为需扫描 100 万零 10 行再丢弃前 100 万行,可采用延迟关联(defer join),子查询利用覆盖索引先获取 id,再关联主表取数据,减少无效劳动。
架构维度优化
核心思想是空间换时间,转移阵地。复杂的多条件筛选搜索适合用 Elasticsearch,高频热点数据放入 Redis。采用冷热分离,将 3 个月前老数据归档到历史表或 Hive,核心业务表只保留热数据。实施读写分离,报表类慢 SQL 强制走从库,避免影响主库写性能。
优化闭环
- 优化是循环过程:从监控发现问题,通过 EXPLAIN、Trace 诊断,进行索引优化、架构升级,最后评估 ROI(投入产出比)。
- 面试回答应涵盖监控(慢日志和实时大盘)、诊断(Trace 分析成本、Sys Schema 看资源)、战术(延迟关联、8.0 跳跃扫描)、架构(ES、Redis 兜底)的组合策略。
架构图解
慢SQL优化
定位问题
分析原因
优化方案
慢查询日志
EXPLAIN分析
全表扫描
索引失效
锁等待
索引优化
SQL重写
分库分表
优化原则
避免SELECT *
合理使用索引
减少JOIN