一、业务金额一致性如何保证(全套生产方案)
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模型,拉取和业务串行,硬件没瓶颈,模型瓶颈。
优化方案(全套生产)
- 调大批量参数:max.poll.records、fetch.min.bytes、fetch.max.wait.ms,减少网络轮询
- 拉取线程与业务线程池解耦:主线程只poll,业务丢队列异步处理,处理完统一批量提交offset
- 批量DB、批量Redis操作,消除单条IO阻塞
- 合理设置max.poll.interval.ms,避免批量处理超时rebalance
- 超大流量拆分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过高原因
- 热点key槽位倾斜(最常见)大量请求打一个节点
- 大key频繁全量遍历 hgetall、lrange 0 -1
- 高危慢命令 keys *、大量pipeline
- 主从全量同步、RDB/AOF持久化开销
- 批量key哈希集中单节点
十二、大key频繁查询优化
- 拆分大key,禁止全量查询命令
- 按需字段查询、分页查询
- JVM本地二级缓存(Caffeine)减压
- 后台预热、数据压缩
- 集群读写分离分摊压力
十三、固定热点key落在同一节点如何打散
核心:单个key哈希固定,无法拆分,只能业务多key打散
- 随机后缀分片
- HashTag 自定义槽位
- 业务ID取模分片
- 本地缓存拦截热点流量
十四、Redis ZSet 提现排行榜(TOP5000)
1. 设计规范
member=用户ID,score=金额×100(整数,杜绝浮点丢失)
2. 核心命令
ZINCRBY累加金额、ZREVRANGE倒序查榜、ZREVRANK查排名
3. 容量控制
ZREMRANGEBYRANK 5000 -1 截断榜单,只保留前5000
4. 集群热点打散
HashTag多榜单分片
十五、排行榜分页分到5001无数据问题(重点压轴)
问题根源
截断后Redis只存0~4999名,5000名之后已删除,分页查不到。
四种完整解决方案(全覆盖)
- 冷热分离(生产最优) TOP5000走Redis,offset≥5000自动降级查询MySQL冷数据分页。
- 分数阈值过滤 不删数据,记录第5000名最低分,按分数筛选榜单,避免截断丢数据。
- Lua原子维护榜单容量 累加+判断数量+自动截断,并发安全,永远稳定5000条。
- 并列名次兼容方案 不按固定条数截断,按临界分数保留,防止并列用户被误删。
十六、RocketMQ 全套可靠性、有序性、一致性
1. 消息不丢失(三层保障)
- 生产者:禁止sendOneWay、同步发送、失败重试、事务消息半消息机制
- Broker:一主多从副本、同步刷盘、CommitLog持久化
- 消费者:业务成功才返回SUCCESS、异常重试、多次失败死信兜底
2. 有序性
队列内有序、同一hashKey进同一队列;单队列单消费者保证有序;全局有序需单队列Topic。
3. 一致性
事务消息:半消息落库→执行本地事务→提交/回滚→Broker定时回查,保证库和消息原子一致。
十七、MySQL 高可用:可用性 & 可靠性区别
- 可用性:服务不中断,靠主从切换、读写分离、集群冗余保证对外可用。
- 可靠性:数据不丢失,靠刷盘策略、半同步复制、多副本、备份恢复保证数据安全。
十八、锁体系终极区分(悲观锁/乐观锁/排他锁/分布式锁)
- 排他锁X锁:InnoDB底层写锁,是悲观锁的实现。
- 悲观锁:提前上锁阻塞,适合写并发高,单机数据库。
- 乐观锁:版本号校验、无锁重试,读多写少。
- 分布式锁:跨JVM、跨服务全局锁,解决微服务并发竞争。