文档章节

下单接口调优实战,性能提高10倍

Sam哥哥聊技术
 Sam哥哥聊技术
发布于 2018/10/19 12:24
字数 1479
阅读 3738
收藏 104

概述


最近公司的下单接口有些慢,老板担心无法支撑双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倍

© 著作权归作者所有

共有 人打赏支持
Sam哥哥聊技术
粉丝 78
博文 12
码字总数 13949
作品 0
广州
高级程序员
私信 提问
加载中

评论(24)

s
strangerC

引用来自“strangerC”的评论

看起来还是要大改才行,多并发下多个线程去操作一条库存记录不合理,真要是并发上来了还是会多个线程等待锁。应该采用库存流水记录的方式。

引用来自“Sam哥哥聊技术”的评论

好主意。能大概说一下,以什么维度来生成一条库存流水记录吗? 仓库+skuId +???? 才能避免不去争夺同一条库存记录。 好像也有点难做吧。
对于某个库存,通过查所有流水获取当前的库存数量,再将本次库存变化写一条流水到数据库。这样无需多个线程更新同一条记录,而且数据库有每次库存变化的数据方便回溯。不过如果并发量一上来还是会有超卖的问题,碰到秒杀,还是上缓存吧。
Sam哥哥聊技术
Sam哥哥聊技术

引用来自“strangerC”的评论

看起来还是要大改才行,多并发下多个线程去操作一条库存记录不合理,真要是并发上来了还是会多个线程等待锁。应该采用库存流水记录的方式。
好主意。能大概说一下,以什么维度来生成一条库存流水记录吗? 仓库+skuId +???? 才能避免不去争夺同一条库存记录。 好像也有点难做吧。
s
strangerC
看起来还是要大改才行,多并发下多个线程去操作一条库存记录不合理,真要是并发上来了还是会多个线程等待锁。应该采用库存流水记录的方式。
Sam哥哥聊技术
Sam哥哥聊技术

引用来自“谢均”的评论

事务放在reduceSkuStock(),insertOrder()的时候出错了,怎么办?
这个只能走后补的形式,这种出现的概率很低。如果出现了。有两种办法:第一种是,直接try catch,然后再catch里面,写代码进行库存逻辑回滚(代码有点难度的)。第二种:根据日志,手动执行sql脚本,将库存回复回来。
谢均
谢均
事务放在reduceSkuStock(),insertOrder()的时候出错了,怎么办?
Sam哥哥聊技术
Sam哥哥聊技术

引用来自“黄羽蒙”的评论

思路和动手做重要
厉害厉害。
黄羽蒙
黄羽蒙
思路和动手做重要
Sam哥哥聊技术
Sam哥哥聊技术

引用来自“王金豆”的评论

不错,学习了。
Thanks。多多交流。
王金豆
王金豆
不错,学习了。
Sam哥哥聊技术
Sam哥哥聊技术

引用来自“爱吃蛋挞的kk”的评论

思路很重要
是的,正解。
推荐书籍:《Java性能调优指南》

本书作者是Java性能和Java HotSpot 虚拟机领域的佼佼者,帮助你利用现代软工实践提高性能,避免常见错误,从实战生涯中总结技巧和窍门。 利用G1克服并行、串行和CMS垃圾收集器的局限性了解G...

ddddd8
2017/11/27
0
0
性能测试day07_性能瓶颈和分析

  其实如果之前都做的很到位的话,那么再加上APM工具(dynaTrace等),监控到非常细节,那么我们跑一个业务,我们就能完全清楚的知道每个请求的时间,也能知道请求所产生sql的时间,这样你自...

~泪小白~
2018/08/14
0
0
Google资深大佬深度讲解Go语言实战视频教程

第1章 课程介绍 第2章 基础语法 第3章 内建容器 第4章 面向“对象” 第5章 面向接口8 ]% 第6章 函数式编程 第7章 错误处理和资源管理7 {( |0X 第8章 测试与性能调优$ Z/ R5 u, Z 第9章 Goro...

蜗牛奔跑
2018/08/17
0
0
RHEL/Centos7新功能

1.身份管理 ●kerberos的跨平台信任机制:kerberos将完全兼容微软活动目录,实现完全使用目录进行认证。 ●REALMD:该功能简化了RHEL加入微软活动目录的配置,支持自动发现域信息。 2.性能管...

Cherry_liang
2017/06/08
0
0
架构师必备词汇和知识点

01 高可用 负载均衡(负载均衡算法) 反向代理 服务隔离 服务限流 服务降级(自动优雅降级) 失效转移 超时重试(代理超时、容器超时、前端超时、中间件超时、数据库超时、NoSql超时) 回滚机...

t4i2b10X4c22nF6A
2017/11/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Docker的系统资源限制及验证

1 、限制容器的资源 默认情况下, 容器没有资源限制 ,可以使用主机内核调度程序允许的尽可能多的给定资源。 Docker 提供了控制容器可以 使用多少内存或 CPU 的方法 ,设置 docker run 命令的...

微笑向暖wx
1分钟前
0
0
Redis5.0之Stream案例应用解读

非常高兴有机会和大家在这里交流Redis5.0之Stream应用。今天的分享更多的是一个抛砖引玉,欢迎大家提出更多关于Redis的思考。 首先,我们来个假设,这里有个杯子,这个杯子是去年我老婆送的,...

中间件小哥
2分钟前
0
0
阿里开发者们的第20个感悟:好的工程师为人写代码,而不仅是为编译器

1月17日,好的工程师为人写代码,而不仅是为编译器。这是我们送给开发者的第20个感悟。 李响,作为开源项目etcd作者更为开发者所熟知。etcd是2013年由李响,Brandon Philips, Alex Polvi 所发...

阿里云官方博客
3分钟前
0
0
Linux vmstat命令详解

导读 Linux命令千千万,而我们在日常工作中真真切切用到的命令应该不超过50个,在接下来的日子里,我会对我经常使用的命令,以及使用过程中不熟悉的命令进行一个总结,一是自我总结,加深记忆...

问题终结者
3分钟前
0
0
MacOS Docker安装及使用

MacOS Docker 安装 Homebrew 安装 macOS 我们可以使用 Homebrew 来安装 Docker。 Homebrew 的 Cask 已经支持 Docker for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装: # 安装命令...

火力全開
4分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部