下單接口調優實戰,性能提高10倍 頂 原 薦

概述


最近公司的下單接口有些慢,老闆擔心無法支撐雙11,想讓我優化一把,但是前提是不允許大改,因爲下單接口太複雜了,如果改動太大,怕有風險。另外開發成本和測試成本也非常大。對於這種有挑戰性的任務,我向來是非常喜歡的,因爲在解決問題的過程中,可以學習到很多東西。

當時我只是知道下單接口慢,但是沒人告訴我慢在哪裏,也即是說,哪些瓶頸導致下單接口慢了。其實沒人知道也沒關係的,因爲我們可以通過壓測來找到具體的瓶頸。

下面會詳細介紹一下,在本次壓測中遇到的問題以及如何解決,期間用了什麼工具。


用到的工具和環境


工具

  • Jmeter
  • JAVA自帶的jvisualvm
  • JMX
  • nmon

環境

  • 騰訊雲Mysql

  • 騰訊雲2核4g的服務器1臺


找瓶頸


下單屬於寫接口,大部分情況下,瓶頸都出在DB裏,程序可能都在等待DB鎖的釋放。爲了驗證這個想法,我們可以使用Jmeterjvisualvm來驗證一下。

爲了監控服務器和服務器中JAVA進程,我們需要開啓JMX,可以在JAVA進程啓動的時候,添加如下幾個參數:

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進程所在服務器的IP地址,-Dcom.sun.management.jmxremote.port=7969是指定JMX監控端口的,這裏是7969。

重新啓動進程後,打開本地的(我用的是Window10)jvisualvm,添加JMX配置。配置成功後,可以點擊線程那個tab,因爲我們要做線程dump,觀察線程的執行情況。

在這裏插入圖片描述

在這裏插入圖片描述

好了,現在我們可以使用Jmeter來對下單接口進行壓測了。可以先用50線程併發壓,執行時間是1分鐘。 在這裏插入圖片描述

在壓測的過程中,做一下線程dump,同時利用nmon觀察應用服務器CPU的負載情況。

在這裏插入圖片描述

負載很低,將線程併發調整到100後,CPU還是上不去,這樣的話,初步可以判斷,代碼裏有鎖。 通過觀察dump文件,發現如下信息:

- locked <22f6e7f3> (a com.mysql.cj.core.io.ReadAheadInputStream)
- at com.sun.proxy.$Proxy231.reduceSkuStock(Unknown Source)

觸發這個lock的業務代碼是reduceSkuStock方法。通過閱讀代碼,發現reduceSkuStock被包在一個大事務裏面。

@Transactional(rollbackFor = {Exception.class})
 createOrder() {
 //1、扣減庫存
 reduceSkuStock();
 //2、創建訂單
 insertOrder();
 //3、其他寫操作
  。。。。
}

庫存記錄通常存在一張獨立的庫存表,由於創建訂單的方法,是一個大事務,這樣就會導致某條庫存記錄只有當整個createorder()方法執行完後,數據庫行鎖纔會被釋放,在這個期間,其他線程是無法對這條庫存記錄進行寫操作的。因此我們可以在reduceSkuStock()中,再開一個事務,操作完這條庫存記錄後,立刻釋放鎖,這樣應該可以提高一些性能。爲了驗證是否是因爲事務的原因導致下單接口慢,我們可以直接將createOrder()方法的事務去掉,再壓測一下。

壓測結果發現,下單接口的TPS提高了一倍,CPU也上去了不少,但是仍然不夠理想,代碼裏,應該還有其他的鎖。再次做線程dump,又發現了一個鎖。

- locked <438be230> (a org.apache.http.pool.AbstractConnPool$2)
- at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)

導致鎖的代碼是HttpClientexecute方法,該方法在執行的時候,一直在等待獲取HTTP連接,通過查看源代碼,發現居然沒有使用連接池,醉了。趕緊加上如下代碼:

 PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager();
 pool.setDefaultMaxPerRoute(400);
 httpClient = HttpClients.custom().setConnectionManager(pool).build();

再次壓測後,發現代碼裏已經沒有鎖了。TPS提升了5倍。但是接下來還得做幾件事情:

1、打印下單接口的所有SQL,然後逐一進行explain操作,看看有沒有全表掃描的語句或者沒用到索引的SQL語句;

2、觀察下單接口執行的過程中,FULL GC發生的次數;

3、增加應用的MYSQL連接數;

好了,到了這地方,我們可以回到前面,來解決庫存問題了。由於老闆說,不能大改,因此我就在reduceSkuStock方法上,再開一個事務。

@Transactional(propagation = Propagation.REQUIRES_NEW)
reduceSkuStock(){}

讓執行庫存操作的線程執行完後,趕緊釋放行鎖。這樣做也有個風險,就是庫存扣減成功後,下單失敗了。不過這種情況比較少,因爲當時的下單接口中,大部分業務邏輯都在前面做好判斷了,到達插入訂單的代碼時,就只是單獨的insert了,除非數據庫掛了,不然不會出現下單失敗的情況。

在開發環境下,經過調優後,下單接口的TPS提升了3倍左右,當然由於開發環境的數據庫和應用服務器都比較差,也會對TPS有影響的。當時優化完後,在生產上進行了壓測,發現TPS提升了10倍。


總結


這個是下單接口的邏輯不能大改的情況下的優化方案,一般來說,庫存操作應該是單獨的服務,可以單獨優化的。而單純的下單邏輯也是可以優化的。


原文鏈接


下單接口調優實戰,性能提高10倍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章