01 100道面试题

vvEcho 2026-01-27 16:40:03
Categories: > Tags:

1.java后端基础(20道)

1.1 jvm内存结构

JVM 分为堆、栈、方法区、程序计数器;对象主要在堆中分配,GC 负责内存回收。
JVM 我重点关注的是堆和GC行为。
堆分新生代和老年代,新生代对象创建频繁,如果GC不合理会直接影响系统延迟。
在交易所撮合和行情服务这种长时间运行、高并发、低延迟的系统里,我们更关注STW时间是否可控,所以生产环境偏向G1。
同时会避免在撮合主路径创建大对象,减少晋升老年代概率,防止FullGC抖动。

tips:SYW是指在垃圾回收时暂停所有业务线程的时间,会直接导致系统卡顿,高并发和低延迟系统必须重点控制

1.2 G1为什么适合交易系统?ZGC不行吗?

G1最大优势是可预测停顿时间。
交易系统对 TPS 不如对 P99 延迟敏感,一次 Full GC 卡 1 秒比 QPS 下降更致命。
G1把堆拆成 Region,可以并行回收、按收益优先回收垃圾多的 Region。
在撮合和 WebSocket 推送这种实时系统里,这种设计非常重要

ZGC 追求极致低停顿,通过并发压缩实现近乎无感 GC,但其 CPU 开销和工程成熟度仍在验证阶段;而 G1 在低延迟与稳定性之间取得了最佳平衡,是当前交易系统最稳妥的默认选择

1.3 对象什么时候进入老年代?

常见是对象多次 Minor GC 存活,或者大对象直接进入老年代。
在我们系统里,像订单快照、行情聚合对象,如果设计不当就可能变成“隐形大对象”。
所以会通过对象复用、拆字段、减少生命周期来控制老年代压力

tips:超过 1MB 的对象,基本就算大对象,几十 KB 起就要警惕(在高频创建场景);在G1模式下,对象大小 ≥ 一个 G1 Region 的 50%,就会被认为是大对象会直接进入老年代;

1.4 synchronized 在JVM中是怎么实现的?

synchronized 基于对象头和Monitor实现,经历偏向锁、轻量级锁到重量级锁的升级。
在低竞争场景成本很低,但高竞争会退化成 OS Mutex(内核态互斥锁)。
所以在撮合这种热点逻辑里,我们不依赖 synchronized,而是直接用单线程模型保证一致性

1.5 volatile 能解决什么,不能解决什么?

volatile 只能保证可见性和一定的有序性,不能保证复合操作的原子性。
我一般只用它做状态标记,比如服务是否可用、是否需要降级。
涉及资金、订单这种强一致场景,绝不会只靠 volatile

1.6 HashMap 为什么线程不安全?

核心问题在并发扩容和链表/红黑树转换过程中可能产生死循环或数据覆盖。
所以在并发环境中统一使用 ConcurrentHashMap,避免隐式风险

1.7 ConcurrentHashMap 为什么快?

JDK8 后是 CAS + synchronized,锁粒度在桶级别。
读操作无锁,写冲突才加锁,非常适合高并发读多写少场景,比如行情缓存

1.8 ThreadLocal 的使用场景和坑?

用于 traceId、用户上下文。
最大的坑是线程池 + ThreadLocal,如果不remove 会导致上下文串线甚至内存泄漏。
我们线上排查过类似问题,所以现在是强制规范

1.9 equals / hashCode 为什么必须一起重写?

Hash 容器依赖 hashCode 定位桶,equals 决定是否相等。
在订单、业务 Key 场景如果不一致,会导致数据重复或查不到

1.10 igDecimal 在金融系统中的使用原则?

所有资金、价格、数量统一 BigDecimal。
明确scale和roundingMode,避免隐式精度丢失。
撮合、结算、链上金额转换都严格控制。

1.11 Java 中的内存泄漏常见场景?你怎么排查?

Java 的内存泄漏本质不是“内存回收不了”,而是对象不再使用但仍被引用。
常见场景包括:
静态集合长期持有对象
ThreadLocal 在线程池中未清理
Listener / 回调未反注册
我们线上主要关注 ThreadLocal + 线程池 和 缓存无限增长 两类问题。
排查时一般先看 GC 日志是否频繁 Full GC,再通过 MAT / JProfiler 分析堆中大对象和引用链。

