一、设计目标:为什么“快”和“稳”必须同时追
在真实业务里,“快”(吞吐、延迟)与“稳”(可维护、可演进)往往拉扯。高性能带来的复杂度不应成为维护负担,而可维护的抽象也不应牺牲关键路径。可落地的目标是:
-
可预测性能:接口在 SLA 下延迟稳定,P95/P99 可控。
-
渐进式优化:从数据结构到 JVM,可以按需“开盖”。
-
可演进架构:可复用、可替换、可灰度,避免一次性设计。
-
可观测闭环:度量—分析—优化—回归,形成循环。
接下来从语言细节到工程化,建立“从里到外”的实践路径。
二、语言层面:写出“易优化”的代码
2.1 不滥用语法糖,写给编译器“看得懂”的代码
-
优先使用显式类型与清晰控制流,减少过度链式/流式造成的匿名类、捕获变量开销。
-
谨慎使用
Stream
在热点路径;数据量小、可读性优先时用之;高吞吐循环内仍建议 for/foreach。
2.2 避免不必要的装箱与临时对象
-
基元类型优先(
int/long/double
等);集合需要时考虑TIntArrayList
等特化结构(或引入 fastutil)。 -
避免在循环中频繁创建
String
/BigDecimal
等临时对象;字符串拼接用StringBuilder
。
2.3 Switch 表达式、Record、Sealed Class 的价值(JDK 17+)
-
Record 适合不可变 DTO,减少样板代码,天然利于缓存与并发安全。
-
Sealed Hierarchy 明确分支,结合
switch
表达式减少if-else
链,提高可读性与 JIT 内联机会。
三、集合与算法:用对数据结构,事半功倍
3.1 HashMap、ConcurrentHashMap 的典型误用
-
容量规划:避免
resize
抖动。估算元素数n
,初始容量设为n / 负载因子
(默认 0.75),四舍五入为 2 的幂。 -
只读场景可用
Collections.unmodifiableMap
或开源的 不可变 Map,减少并发风险。
3.2 List vs LinkedList
-
几乎总是用 ArrayList。
LinkedList
的链式存储带来更差的 CPU 缓存局部性与额外节点对象。
3.3 LRU/LFU 缓存
-
本地缓存优先选
Caffeine
:高命中、写入竞争优化、支持基于大小/时间的淘汰策略。
3.4 排序、去重与 TopK
-
大数据 TopK 使用 最小堆 或 Count-Min Sketch;避免先全量排序后截断造成的 O(n log n) 浪费。
四、并发与线程池:高吞吐不等于多线程泛滥
4.1 线程池的三大纪律
-
用途隔离:将阻塞 I/O、CPU 计算、第三方调用分池管理,避免资源争抢。
-
参数可观测可调:核心/最大线程数、队列长度、拒绝策略并暴露指标(Prometheus/Actuator)。
-
拒绝策略兜底:生产通常采用
CallerRunsPolicy
或自定义降级,避免静默丢弃。
4.2 CompletableFuture 的“对与错”
-
正确:组合/编排、并行汇聚、异常管道化,避免同步
get()
。 -
误区:默认 ForkJoinPool 竞争严重;建议显式指定线程池。
4.3 锁的策略:避免“无脑 synchronized”
-
读多写少用
StampedLock
的乐观读,或ReadWriteLock
。 -
尽量缩小临界区、避免锁内调用外部方法。
-
对热点数据采用 分段锁/分桶(striping)。
五、JVM 与 GC:把“看不见”的调快
5.1 分代调优基础
-
新生代越大,Minor GC 越少;但过大可能拖慢 Minor 时间。
-
对象存活期短的应用(电商下单、风控)适当增大 Eden。
-
-Xms = -Xmx
减少伸缩抖动;压测后再定比例。
5.2 垃圾收集器的选择(JDK 11/17)
-
G1:大多数服务可选,暂停时间可控,分区回收。
-
ZGC / Shenandoah:超低暂停(< 10ms),适合大堆/低延迟;权衡是吞吐与可用性需求。
常用参数示例:
5.3 逃逸分析与标量替换
-
JIT 会将仅在方法内使用的短生命周期对象“标量化”,避免堆分配与同步开销。
-
建议:热点路径多用局部变量,减少共享状态。
六、I/O 与序列化:吞吐的“隐形天花板”
6.1 BIO、NIO 与异步
-
高并发网络服务建议使用 Netty 或 Spring WebFlux 实现非阻塞 I/O。
-
文件 I/O:批量/顺序写优于随机写;零拷贝(sendfile) 技术适合大文件传输。
6.2 序列化选择
-
JSON 便捷但体积与 CPU 开销高;内部服务优先选择 Protobuf/FlatBuffers/Kryo。
-
千万级 QPS 的链路严禁频繁 JSON 解析——预热对象映射器、复用
ObjectMapper
。
七、响应式与背压:应对流量不均的利器
7.1 Project Reactor 背压模型
-
通过
Flux
/Mono
链式处理,天然支持背压,避免上游“淹没”下游。 -
onBackpressureBuffer/drop/latest
根据业务选型;请求-响应服务可在入口做速率转换。
7.2 何时不建议响应式
-
计算密集且无 I/O 等待时,响应式收益有限;命令式更简单可读。
八、数据库与缓存:把最“贵”的路径变最“短”
8.1 SQL 层面的硬性规则
-
必须命中索引(
EXPLAIN
走起);避免模糊前缀%xx
导致全表扫。 -
批量写入、合并小事务;短事务避免长时间锁等待。
-
分页优化:深分页用“延续标记(seek)”代替
OFFSET
。
8.2 缓存策略三连
-
Key 设计可控:带版本号/业务前缀,方便回收。
-
过期与淘汰:热点分散过期时间(随机抖动),避免缓存雪崩。
-
回源与兜底:缓存穿透时 BloomFilter/短期空值缓存。
九、领域建模与分层:让“可维护”不拖慢“性能”
9.1 分层清晰但不教条
-
Controller 只做 鉴权/入参校验/编排;
-
Service 聚焦 业务规则;
-
Repository 只处理 持久化;
-
DTO/DO/VO 分离使演进成本最小化。
9.2 领域对象不可变
-
聚合根尽量不可变(或最小可变),减少并发条件下的共享状态问题;更利于缓存与重构。
十、异常与日志:少写 try-catch,多写结构化日志
10.1 异常分层
-
业务可预期异常(如参数错误)使用自定义受检异常/错误码;
-
非预期异常转化为统一错误响应并记录上下文。
10.2 日志策略
-
统一
traceId
、spanId
贯穿调用链; -
避免字符串拼接日志参数,使用占位符;
-
按级别采样:高 QPS 接口对
INFO
/DEBUG
做采样,异常全量。
十一、测试与质量:让性能优化有“安全网”
11.1 单测策略
-
避免“只测 getter/setter”;聚焦 复杂分支与边界。
-
快速失败:业务断言(
assertThrows
)、契约测试(接口兼容)。
11.2 基准测试(JMH)
-
避免微基准陷阱:预热、避免逃逸、消除无用代码。
-
关注 p95/p99,而非平均值。
十二、可观测性:度量—分析—优化的闭环
12.1 度量四象限
-
指标(Metrics):QPS、延迟、错误率、线程池利用率、JVM 内存/GC。
-
日志(Logs):结构化、可检索、带上下文。
-
链路(Trace):跨服务调用可视化。
-
事件(Events):发布、扩容、配置变更与告警。
12.2 关键看板
-
接口延迟分位(P50/P95/P99),线程池拥塞度,GC 暂停时间直方图;
-
热点 SQL 排行与慢查询明细;
-
缓存命中率/穿透/回源比。
十三、部署与回滚:性能优化的“保险丝”
13.1 蓝绿/灰度与特性开关
-
灰度 + 熔断:新版本先在小流量验证性能并观测趋势。
-
特性开关:性能优化代码尽量以开关控制,可快速关闭回退。
13.2 容器与 JVM 亲和
-
容器内请设置 堆/线程/FD 上限;
-
JVM 感知容器资源(JDK 10+ 已默认支持 cgroup),但仍建议显式设置
-Xmx
与池大小,避免误判。
十四、典型“坑位”清单(生产事故高发)
-
默认线程池 + 无限队列 导致 OOM。
-
热点路径频繁创建对象(
BigDecimal
/SimpleDateFormat
非线程安全)。 -
JSON 反序列化缺省值 导致布尔/数值异常。
-
ConcurrentHashMap
里复合操作不原子(先get
再put
的竞态)。 -
缓存雪崩:同刻过期;解决:抖动、预热、双层缓存。
-
深分页 拖垮数据库;解决:seek 模式或游标。
-
默认 ForkJoinPool 争用 导致“看不见的阻塞”。
-
日志过量:高峰期 IO 占满,接口抖动。
-
JIT 暖机期:压测未充分预热,线上前 10 分钟抖动。
-
限流算法实现错误:滑动窗口与漏桶实现不严谨,导致突发漏限。
十五、一个端到端样例:从慢接口到稳定低延迟
背景:支付聚合接口 P99 800ms,偶发超时。
定位路径:
-
指标:线程池队列积压 → I/O 线程与业务线程混用。
-
链路:外部风控服务 200ms+,并发调用未隔离。
-
SQL:账单表深分页,QPS 上来后 RT 抖动。
优化动作:
-
线程池隔离:I/O、外部调用、计算分池;拒绝策略改为
CallerRuns
并告警。 -
并行编排:
CompletableFuture
并行拉取外部数据,显式自定义线程池。 -
缓存与分页:账单分页改“seek”,同时缓存热门账单元数据。
-
降级与兜底:外部风控超时走缓存/默认策略 5 分钟。
-
GC 调整:启用 G1,设置
MaxGCPauseMillis=100
,观察暂停直方图。
结果:P99 从 800ms 降到 220ms,错误率 < 0.05%,线程池拥塞告警消失。
十六、团队落地清单(可直接纳入代码规范/CR 模板)
-
热点路径禁止深层级
Stream
/map
/flatMap
链式;必要时改 for。 -
Map/List 初始容量按估算值设置,避免频繁扩容。
-
线程池必须命名、隔离、可观测、可调整;禁止无限队列。
-
CompletableFuture
必须显式线程池,链路必须处理异常。 -
每个接口必须暴露基础指标(QPS/RT/错误率);每个线程池暴露活跃/队列/拒绝计数。
-
数据访问层必须有慢 SQL 监控与索引审计。
-
深分页禁止;提供游标/seek 与导出任务。
-
缓存 key 规范化、过期抖动、穿透/击穿/雪崩预案齐备。
-
关键优化点具备 特性开关 与可回滚策略。
-
基准测试与回归压测必须纳入 CI/CD,变更前后对比分位延迟。
结语
性能优化不是“某几条魔法参数”,而是一套可度量、可复用、可回滚的工程化方法。本文从代码层、数据结构、并发、JVM、I/O、响应式、数据库、缓存、观测、部署全链路展开,目的在于让团队把“快”与“稳”同时握在手里。