心动方糖面试题

0 阅读8分钟

一、业务金额一致性如何保证(全套生产方案)

1. 数据库底层精度保证

金额字段统一使用 DECIMAL 定点类型,禁止 double/float,彻底避免浮点精度丢失。

2. 单机并发一致性(防超扣)

  • 悲观锁:余额扣减使用 select ... for update 行排他锁,事务内锁定用户余额,防止并发超扣。
  • 乐观锁:通过 version 版本号机制,更新时校验版本,冲突直接重试,适合读多写少。

3. 本地事务一致性

单笔账务操作必须事务包裹:扣余额、生成流水、状态变更 同时成功、同时回滚,不允许半事务。

4. 分布式跨服务一致性

  • 使用 RocketMQ 事务消息/本地消息表 最终一致性方案。
  • 顺序:先落库、再发消息,杜绝先发消息后回滚导致资金错乱。
  • 生产者确认、消费者手动 ACK、失败重试、死信兜底。

5. 幂等防重复扣款

所有交易使用唯一流水号,接口层、MQ消费层双重幂等,避免重试重复扣款。

6. 最终兜底:每日对账

上游流水、平台余额、下游渠道三方对账,差额自动生成差错单,人工调账,保证最终数据绝对一致。

二、跨数据源事务问题(多库事务)

核心问题:一个 Spring 事务管理器只能管一个数据源,跨库会出现部分提交、部分回滚,数据不一致。

最优方案:业务拆分,从根源杜绝跨库事务。

分布式事务解决方案

  • Seata AT(项目最常用) :无侵入、undo日志快照、全局协调,失败自动回滚。
  • TCC:Try预留、Confirm确认、Cancel回滚,高资金高可靠场景使用,侵入高。
  • SAGA:长流程事务,正向执行+补偿回滚,适合超长业务链路。

三、订单大数量、多状态、多条件查询索引设计

  • 订单号:唯一索引,精准查询。
  • 用户ID + 创建时间 + 订单状态:联合索引,满足用户维度、时间段、状态筛选(最左匹配)。
  • 创建时间 + 订单状态:运营后台全局时间范围、状态统计、分页查询。
  • 主键索引保证基础查询性能。

四、ES 根据ID查询 API

直接通过文档主键查询,不走检索:

GET /index/_doc/{id}

Java客户端:GetRequest

支持:exists 判断是否存在、_source 按需返回字段,性能极高。

五、MongoDB 自动过期清理机制

依靠TTL 索引 实现自动过期删除。

  • 字段必须是 Date 类型,字符串无效。
  • expireAfterSeconds=0,以字段时间为准过期。
  • 后台线程每分钟扫描一次,存在最多60s延迟。
  • 一个集合只能一个 TTL 索引,不支持复合索引TTL。

六、Kafka 核心知识点(可靠性、延迟、消费瓶颈)

1. 核心架构

生产者 → Broker集群(Partition) → 消费者组

一个分区同一消费组只能一个消费者消费,实现并发与负载均衡。

2. 生产者可靠性三参数

  • acks=0:不等待,不可靠
  • acks=1:leader写入成功即返回(默认)
  • acks=all(-1) :主从全部写入成功才返回,金融最高可靠,搭配重试

3. 消息丢失三大场景与解决

  • 生产者:acks配置低、无重试 → 改为 acks=all+重试
  • Broker:副本少、宕机丢数据 → 多副本集群
  • 消费者:自动提交offset,业务未处理完就位移提交 → 关闭自动提交,业务成功手动提交

4. 重复消费与幂等

重试、rebalance、offset重复提交都会导致重复消费;唯一业务ID+数据库唯一索引/Redis幂等标记解决。

5. 分区作用

提升吞吐量、支持并发消费;相同key哈希到同一分区保证局部有序。

七、Kafka 消费延迟堆积原因 + 解决方案

  • offset自动提交过早:拉取即提交,业务卡顿造成延迟 → 手动提交
  • 分区数不足:分区数决定最大并发,线程再多无效 → 扩容分区、横向部署实例
  • 消费内部慢IO、慢SQL、同步阻塞 → 异步线程池、批量操作
  • 生产流量瞬时暴增 → 限流、削峰、批量消费
  • Broker负载高、网络卡顿 → 集群监控优化
  • 频繁rebalance → 调大心跳、会话超时,减少重启

八、单节点Jar消费瓶颈(CPU内存空闲但消费慢)

根本原因:Kafka原生单线程poll模型,拉取和业务串行,硬件没瓶颈,模型瓶颈。