1.12 Java 中的深拷贝和浅拷贝有什么区别?你在哪用过?

在交易系统里,快照语义非常重要。
比如撮合过程中,如果直接把订单对象引用传给下游,很容易被并发修改。
所以在订单快照、撮合事件中,我们会用深拷贝或不可变对象,确保下游消费到的是一致状态

1.13 Java 为什么不推荐 finalize?你们怎么做资源释放?

finalize 执行时机不确定,会增加GC负担,JDK 9 之后也不推荐使用。
我们在项目里统一使用:
try-with-resources
明确的close生命周期
像MQ Producer、Netty Channel 都必须在明确时机释放,避免依赖GC

1.14 Java 中异常体系你是怎么设计的?

我们业务中更偏向 运行时异常 + 业务错误码。
Checked Exception 在复杂链路中会污染方法签名,不利于扩展。
比如撮合失败、余额不足,本质是业务异常,不是系统异常。
所以我们统一封装业务异常,结合错误码向上透传

1.15 Java 序列化有哪些问题?你们是怎么做的?

Java 原生序列化有三个问题:
1️⃣ 性能差
2️⃣ 体积大
3️⃣ 存在反序列化安全风险

在 MQ、RPC 场景中,我们统一使用 JSON / Protobuf,
严禁 Serializable直接上生产链路

1.16 Java 中的不可变对象有什么好处?

不可变对象在并发场景下非常有价值。
在行情、配置、交易对参数等场景,我们更倾向不可变对象:
无需加锁
天然线程安全
减少并发 Bug
复杂系统里,减少状态变化本身就是优化

1.17 Java 中的反射为什么慢?你们怎么用?

反射慢主要因为:
无法内联
有安全检查

我们只在测试工具、框架层使用反射,比如京东项目里的单测辅助工具。
撮合、交易核心路径 禁止反射

1.18 Java 的类加载机制?双亲委派的意义?

安全、避免重复加载,双亲委派保证核心类不会被篡改。
在金融和交易系统里,这是安全基线。
即使自定义 ClassLoader,也不会破坏 JDK 核心类的加载顺序

1.19 Java 中的 SPI 机制了解吗?适合用在什么地方?

服务发现、解耦实现
SPI 适合低频、非核心路径的可插拔能力,比如策略扩展、插件化组件。
在交易核心链路不适合,因为类加载和反射开销不可控

1.20 JIT 编译对高并发系统的影响?

JIT会把热点方法编译成本地代码。
对撮合、行情这种长期运行的服务非常有利。
这也是为什么我们更倾向 常驻服务 + 稳定流量,而不是频繁重启

2.并发 / 中间件 / 分布式(20题)

2.1 你在系统中是如何设计线程模型的?

我们的原则是:线程模型必须贴合业务一致性要求。
比如撮合引擎是单交易对单线程,保证强一致性;
行情推送、MQ 消费属于 IO 或计算型任务,采用线程池并行处理。
通过这种方式,把“必须串行”的地方和“可以并发”的地方彻底分离

2.2 为什么撮合引擎要用单线程?不会成为瓶颈吗?

撮合的瓶颈不是 CPU,而是一致性复杂度。单线程可以避免锁竞争、死锁、ABA 等问题。
扩展性靠交易对维度拆分,不是靠堆线程。这在真实交易所是非常成熟的设计

2.3 线程池参数你是如何设置的?

我们不会套公式,而是按场景来:
IO 型:线程数 > CPU
计算型:线程数 ≈ CPU

所有线程池统一要求:
有界队列
明确拒绝策略
可观测(队列长度、活跃线程)
防止流量突刺直接拖垮JVM

2.4 线程池队列满了怎么办?

队列满本质是系统过载信号,我们不会无脑 CallerRuns。
对交易类请求直接失败并告警;
对非核心任务(报表、统计)可以降级或丢弃。核心原则是:保护核心链路

2.5 Redis 在你们系统中主要承担什么角色?

