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

概述


最近公司的下單接口有些慢,老闆擔憂沒法支撐雙11,想讓我優化一把,可是前提是不容許大改,由於下單接口太複雜了,若是改動太大,怕有風險。另外開發成本和測試成本也很是大。對於這種有挑戰性的任務,我向來是很是喜歡的,由於在解決問題的過程當中,能夠學習到不少東西。java

當時我只是知道下單接口慢,可是沒人告訴我慢在哪裏,也便是說,哪些瓶頸致使下單接口慢了。其實沒人知道也不要緊的,由於咱們能夠經過壓測來找到具體的瓶頸。mysql

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


用到的工具和環境


工具

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

環境

  • 騰訊雲Mysql數據庫

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


找瓶頸


下單屬於寫接口,大部分狀況下,瓶頸都出在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() {
 //一、扣減庫存
 reduceSkuStock();
 //二、建立訂單
 insertOrder();
 //三、其餘寫操做
  。。。。
}

庫存記錄一般存在一張獨立的庫存表,因爲建立訂單的方法,是一個大事務,這樣就會致使某條庫存記錄只有當整個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倍。可是接下來還得作幾件事情:

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

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

三、增長應用的MYSQL鏈接數;

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

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

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

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


總結


這個是下單接口的邏輯不能大改的狀況下的優化方案,通常來講,庫存操做應該是單獨的服務,能夠單獨優化的。而單純的下單邏輯也是能夠優化的。


原文連接


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

相關文章
相關標籤/搜索