优化方案(全套生产)

  1. 调大批量参数:max.poll.records、fetch.min.bytes、fetch.max.wait.ms,减少网络轮询
  2. 拉取线程与业务线程池解耦:主线程只poll,业务丢队列异步处理,处理完统一批量提交offset
  3. 批量DB、批量Redis操作,消除单条IO阻塞
  4. 合理设置max.poll.interval.ms,避免批量处理超时rebalance
  5. 超大流量拆分topic、本地队列削峰

九、Redis 分布式锁(面试满分版)

1. 错误方案

setnx + expire 非原子,宕机死锁

2. 正确单机加锁(原子)

SET lock key uuid NX EX 30

3. 正确解锁(Lua脚本防误删)

必须校验当前value是自己的锁再删除,防止过期后误删别人锁。

4. 核心坑点

  • value必须唯一(UUID/线程ID)
  • 业务超时 > 锁超时,需要续期
  • 原生锁不可重入
  • 主从切换存在锁失效风险

5. 生产首选 Redisson

可重入、看门狗自动续期、公平锁、读写锁、集群锁,解决所有原生问题。

十、Redis 缓存与数据库一致性

1. 主流方案(推荐)

先更新数据库,再删除缓存,并发冲突概率最低。

2. 不推荐

先删缓存再更库,高并发极易脏数据。

3. 强一致方案

延时双删、分布式锁串行读写、缓存短期过期兜底。

4. 失败兜底

MQ重试删除、定时任务刷新缓存。

十一、Redis集群单节点CPU过高原因

  1. 热点key槽位倾斜(最常见)大量请求打一个节点
  2. 大key频繁全量遍历 hgetall、lrange 0 -1
  3. 高危慢命令 keys *、大量pipeline
  4. 主从全量同步、RDB/AOF持久化开销
  5. 批量key哈希集中单节点

十二、大key频繁查询优化

  • 拆分大key,禁止全量查询命令
  • 按需字段查询、分页查询
  • JVM本地二级缓存(Caffeine)减压
  • 后台预热、数据压缩
  • 集群读写分离分摊压力

十三、固定热点key落在同一节点如何打散

核心:单个key哈希固定,无法拆分,只能业务多key打散

  1. 随机后缀分片
  2. HashTag 自定义槽位
  3. 业务ID取模分片
  4. 本地缓存拦截热点流量

十四、Redis ZSet 提现排行榜(TOP5000)

1. 设计规范

member=用户ID,score=金额×100(整数,杜绝浮点丢失)

2. 核心命令

ZINCRBY累加金额、ZREVRANGE倒序查榜、ZREVRANK查排名

3. 容量控制

ZREMRANGEBYRANK 5000 -1 截断榜单,只保留前5000

4. 集群热点打散

HashTag多榜单分片

十五、排行榜分页分到5001无数据问题(重点压轴)

问题根源

截断后Redis只存0~4999名,5000名之后已删除,分页查不到。

四种完整解决方案(全覆盖)

  1. 冷热分离(生产最优) TOP5000走Redis,offset≥5000自动降级查询MySQL冷数据分页。
  2. 分数阈值过滤 不删数据,记录第5000名最低分,按分数筛选榜单,避免截断丢数据。
  3. Lua原子维护榜单容量 累加+判断数量+自动截断,并发安全,永远稳定5000条。
  4. 并列名次兼容方案 不按固定条数截断,按临界分数保留,防止并列用户被误删。

十六、RocketMQ 全套可靠性、有序性、一致性

1. 消息不丢失(三层保障)

  • 生产者:禁止sendOneWay、同步发送、失败重试、事务消息半消息机制
  • Broker:一主多从副本、同步刷盘、CommitLog持久化
  • 消费者:业务成功才返回SUCCESS、异常重试、多次失败死信兜底

2. 有序性

队列内有序、同一hashKey进同一队列;单队列单消费者保证有序;全局有序需单队列Topic。

3. 一致性

事务消息:半消息落库→执行本地事务→提交/回滚→Broker定时回查,保证库和消息原子一致。

十七、MySQL 高可用:可用性 & 可靠性区别

  • 可用性:服务不中断,靠主从切换、读写分离、集群冗余保证对外可用。
  • 可靠性:数据不丢失,靠刷盘策略、半同步复制、多副本、备份恢复保证数据安全。

十八、锁体系终极区分(悲观锁/乐观锁/排他锁/分布式锁)

  • 排他锁X锁:InnoDB底层写锁,是悲观锁的实现。
  • 悲观锁:提前上锁阻塞,适合写并发高,单机数据库。
  • 乐观锁:版本号校验、无锁重试,读多写少。
  • 分布式锁:跨JVM、跨服务全局锁,解决微服务并发竞争。