Redis 在我们系统中不是“万能存储”,而是三类角色:
1️⃣ 加速读(行情、配置)
2️⃣ 分布式锁(支付、防重复)
3️⃣ 排序结构(排行榜)

所有最终状态一定落 MySQL,Redis挂了可以降级,但交易不能错

2.6 你是如何实现分布式锁的?有哪些坑?

分布式锁最基本是 SET NX + 过期时间。
关键不是“能不能加锁”,而是锁失效怎么办。
所以我们用 Redisson 的看门狗机制,并且锁只用于短事务,避免长时间占用。在资金、撮合核心路径尽量避免分布式锁

2.7 Redis 挂了会发生什么?你们怎么应对?

Redis 挂了后:缓存失效,锁不可用
我们的设计是:核心交易不依赖 Redis 最终状态
必要时直接走 DB,行情类服务可直接降级
Redis是加速器,不是地基

2.8 MQ在你们系统中解决什么问题?

MQ 主要解决三件事:
1️⃣ 撮合和结算解耦
2️⃣ 削峰填谷
3️⃣ 异步扩展能力

撮合只负责产生成交事件,后续资金、统计、风控通过 MQ 扩展,不影响撮合性能

2.9 如何保证MQ消息不丢?

我们从两端保证:
生产端:同步发送 + ACK
消费端:业务成功才提交 offset

失败进入重试或死信队列,任何“发出去就不管”的MQ都是不合格的设计

2.10 MQ重复消费你是怎么处理的?

MQ 语义是至少一次,所以业务层必须幂等
常见做法:
业务唯一key
DB唯一索引
Redis去重

京东和交易所场景下我都用过,尤其是在新老系统双写阶段

2.11 你如何保证分布式系统中的数据一致性?

我们不追求强一致覆盖全链路,而是分层处理一致性。
撮合、余额扣减属于强一致,必须串行;
统计、报表、风控是最终一致,通过MQ异步对齐。
通过这种方式,在保证正确性的同时不牺牲性能

2.12 你怎么看分布式事务?在项目中怎么用?

分布式事务在高并发系统里成本很高。
我们更倾向本地事务 + 消息最终一致性。
比如撮合落成交记录,本地事务成功后发MQ,下游失败可以重试或补偿,而不是阻塞主链路

2.13 你了解哪些分布式事务方案?为什么不用 2PC?

2PC、TCC、Saga
2PC 阻塞严重,不适合高并发交易系统。
TCC 侵入性强,开发成本高。
我们实际使用的是 Saga / 事件驱动补偿模型,允许短时间不一致,但必须最终可恢复

2.14 如何保证 MQ 消息的顺序性?

顺序是业务维度问题。
我们以订单号/交易对作为分区 key,同一key的消息进同一分区、同一消费者。
顺序只保证在“需要顺序”的粒度上

2.15 消息堆积你是怎么处理的?

首先区分是生产快还是消费慢。
消费慢:
临时扩容消费者
提升单批处理能力

生产快:限流/削峰
京东订单消息和交易所行情高峰期都遇到过这种问题

2.16 延迟消息你用过吗?适合什么场景?

延迟消息适合状态超时检查
比如:
订单未支付自动关闭
链上充值长时间未确认

相比定时任务,延迟消息更精确、更易扩展

2.17 幂等设计你是如何系统性考虑的?

幂等不是“补丁”,而是设计前置条件。
每个业务操作必须有:
唯一业务 ID
明确的状态机

重复请求只可能:
返回相同结果
被拒绝

在支付、下单、结算里尤为关键

2.18 分布式ID你用过哪些方案?

UUID、Snowflake
高并发系统我们不用UUID,索引不友好。
更偏向Snowflake类方案,保证趋势递增,利于DB索引和分页。
京东内部也有类似方案

2.19 配置中心你是怎么用的?

配置中心用于:
交易对参数
风控阈值
开关控制

所有配置必须支持:
热更新
回滚
避免频繁重启影响交易

2.20 注册中心挂了会怎么样?

注册中心不可用时,已建立的连接仍然可用。
所以关键是:
客户端本地缓存
服务列表容错

不能让注册中心成为单点

3 项目深挖(20题)

