JAVA下單接口優(yōu)化實(shí)戰(zhàn)TPS性能提高10倍
概述
最近公司的下單接口有些慢,老板擔(dān)心無法支撐雙11,想讓我優(yōu)化一把,但是前提是不允許大改,因?yàn)橄聠谓涌谔珡?fù)雜了,如果改動(dòng)太大,怕有風(fēng)險(xiǎn)。另外開發(fā)成本和測試成本也非常大。對于這種有挑戰(zhàn)性的任務(wù),我向來是非常喜歡的,因?yàn)樵诮鉀Q問題的過程中,可以學(xué)習(xí)到很多東西。
當(dāng)時(shí)我只是知道下單接口慢,但是沒人告訴我慢在哪里,也即是說,哪些瓶頸導(dǎo)致下單接口慢了。其實(shí)沒人知道也沒關(guān)系的,因?yàn)槲覀兛梢酝ㄟ^壓測來找到具體的瓶頸。
下面會(huì)詳細(xì)介紹一下,在本次壓測中遇到的問題以及如何解決,期間用了什么工具。
用到的工具和環(huán)境
工具
- Jmeter
- JAVA自帶的jvisualvm
- JMX
- nmon
環(huán)境
- 騰訊云Mysql
- 騰訊云2核4g的服務(wù)器1臺(tái)
找瓶頸
下單屬于寫接口,大部分情況下,瓶頸都出在DB
里,程序可能都在等待DB
鎖的釋放。為了驗(yàn)證這個(gè)想法,我們可以使用Jmeter
和jvisualvm
來驗(yàn)證一下。
為了監(jiān)控服務(wù)器和服務(wù)器中JAVA進(jìn)程,我們需要開啟JMX,可以在JAVA進(jìn)程啟動(dòng)的時(shí)候,添加如下幾個(gè)參數(shù):
JMX_OPTS="-Dcom.sun.management.jmxremote.port=7969 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=xx.xx.xx.xx" nohup java ${JMX_OPTS} -jar xxxxx.jar
Djava.rmi.server.hostname
填寫JAVA
進(jìn)程所在服務(wù)器的IP
地址,-Dcom.sun.management.jmxremote.port=7969
是指定JMX
監(jiān)控端口的,這里是7969。
重新啟動(dòng)進(jìn)程后,打開本地的(我用的是Window10)jvisualvm
,添加JMX
配置。配置成功后,可以點(diǎn)擊線程那個(gè)tab
,因?yàn)槲覀円鼍€程dump
,觀察線程的執(zhí)行情況。
好了,現(xiàn)在我們可以使用Jmeter
來對下單接口進(jìn)行壓測了。可以先用50線程并發(fā)壓,執(zhí)行時(shí)間是1分鐘。
在壓測的過程中,做一下線程dump
,同時(shí)利用nmon
觀察應(yīng)用服務(wù)器CPU
的負(fù)載情況。
負(fù)載很低,將線程并發(fā)調(diào)整到100后,CPU還是上不去,這樣的話,初步可以判斷,代碼里有鎖。
通過觀察dump文件,發(fā)現(xiàn)如下信息:
- locked <22f6e7f3> (a com.mysql.cj.core.io.ReadAheadInputStream)
- at com.sun.proxy.$Proxy231.reduceSkuStock(Unknown Source)
觸發(fā)這個(gè)lock的業(yè)務(wù)代碼是reduceSkuStock
方法。通過閱讀代碼,發(fā)現(xiàn)reduceSkuStock
被包在一個(gè)大事務(wù)里面。
@Transactional(rollbackFor = {Exception.class}) createOrder() { //1、扣減庫存 reduceSkuStock(); //2、創(chuàng)建訂單 insertOrder(); //3、其他寫操作 。。。。 }
庫存記錄通常存在一張獨(dú)立的庫存表,由于創(chuàng)建訂單的方法,是一個(gè)大事務(wù),這樣就會(huì)導(dǎo)致某條庫存記錄只有當(dāng)整個(gè)createorder()
方法執(zhí)行完后,數(shù)據(jù)庫行鎖才會(huì)被釋放,在這個(gè)期間,其他線程是無法對這條庫存記錄進(jìn)行寫操作的。因此我們可以在reduceSkuStock()
中,再開一個(gè)事務(wù),操作完這條庫存記錄后,趕緊釋放鎖,這樣應(yīng)該可以提高一些性能。為了驗(yàn)證是否是因?yàn)槭聞?wù)的原因?qū)е孪聠谓涌诼?,我們可以直接?code>createOrder()方法的事務(wù)去掉,再壓測一下。
壓測結(jié)果發(fā)現(xiàn),下單接口的TPS
提高了一倍,CPU
也上去了不少,但是仍然不夠理想,代碼里,應(yīng)該還有其他的鎖。再次做線程dump
,又發(fā)現(xiàn)了一個(gè)鎖。
- locked <438be230> (a org.apache.http.pool.AbstractConnPool$2)
- at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
導(dǎo)致鎖的代碼是HttpClient
的execute
方法,該方法在執(zhí)行的時(shí)候,一直在等待獲取HTTP
連接,通過查看源代碼,發(fā)現(xiàn)居然沒有使用連接池,醉了。趕緊加上如下代碼:
PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(); pool.setDefaultMaxPerRoute(400); httpClient = HttpClients.custom().setConnectionManager(pool).build();
再次壓測后,發(fā)現(xiàn)代碼里已經(jīng)沒有鎖了。TPS
提升了5倍。但是接下來還得做幾件事情:
1、打印下單接口的所有SQL
,然后逐一進(jìn)行explain
操作,看看有沒有全表掃描的語句或者沒用到索引的SQL
語句;
2、觀察下單接口執(zhí)行的過程中,FULL GC
發(fā)生的次數(shù);
3、增加應(yīng)用的MYSQL
連接數(shù);
好了,到了這地方,我們可以回到前面,來解決庫存問題了。由于老板說,不能大改,因此我就在reduceSkuStock
方法上,再開一個(gè)事務(wù)。
@Transactional(propagation = Propagation.REQUIRES_NEW) reduceSkuStock(){}
讓執(zhí)行庫存操作的線程執(zhí)行完后,趕緊釋放行鎖。這樣做也有個(gè)風(fēng)險(xiǎn),就是庫存扣減成功后,下單失敗了。不過這種情況比較少,因?yàn)楫?dāng)時(shí)的下單接口中,大部分業(yè)務(wù)邏輯都在前面做好判斷了,到達(dá)插入訂單的代碼時(shí),就只是單獨(dú)的insert了,除非數(shù)據(jù)庫掛了,不然不會(huì)出現(xiàn)下單失敗的情況。
在開發(fā)環(huán)境下,經(jīng)過調(diào)優(yōu)后,下單接口的TPS提升了3倍左右,當(dāng)然由于開發(fā)環(huán)境的數(shù)據(jù)庫和應(yīng)用服務(wù)器都比較差,也會(huì)對TPS
有影響的。當(dāng)時(shí)優(yōu)化完后,在生產(chǎn)上進(jìn)行了壓測,發(fā)現(xiàn)TPS
提升了10倍。
這個(gè)是下單接口的邏輯不能大改的情況下的優(yōu)化方案,一般來說,庫存操作應(yīng)該是單獨(dú)的服務(wù),可以單獨(dú)優(yōu)化的。而單純的下單邏輯也是可以優(yōu)化的。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
springboot2.2.2集成dubbo的實(shí)現(xiàn)方法
這篇文章主要介紹了springboot2.2.2集成dubbo的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01解決?IDEA?Maven?項(xiàng)目中"Could?not?find?artifact"?
這篇文章主要介紹了解決IDEA Maven項(xiàng)目中Could not?find?artifact問題的常見情況和解決方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07Java實(shí)現(xiàn)天天酷跑小游戲完整代碼(附源碼)
這篇文章主要介紹了使用Java實(shí)現(xiàn)天天酷跑(附源碼),本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12Java解決計(jì)算相鄰兩個(gè)數(shù)的最大差值的問題
今天給大家?guī)硪坏浪惴}:給定一個(gè)數(shù)組,求如果排序之后,相鄰兩數(shù)的最大差值。要求時(shí)間復(fù)雜度O(N),且要求不能用非基于比較的排序??靵砀S小編一起學(xué)習(xí)一下如何解決這一問題吧2021-12-12java對接webservice接口的4種方式總結(jié)
因工作需要和一個(gè)Sap相關(guān)系統(tǒng)以WebService的方式進(jìn)行接口聯(lián)調(diào),之前僅聽過這種技術(shù),但并沒有實(shí)操過,所以將本次開發(fā)進(jìn)行記錄,這篇文章主要給大家介紹了關(guān)于java對接webservice接口的4種方式,需要的朋友可以參考下2023-10-10