架構(gòu)師必知:SpringBoot性能優(yōu)化的12招
前言
不知道你在SpringBoot項目中,有沒有遇到過下面這樣的代碼:
@GetMapping("/orders")
public List<Order> listOrders() {
return orderDao.findAll();
}
一次性查詢了所有的訂單,全表掃描50萬數(shù)據(jù),導致接口查詢性能很差,嚴重的時候可能會導致OOM問題。
問題定位:
- 未分頁查詢
- 無緩存機制
- 未啟用批量處理
這次事故讓我明白:性能優(yōu)化必須貫穿開發(fā)全流程。
今天這篇文章,跟大家一起聊聊SpringBoot優(yōu)化的12招,希望對你會有所幫助。
圖片
第1招:連接池參數(shù)調(diào)優(yōu)
問題場景:默認配置導致連接池資源浪費,高并發(fā)時出現(xiàn)連接等待
錯誤配置:
spring:
datasource:
hikari:
maximum-pool-size: 1000
connection-timeout: 30000
數(shù)據(jù)庫連接池的最大連接數(shù),盲目設置過大,連接超時時間設置過長。
優(yōu)化方案:
spring:
datasource:
hikari:
maximum-pool-size: ${CPU核心數(shù)*2} # 動態(tài)調(diào)整
minimum-idle: 5
connection-timeout: 3000 # 3秒超時
max-lifetime: 1800000 # 30分鐘
idle-timeout: 600000 # 10分鐘空閑釋放
數(shù)據(jù)庫連接池的最大連接數(shù),改成根據(jù)CPU核心數(shù)動態(tài)調(diào)整。
將連接超時時間由30000,改成3000。
第2招:JVM內(nèi)存優(yōu)化
問題場景:頻繁Full GC導致服務卡頓
我們需要優(yōu)化JVM參數(shù)。
啟動參數(shù)優(yōu)化:
java -jar -Xms4g -Xmx4g
-XX:NewRatio=1
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=35
-XX:+AlwaysPreTouch
最大堆內(nèi)存和初始堆內(nèi)存都設置成了4G。
-XX:NewRatio=1,設置新生代和老年代各占一半。
垃圾收集器配置的是G1。
垃圾回收的最大停頓時間為200毫秒。
第3招:關閉無用組件
問題場景:自動裝配加載不需要的Bean
優(yōu)化方案:
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
SecurityAutoConfiguration.class
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
如果有些功能暫時用不到,可以先排除一下。
在SpringBoot項目啟動的時候,排除了DataSourceAutoConfiguration和SecurityAutoConfiguration配置類的自動裝載。
第4招:響應壓縮配置
問題場景:接口返回JSON數(shù)據(jù)體積過大
優(yōu)化方案:
server:
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/json
min-response-size: 1024
配置開啟響應的壓縮。
第5招:請求參數(shù)校驗
問題場景:惡意請求導致資源耗盡
防御代碼:
@GetMapping("/products")
public PageResult<Product> list(
@RequestParam @Max(value=100, message="頁大小不能超過100") int pageSize,
@RequestParam @Min(1) int pageNum) {
//...
}
在接口中做好參數(shù)校驗,可以攔截很多惡意請求。
第6招:異步處理機制
問題場景:同步處理導致線程阻塞
優(yōu)化方案:
@Async("taskExecutor")
public CompletableFuture<List<Order>> asyncProcess() {
return CompletableFuture.completedFuture(heavyProcess());
}
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(500);
return executor;
}
在有些業(yè)務邏輯中,使用異步處理性能可能會更好。
第7招:使用緩存
使用緩存可以提升效率。
緩存架構(gòu):
圖片
代碼實現(xiàn):
@Cacheable(cacheNames = "products", key = "#id",
cacheManager = "caffeineCacheManager")
public Product getDetail(Long id) {
return productDao.getById(id);
}
這里使用了內(nèi)存緩存。
第8招:批量操作優(yōu)化
問題場景:逐條插入導致性能低下
優(yōu)化方案:
@Transactional
public void batchInsert(List<Product> products) {
jdbcTemplate.batchUpdate(
"INSERT INTO product(name,price) VALUES(?,?)",
products,
500, // 每批數(shù)量
(ps, product) -> {
ps.setString(1, product.getName());
ps.setBigDecimal(2, product.getPrice());
});
}
每500條數(shù)據(jù)插入一次數(shù)據(jù)庫。
第9招:索引深度優(yōu)化
問題場景:慢查詢?nèi)罩绢l繁出現(xiàn)全表掃描,SQL執(zhí)行時間波動大
錯誤案例:
-- 商品表結(jié)構(gòu)
CREATETABLE products (
idBIGINT PRIMARY KEY,
nameVARCHAR(200),
categoryVARCHAR(50),
price DECIMAL(10,2),
create_time DATETIME
);
-- 低效查詢
SELECT * FROM products
WHEREcategory = '手機'
AND price > 5000
ORDERBY create_time DESC;
問題分析:
圖片
優(yōu)化方案一:聯(lián)合索引設計
索引創(chuàng)建:
下面創(chuàng)建了一個分類ID,單價和時間的聯(lián)合索引:
ALTER TABLE products
ADD INDEX idx_category_price_create
(category, price, create_time);
優(yōu)化方案二:覆蓋索引優(yōu)化
查詢改造:
只查詢索引包含字段:
SELECT id, category, price, create_time
FROM products
WHERE category = '手機'
AND price > 5000
ORDER BY create_time DESC;
這里使用了覆蓋索引。
優(yōu)化方案三:索引失效預防
常見失效場景:
圖片
案例修復:
錯誤寫法:
SELECT * FROM products
WHERE DATE(create_time) = '2023-01-01';
正確寫法:
SELECT * FROM products
WHERE create_time BETWEEN '2023-01-01 00:00:00'
AND '2023-01-01 23:59:59';
查詢時間范圍,這里使用了BETWEEN AND關鍵字,代替了等于號。
優(yōu)化方案四:索引監(jiān)控分析
診斷命令:
查看索引使用情況:
SELECT
index_name,
rows_read,
rows_selected
FROM
sys.schema_index_statistics
WHERE
table_name = 'products';
分析索引效率:
EXPLAIN FORMAT=JSON
SELECT ...;
索引優(yōu)化黃金三原則
- 最左前綴原則:聯(lián)合索引的第一個字段必須出現(xiàn)在查詢條件中
- 短索引原則:整型字段優(yōu)先,字符串字段使用前綴索引
- 適度索引原則:單個表索引數(shù)量不超過5個,總索引長度不超過表數(shù)據(jù)量30%
DBA工具箱
- 索引分析腳本
- 執(zhí)行計劃可視化工具
- 索引碎片檢測工具
第10招:自定義線程池
問題場景:默認線程池導致資源競爭
優(yōu)化方案:
@Bean("customPool")
public Executor customThreadPool() {
return new ThreadPoolExecutor(
10, // 核心線程
50, // 最大線程
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new CustomThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
}
在高并發(fā)業(yè)務場景中,使用Executors類創(chuàng)建默認的線程池,可能會導致OOM問題。
因此,我們需要自定義線程池。
第11招:熔斷限流策略
問題場景:突發(fā)流量導致服務雪崩
解決方案:
// 使用Sentinel實現(xiàn)接口限流
@SentinelResource(value = "orderQuery",
blockHandler = "handleBlock",
fallback = "handleFallback")
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
return orderService.getById(id);
}
// 限流處理
public Order handleBlock(Long id, BlockException ex) {
thrownew RuntimeException("服務繁忙,請稍后重試");
}
// 降級處理
public Order handleFallback(Long id, Throwable t) {
return Order.getDefaultOrder();
}
為了解決重復流量導致服務雪崩的問題,我們需要增加接口熔斷、限流和降級處理。
第12招:全鏈路監(jiān)控體系
問題場景:線上問題定位困難,缺乏數(shù)據(jù)支撐
我們需要增加項目全鏈路的監(jiān)控。
監(jiān)控方案:
# SpringBoot配置
management:
endpoints:
web:
exposure:
include: "*"
metrics:
export:
prometheus:
enabled: true
這里使用了prometheus監(jiān)控。
監(jiān)控架構(gòu):
圖片
核心監(jiān)控指標:
圖片
總結(jié)
SpringBoot性能優(yōu)化檢查清單
- 連接池參數(shù)按業(yè)務調(diào)整
- JVM參數(shù)經(jīng)過壓測驗證
- 所有查詢走緩存機制
- 批量操作替代逐條處理
- 線程池按場景定制
- 全鏈路監(jiān)控覆蓋
圖片
三條黃金法則:
- 預防性優(yōu)化:編碼時考慮性能影響
- 數(shù)據(jù)驅(qū)動:用監(jiān)控指標指導優(yōu)化方向
- 持續(xù)迭代:性能優(yōu)化是持續(xù)過程
性能工具包
- Arthas在線診斷
- JProfiler性能分析
- Prometheus監(jiān)控體系