3.1 你们 CEX 系统的整体架构是怎样的?

整体是微服务架构,核心拆为四块:
Exchange:撮合引擎,强一致
Market:行情服务,高并发读
Wallet / Settlement:资金结算
Admin / Risk:风控与管理

撮合只产生成交事件,不直接改资金,通过MQ解耦结算,保证撮合低延迟

3.2 撮合引擎为什么一定要和资金解耦?

撮合是整个系统延迟最敏感的模块。
任何DB/RPC/链上操作都会放大尾延迟。

所以撮合只负责:
价格撮合
成交数量计算

成交结果通过 MQ 发送给结算系统,即使结算慢,也不会影响撮合吞吐

3.3 撮合引擎内部的数据结构是怎样的?

每个交易对维护独立订单簿:
买卖盘分别用红黑树
Key 是价格
Value 是同价位 FIFO 队列

这样既能快速找到最优价,又能保证价格优先、时间优先

3.4 市价单和限价单在实现上最大的区别?

限价单可能部分成交,剩余进入订单簿;市价单永远不入簿,只按当前深度吃单。

我们在市价单上做过优化,
利用红黑树减少无效遍历,将撮合耗时从15ms降到 2ms

3.5 高并发下如何保证撮合结果不乱、不重?

单交易对单线程撮合是关键。不同交易对并行,同一交易对严格串行。
这样可以:不加锁,不死锁,不ABA
一致性由线程模型保证,而不是靠锁

3.6 撮合服务如果宕机,怎么恢复?

快照 + 重放。

撮合本身是无状态的:
订单簿定期快照
成交事件持久化在 MQ

重启后:
加载快照
重放未处理的成交消息

不会丢单,也不会多成交

3.7 行情数据是如何产生和推送的?

撮合 → 行情聚合 → 推送

行情源头来自撮合成交事件。
Market 服务订阅成交流,
聚合盘口、K 线、Ticker。

WebSocket 推送采用:
新用户先快照
后续只推增量
控制延迟和带宽

3.8 WebSocket 如何防止慢客户端拖垮系统?

限流 + 主动断开
每个连接都有发送缓冲区上限。一旦积压:丢弃低优先级消息或直接断连

行情是“尽力而为”,不能影响整体系统稳定性

3.9 链上充值你们是怎么做的?

监听链上事件
使用 web3j:启动时扫历史区块补数据,实时订阅新区块,解析 ERC20 / ERC721 Transfer 日志,将链上状态同步到链下账户体系

3.10 链上回滚你们如何处理?

链上交易先标记 pending,达到N个确认数后才 final。

如果检测到 blockHash 不一致:回滚链下状态,重新扫描。所有充值都必须“可重放、可回滚”

3.11 NFT铸造流程你是怎么设计的?

链下发起,链上执行
铸造流程分三步:
1️⃣ 链下校验 & 业务锁
2️⃣ 调用链上合约
3️⃣ 监听事件确认结果

链上是最终状态,链下失败可通过事件重放恢复

3.12 NFT 支付重复下单如何避免?

支付前通过:
分布式锁,订单状态机

保证同一订单只能支付一次。即使 MQ / 回调重复,也不会多扣款

3.13 订单状态机你是怎么设计的?

订单状态只能单向流转:
CREATED → PAID → FINISHED / CLOSED

每次状态变更都校验当前状态,并通过乐观锁防并发写冲突

3.14 京东项目中你是如何落地 DDD 的?

按业务能力拆域:
行为域
订单域
佣金计算域
价保域

每个域内模型自洽,通过事件而不是直接调用交互

3.15 为什么要用策略模式?

佣金规则复杂且变化频繁。使用策略模式把规则拆散,新规则只新增实现,不改原逻辑。这对长期维护非常关键

3.16 京东迁移新老系统时如何做到 0 事故?

上游双写消息,新老系统并行消费。
消息携带唯一 ID,
下游做幂等处理。

确认新系统稳定后再切流

3.17 你们是如何保证单测覆盖率的?

工具 + 规范
我们补齐了私有方法测试工具,引入 JMockit 覆盖复杂分支。

两个人一个月把覆盖率提升到 87%,顺利通过合规审查

