下单接口调优实战,性能提升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倍

相关文章
相关标签/搜索