优化思路分层:架构层 → JVM 层 → 代码层 → 数据库层 → 中间件层 → 容器 / 操作系统层 → 监控压测验证,优先解决收益最高的瓶颈,不要盲目提前优化。
核心原则:先定位瓶颈,再优化;压测验证优化效果;保留基准指标
一、性能排查手段(先做这个!)
没有定位就没有优化
压测工具:JMeter、Gatling、k6、JMH(Java 微基准测试,适合方法级)
链路追踪:SkyWalking / Pinpoint / OpenTelemetry,看慢接口、慢 SQL、外部调用耗时
JVM 诊断
线上:
jstat、jmap、jstack、jhat分析:Arthas(阿里,线上神器)、Async-Profiler(火焰图,CPU / 内存 / 锁)
重点看:GC 频率、GC 停顿、线程阻塞、死锁、OOM、CPU 高占比
数据库:MySQL 慢查询日志、Explain、Performance Schema
二、架构层面优化(收益最大)
1. 分层缓存,减少下游压力
本地缓存:Caffeine(推荐,优于 Guava Cache),热点小数据,注意缓存失效、容量控制、避免内存溢出
分布式缓存:Redis,缓存 DB 查询结果、配置、token、热点商品;避免缓存穿透 / 击穿 / 雪崩
穿透:布隆过滤器、缓存空值(设置短过期)
击穿:互斥锁、热点数据永不过期
雪崩:过期时间加随机偏移、多级缓存、限流熔断
缓存设计原则:不要缓存大对象、不要强一致性数据、更新策略统一(更新 DB 同时更新 / 删除缓存)
2. 异步化 & 解耦
非核心链路异步:消息队列 RocketMQ / Kafka / RabbitMQ,削峰填谷
例:下单主流程落库返回,日志、通知、积分、统计丢 MQ 异步消费
线程池规范化:不要直接
new Thread()、不要用 Executors 默认线程池(无界队列风险),自定义 ThreadPoolExecutor,核心参数:核心线程、最大线程、队列、拒绝策略CompletableFuture:并行执行独立 IO 任务(多接口并行调用),注意线程池隔离,防止共用线程池被拖垮
3. 限流、熔断、降级(高可用,防止雪崩)
Sentinel / Resilience4j:
限流:QPS 限流、令牌桶 / 漏桶
熔断:下游持续超时直接熔断,快速失败
降级:高峰期关闭非核心功能(推荐、统计、日志)
4. 分库分表 & 读写分离
读多写少:主库写,从库读;Spring 读写分离路由
单表过大(千万级):分表(水平分表按 uid/orderId 哈希);分库隔离业务
注意:跨库 join、分页、排序、分布式事务成本很高,尽量业务规避
5. 服务拆分 & 资源隔离
核心业务、非核心业务服务 / 线程池隔离,互不抢占资源
大流量接口独立部署,资源隔离
三、数据库优化(绝大多数后端瓶颈在这里)
MySQL 优化
索引优化
遵循最左前缀;避免失效:
like '%xxx'、函数、隐式类型转换、or、not in不要过度建索引,索引会拖慢写入
联合索引把等值放前面,范围放最后
用
explain看 type、key、rows、Extra(Using filesort、Using temporary 要重点优化)
SQL 写法
禁止
select *,只查需要字段;减少回表大分页不要
offset 100000,使用主键分页:where id > ? limit ?避免大事务:事务越小越好,锁持有时间越短越好
批量操作:batch insert/update,控制批次大小(500~1000,太大锁表、binlog 暴涨)
表结构
合适字段类型:用 int 代替 varchar 存状态,日期 datetime 代替 varchar;字段尽量 NOT NULL
大字段(text、blob)拆到独立附表,避免查询拖慢
数据库配置:连接池、buffer pool、redo log、binlog 策略
连接池:HikariCP(首选),合理设置最大连接数,连接数不是越大越好
ORM 避坑(MyBatis / JPA)
禁止循环里单条查库(N+1 问题!),批量查询、IN 查询、关联查询
MyBatis:懒加载慎用;resultMap 复用;避免动态 SQL 写烂索引
JPA/Hibernate:不要默认 fetch EAGER,慎用级联
四、JVM 调优
现代 JDK(JDK17+,SpringBoot3 推荐)默认 G1 已经很好,不要上来乱改参数,先看 GC 日志
GC 选择
JDK8:G1;高延迟低吞吐:ZGC(JDK11 + 稳定);超高吞吐:Parallel GC
生产优先 ZGC:低停顿(亚毫秒级),适合接口响应敏感业务
基础参数要点
Xmx、Xms 设置成一样,减少堆扩容
堆不要无限大,受宿主机内存限制,预留 OS、容器、缓存内存
开启 GC 日志、使用 Async-Profiler 抓 GC 停顿
内存泄漏常见点
静态集合长期持有对象、线程池无限堆积任务、未关闭流、ThreadLocal 忘记 remove(极易内存泄漏!)
大对象频繁创建,进入老年代,触发 FullGC
小优化
对象池:频繁创建销毁的重对象(Netty Buffer、连接),普通 POJO 不要池化
优先栈上分配、逃逸分析开启
五、Java 代码层面优化(单接口微观优化)
注意:代码优化收益通常低于数据库 / 缓存,不要过早优化可读性
集合
遍历:ArrayList 随机访问快;LinkedList 随机访问极慢
HashMap:预估初始容量,减少 rehash;
new HashMap(128)而不是默认 16线程安全:优先 Caffeine、ConcurrentHashMap;放弃 Hashtable、Collections.synchronizedMap
遍历集合不要在 for 里 remove(用迭代器 remove)
字符串
循环拼接:
StringBuilder,不要+(编译器优化只在简单场景生效)大文本不要用 String,考虑流式处理
IO 资源
所有流、连接、Socket:try-with-resources 自动关闭,防止句柄泄漏
循环
循环内避免 new 对象、避免重复计算(提前提取常量)
避免深层嵌套循环
序列化
JSON:Jackson > Fastjson2;序列化大对象非常耗 CPU;减少传输字段
内部 RPC:Protobuf/Hessian,比 JSON 体积更小、更快
日期、正则
禁止
SimpleDateFormat(非线程安全),用java.time(JDK8+ LocalDateTime)正则预编译 Pattern,不要每次 new
锁优化
缩小锁粒度;优先无锁、CAS、ThreadLocal、读写锁 ReentrantReadWriteLock
避免锁竞争:分段锁;不要在锁内执行 IO/DB 调用(锁持有太久)
synchronized 偏向锁、轻量级锁了解,不要盲目换成 Lock
Stream
简单遍历 for 循环性能高于 Stream;并行流
parallelStream()慎用!默认共用 ForkJoinPool,容易挤占全局线程,IO 密集不适合并行流
六、RPC & 外部调用优化
Feign/Dubbo:超时时间、重试策略合理配置;重试只允许幂等接口
外部 HTTP 调用:连接池(OkHttp、Apache HttpClient),复用连接,减少 TCP 握手
串行独立 IO 改成并行(CompletableFuture.allOf),注意超时控制,防止一个慢调用拖整体
批量接口:优先批量查询代替多次单条调用
七、容器 & 操作系统层面(K8s/Docker)
Docker:JDK 感知容器内存(JDK10 + 默认支持,JDK8 需要参数),避免 OOM kill
Linux:文件句柄数 ulimit、TCP 参数(tcp_tw_reuse、连接队列)
CPU 绑定、调度策略:高吞吐业务避免频繁上下文切换
八、常用避坑清单(高频踩坑)
✅ 禁止:循环查库 N+1 ✅ 禁止:无界队列线程池 ✅ 禁止:ThreadLocal 不清理 ✅ 禁止:大事务、长事务 ✅ 禁止:缓存大对象、缓存无限增长 ✅ 禁止:接口里面同步调用一堆第三方串行调用 ✅ 禁止:分页 offset 超大 ✅ 禁止:连接池设置过大
九、优化落地顺序(推荐)
压测 + 链路追踪,定位瓶颈(慢 SQL / 外部调用 / GC / 锁)
优先:加缓存、异步化、优化 SQL 和索引(收益最高)
然后:限流熔断、读写分离分表
再做:JVM 调优、线程池规范
最后:微观代码优化
十、示例:JMH 做接口内方法基准测试
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class StringBench {
@Benchmark
public String testBuilder() {
StringBuilder sb = new StringBuilder();
for(int i=0;i<100;i++) sb.append(i);
return sb.toString();
}
}本文原创作者:易君召,详见:https://www.yijunzhao.cn/authors/yijunzhao,转载请注明出处。
原文链接
欢迎访问 小易撩挨踢