3.18 高并发下你们最怕哪类 Bug?

最怕的是:
重复扣款
多成交 / 少成交

所以所有资金、订单逻辑:
明确状态
明确幂等
明确补偿

3.19 你觉得你项目中最难的点是什么?

最难的是在 一致性、性能、可恢复性 之间取平衡。不是追求极致快,而是可控、可恢复

3.20 如果现在让你重构一次撮合系统,你会改什么?

核心思路不变:单交易对单线程,撮合与结算解耦

会补充更多:可观测性;自动降级;更完善的回放工具

4 系统设计 / 场景题(20题)

4.1 让你设计一个现货交易系统,你会怎么拆模块?

我会先按一致性和并发特性拆模块:

Exchange:撮合,强一致,单线程模型
Market:行情,高并发读
Wallet / Settlement:资金结算
Risk:限额、限频、风控
强一致模块尽量少,高并发模块通过缓存和异步扩展

4.2 为什么撮合一定不能直接操作资金?

撮合是延迟最敏感的模块。一旦引入 DB、RPC 或链上操作,尾延迟会被无限放大。

所以撮合只生成成交事实,资金变化通过MQ异步处理,即使结算慢,也不影响撮合吞吐

4.3 如果交易量突然暴涨 10 倍,你系统哪里最先扛不住

通常最先扛不住的是行情和推送链路,而不是撮合本身。

应对方式:
行情降级(减少频率、字段)
WebSocket 连接限流
核心交易优先级最高

交易系统一定要有“止损阀”

4.4 如何设计一个高可用的撮合服务?

撮合服务本身是无状态的:
订单簿内存
定期快照
成交事件持久化

任意节点宕机:
重启加载快照
重放成交事件

可用性靠可恢复性,不是堆机器

4.5 你如何设计订单系统的状态流转?

订单状态只能单向流转,
每次流转校验当前状态。

并发场景下结合:
乐观锁
版本号

防止重复扣款或乱序更新

4.6 Redis 在系统设计中你会放在哪一层?

Redis 只能作为加速层:
行情
配置
临时状态

所有最终状态必须落DB。任何“Redis就是主存”的设计都很危险

4.7 如果 Redis 整个集群不可用,系统如何自保

Redis 不可用时:
核心交易直接走DB
行情服务降级

排行榜等非核心功能关闭
系统设计必须区分:能慢 vs 不能错

4.8 MQ 堆积严重你会怎么处理?

扩容、限流
先判断原因:
消费慢 → 扩容消费者
生产快 → 限流 / 削峰

同时监控:积压长度,消费延迟
必须做到“可观测 + 可干预”。

4.9 如何设计一个幂等的下单接口?

下单必须携带clientOrderId。
服务端:
DB唯一索引
状态机校验

重复请求只能返回相同结果,绝不能生成新订单

4.10 如何设计一个安全的充值提现系统?

多重校验。
充值:

提现:

资金系统必须:

4.11 如何避免超卖或资产为负?

冻结、校验

下单前先冻结资产,撮合只使用可用余额。
所有扣减必须校验版本号,出现异常直接失败而不是修数据

4.12 高并发下如何设计分页和导出?

异步、分批
大数据量导出必须异步,分页查询并行化处理,中间结果落临时存储。
平安项目中把导出从3分钟优化到40秒

4.13 如何设计一个风控系统?

规则 + 实时

风控分两层:
实时风控:限额、限频
离线风控:行为分析

风控永远不能阻塞交易主链路

4.14 如何做系统限流?

令牌桶模型
不同接口不同限流策略:
下单:严格
查询:宽松

限流优先在入口做,防止请求打到核心服务

4.15 如何设计系统的灰度发布?

分批切流
灰度核心是:小流量验证 + 可回滚

京东项目中通过消息双写保证新老系统共存

4.16 系统日志你重点关注哪些?

可追踪,每笔交易必须能串起完整链路:

4.17 如何设计系统的监控指标?

延迟、错误率

重点监控:

4.18 如果发现成交价格异常波动,你会怎么排查?

数据回溯,第一时间冻结交易对。
回放撮合日志,检查是否异常订单或行情输入。

