簡(jiǎn)單聊聊SpringBoot性能優(yōu)化的12個(gè)小技巧
前言
不知道你在SpringBoot項(xiàng)目中,有沒有遇到過下面這樣的代碼:
@GetMapping("/orders") public List<Order> listOrders() { return orderDao.findAll(); }
一次性查詢了所有的訂單,全表掃描50萬數(shù)據(jù),導(dǎo)致接口查詢性能很差,嚴(yán)重的時(shí)候可能會(huì)導(dǎo)致OOM問題。
問題定位:
- 未分頁查詢
- 無緩存機(jī)制
- 未啟用批量處理
這次事故讓我明白:性能優(yōu)化必須貫穿開發(fā)全流程。
今天這篇文章,跟大家一起聊聊SpringBoot優(yōu)化的12招,希望對(duì)你會(huì)有所幫助。
第1招:連接池參數(shù)調(diào)優(yōu)
問題場(chǎng)景:默認(rèn)配置導(dǎo)致連接池資源浪費(fèi),高并發(fā)時(shí)出現(xiàn)連接等待
錯(cuò)誤配置:
spring: datasource: hikari: maximum-pool-size: 1000 connection-timeout: 30000
數(shù)據(jù)庫連接池的最大連接數(shù),盲目設(shè)置過大,連接超時(shí)時(shí)間設(shè)置過長(zhǎng)。
優(yōu)化方案:
spring: datasource: hikari: maximum-pool-size: ${CPU核心數(shù)*2} # 動(dòng)態(tài)調(diào)整 minimum-idle: 5 connection-timeout: 3000 # 3秒超時(shí) max-lifetime: 1800000 # 30分鐘 idle-timeout: 600000 # 10分鐘空閑釋放
數(shù)據(jù)庫連接池的最大連接數(shù),改成根據(jù)CPU核心數(shù)動(dòng)態(tài)調(diào)整。
將連接超時(shí)時(shí)間由30000,改成3000。
第2招:JVM內(nèi)存優(yōu)化
問題場(chǎng)景:頻繁Full GC導(dǎo)致服務(wù)卡頓
我們需要優(yōu)化JVM參數(shù)。
啟動(dòng)參數(shù)優(yōu)化:
java -jar -Xms4g -Xmx4g -XX:NewRatio=1 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=35 -XX:+AlwaysPreTouch
最大堆內(nèi)存和初始堆內(nèi)存都設(shè)置成了4G。
-XX:NewRatio=1,設(shè)置新生代和老年代各占一半。
垃圾收集器配置的是G1。
垃圾回收的最大停頓時(shí)間為200毫秒。
第3招:關(guān)閉無用組件
問題場(chǎng)景:自動(dòng)裝配加載不需要的Bean
優(yōu)化方案:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, SecurityAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
如果有些功能暫時(shí)用不到,可以先排除一下。
在SpringBoot項(xiàng)目啟動(dòng)的時(shí)候,排除了DataSourceAutoConfiguration和SecurityAutoConfiguration配置類的自動(dòng)裝載。
第4招:響應(yīng)壓縮配置
問題場(chǎng)景:接口返回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
配置開啟響應(yīng)的壓縮。
第5招:請(qǐng)求參數(shù)校驗(yàn)
問題場(chǎng)景:惡意請(qǐng)求導(dǎo)致資源耗盡
防御代碼:
@GetMapping("/products") public PageResult<Product> list( @RequestParam @Max(value=100, message="頁大小不能超過100") int pageSize, @RequestParam @Min(1) int pageNum) { //... }
在接口中做好參數(shù)校驗(yàn),可以攔截很多惡意請(qǐng)求。
第6招:異步處理機(jī)制
問題場(chǎng)景:同步處理導(dǎo)致線程阻塞
優(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è)務(wù)邏輯中,使用異步處理性能可能會(huì)更好。
第7招:使用緩存
使用緩存可以提升效率。
緩存架構(gòu):
代碼實(shí)現(xiàn):
@Cacheable(cacheNames = "products", key = "#id", cacheManager = "caffeineCacheManager") public Product getDetail(Long id) { return productDao.getById(id); }
這里使用了內(nèi)存緩存。
第8招:批量操作優(yōu)化
問題場(chǎng)景:逐條插入導(dǎo)致性能低下
優(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)化
問題場(chǎng)景:慢查詢?nèi)罩绢l繁出現(xiàn)全表掃描,SQL執(zhí)行時(shí)間波動(dòng)大
錯(cuò)誤案例:
-- 商品表結(jié)構(gòu) CREATE TABLE products ( id BIGINT PRIMARY KEY, name VARCHAR(200), category VARCHAR(50), price DECIMAL(10,2), create_time DATETIME ); -- 低效查詢 SELECT * FROM products WHERE category = '手機(jī)' AND price > 5000 ORDER BY create_time DESC;
問題分析:
優(yōu)化方案一:聯(lián)合索引設(shè)計(jì)
索引創(chuàng)建:
下面創(chuàng)建了一個(gè)分類ID,單價(jià)和時(shí)間的聯(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 = '手機(jī)' AND price > 5000 ORDER BY create_time DESC;
這里使用了覆蓋索引。
優(yōu)化方案三:索引失效預(yù)防
常見失效場(chǎng)景:
案例修復(fù):
錯(cuò)誤寫法:
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';
查詢時(shí)間范圍,這里使用了BETWEEN AND關(guān)鍵字,代替了等于號(hào)。
優(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)合索引的第一個(gè)字段必須出現(xiàn)在查詢條件中
- 短索引原則:整型字段優(yōu)先,字符串字段使用前綴索引
- 適度索引原則:?jiǎn)蝹€(gè)表索引數(shù)量不超過5個(gè),總索引長(zhǎng)度不超過表數(shù)據(jù)量30%
DBA工具箱
- 索引分析腳本
- 執(zhí)行計(jì)劃可視化工具
- 索引碎片檢測(cè)工具
第10招:自定義線程池
問題場(chǎng)景:默認(rèn)線程池導(dǎo)致資源競(jìng)爭(zhēng)
優(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è)務(wù)場(chǎng)景中,使用Executors類創(chuàng)建默認(rèn)的線程池,可能會(huì)導(dǎo)致OOM問題。
因此,我們需要自定義線程池。
第11招:熔斷限流策略
問題場(chǎng)景:突發(fā)流量導(dǎo)致服務(wù)雪崩
解決方案:
// 使用Sentinel實(shí)現(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) { throw new RuntimeException("服務(wù)繁忙,請(qǐng)稍后重試"); } // 降級(jí)處理 public Order handleFallback(Long id, Throwable t) { return Order.getDefaultOrder(); }
為了解決重復(fù)流量導(dǎo)致服務(wù)雪崩的問題,我們需要增加接口熔斷、限流和降級(jí)處理。
第12招:全鏈路監(jiān)控體系
問題場(chǎng)景:線上問題定位困難,缺乏數(shù)據(jù)支撐
我們需要增加項(xiàng)目全鏈路的監(jiān)控。
監(jiān)控方案:
# SpringBoot配置 management: endpoints: web: exposure: include: "*" metrics: export: prometheus: enabled: true
這里使用了prometheus監(jiān)控。
監(jiān)控架構(gòu):
核心監(jiān)控指標(biāo):
總結(jié)
SpringBoot性能優(yōu)化檢查清單
- 連接池參數(shù)按業(yè)務(wù)調(diào)整
- JVM參數(shù)經(jīng)過壓測(cè)驗(yàn)證
- 所有查詢走緩存機(jī)制
- 批量操作替代逐條處理
- 線程池按場(chǎng)景定制
- 全鏈路監(jiān)控覆蓋
三條黃金法則:
- 預(yù)防性優(yōu)化:編碼時(shí)考慮性能影響
- 數(shù)據(jù)驅(qū)動(dòng):用監(jiān)控指標(biāo)指導(dǎo)優(yōu)化方向
- 持續(xù)迭代:性能優(yōu)化是持續(xù)過程
性能工具包
- Arthas在線診斷
- JProfiler性能分析
- Prometheus監(jiān)控體系
(看著監(jiān)控大屏上平穩(wěn)的QPS曲線,我知道今晚可以睡個(gè)好覺了...)
到此這篇關(guān)于簡(jiǎn)單聊聊SpringBoot性能優(yōu)化的12個(gè)小技巧的文章就介紹到這了,更多相關(guān)SpringBoot性能優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
sharding-jdbc實(shí)現(xiàn)分頁查詢的示例代碼
sharding-jdbc是一個(gè)輕量級(jí)Java框架,它提供了分布式數(shù)據(jù)庫中間件的功能,支持水平分表和分庫分表,在分頁查詢方面,sharding-jdbc支持兩種方式:基于物理分頁和基于邏輯分頁,本文給大家介紹sharding-jdbc如何實(shí)現(xiàn)分頁查詢,需要的朋友可以參考下2024-05-05Java Web文件上傳與下載優(yōu)化的實(shí)現(xiàn)方案
文件上傳與下載是 Web 應(yīng)用中常見的功能,尤其是在需要處理大量文件傳輸、存儲(chǔ)的場(chǎng)景下,傳統(tǒng)的文件上傳和下載方式雖然簡(jiǎn)單,但如果不加以優(yōu)化,可能會(huì)帶來一些問題,所以今天,我們將深入探討 Java Web 中如何實(shí)現(xiàn)高效的文件上傳和下載,需要的朋友可以參考下2025-02-02淺談Java開發(fā)架構(gòu)之領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)DDD落地
DDD(Domain-Driven Design 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))是由Eric Evans最先提出,目的是對(duì)軟件所涉及到的領(lǐng)域進(jìn)行建模,以應(yīng)對(duì)系統(tǒng)規(guī)模過大時(shí)引起的軟件復(fù)雜性的問題2021-06-06Java實(shí)現(xiàn)解析dcm醫(yī)學(xué)影像文件并提取文件信息的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)解析dcm醫(yī)學(xué)影像文件并提取文件信息的方法,結(jié)合實(shí)例形式分析了java基于第三方庫文件針對(duì)dcm醫(yī)學(xué)影像文件的解析操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-04-04java SelectableChannel的使實(shí)例用法講解
在本篇文章里小編給大家整理的是一篇關(guān)于java SelectableChannel的使實(shí)例用法講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2021-03-03Windows系統(tǒng)中Java調(diào)用cmd命令及執(zhí)行exe程序的方法
這篇文章主要介紹了Windows系統(tǒng)中Java調(diào)用cmd命令及執(zhí)行exe程序的方法,主要用到了IOException類,需要的朋友可以參考下2016-03-03詳解Java多態(tài)對(duì)象的類型轉(zhuǎn)換與動(dòng)態(tài)綁定
這篇文章主要介紹了詳解Java多態(tài)對(duì)象的類型轉(zhuǎn)換與動(dòng)態(tài)綁定,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09Java靜態(tài)static與實(shí)例instance方法示例
這篇文章主要為大家介紹了Java靜態(tài)static與實(shí)例instance方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08