交易系统必须支持历史回放,且可复现

4.19 如何设计一个可回放的系统?

事件驱动。

核心状态由事件驱动。事件不可变、可持久化。

任何状态都能通过事件重建,这是金融系统的底线能力

4.20 你如何评价一个系统设计的好坏?

我会看三点:
1️⃣ 出问题能不能恢复
2️⃣ 压力下会不会失控
3️⃣ 复杂度是否可维护

好系统不是“从不出错”,而是出错不致命

5.Web3 / 区块链专项(20题)

5.1 你如何理解“链上最终一致性”和 Web2 的区别?

确认数 + 不可逆性
Web2 一致性靠事务和锁,链上一致性靠区块确认数。

链上交易不是“立即成功”,而是 pending → confirmed → final。

所以链下系统必须支持:回滚,重放,延迟确认

5.2 为什么监听链上事件不能只靠 WebSocket?

WS 只适合实时性,不适合完整性。
节点断线、重连都会丢事件。
正确方式是:

两者结合才能保证不漏数据

5.3 ERC20 Transfer 事件你是怎么解析的?

日志解析

通过 web3j 解析 logs:
topics[0] 是 Transfer 签名
topics[1]/[2] 是 from / to
data 是 amount
解析后再映射到链下账户体系

5.4 USDC 为什么适合做结算币?

稳定、规范

USDC:法币锚定 + 小数位固定 + 审计透明

比ETH波动风险小,更适合做交易、预测市场结算

5.5 链上充值如何防止重复入账?

以 txHash + logIndex 作为唯一键。
DB 层做唯一索引。

即使事件重复扫描,也只入账一次

5.6 链上 Reorg 会对你系统产生什么影响?

状态回滚。
Reorg 会导致:

所以所有链上数据:

5.7 提现为什么一定要走链下审批?

提现是资金外流的唯一通道。
必须:风控,人工或多签

不能完全自动化,否则一旦被攻击就是灾难。

5.8 冷热钱包你如何理解?

风险隔离
热钱包:小额 + 高频
冷钱包:大额 + 离线

热钱包余额不足时,由冷钱包定向补充。

5.9 智能合约升级你怎么看?

合约升级必须极度克制。
通常采用:Proxy模式 或新合约迁移

所有升级都要:
公告,时间锁,可审计

5.10 Solidity 中最容易出安全问题的点?

重入、精度

常见问题:重入攻击、精度溢出、授权滥用

所以写合约时:
Checks-Effects-Interactions、权限最小化

5.11 为什么很多逻辑不放在链上?

成本、效率

链上:成本高、延迟大;所以复杂业务逻辑放链下
链上只做:状态结算、关键校验

5.12 预测市场为什么适合用 USDC?

价格直观
预测市场的价格本身就是概率。
用稳定币结算,用户更容易理解赔率和收益

5.13 你如何设计链下 + 链上数据一致性?

事件驱动
链上是最终裁决者。
链下状态必须:

5.14 为什么链下系统必须支持重扫区块?

恢复能力
因为节点、网络、程序都会出问题。
唯一可信的是链上历史。

能重扫,系统才算“活着”

5.15 Gas 费用波动你如何处理?

通过:
动态 Gas Price
延迟执行

在不影响用户体验的前提下控制成本

5.16 多链支持你会怎么设计?

抽象链适配层:
地址,事件,交易

业务逻辑不感知具体链,只关心标准接口

5.17 Web3 系统中最难排查的 Bug 是什么?

链上慢、异步、可回滚,Bug 往往不是“错”,而是“没等到”。

所以必须有完整状态机和超时机制

5.18 Web3 项目如何做风控?

链上 + 链下
链上防合约漏洞,链下防女巫、套利、刷子。
风控永远是多层的

5.19 Web3 后端和传统后端最大的不同?

Web3 后端面对的是:不可信输入、不可控延迟、不可回滚成本
所以设计上更保守、更强调恢复能力

5.20 你为什么适合做 Web3 后端?

我有传统金融级后端的工程经验,同时理解链上系统的不确定性。

能把Web2的稳定性和 Web3的开放性结合起来,这是我最大的优势。