分享一次分析/解决支付应用性能问题的全过程,细节和采用的工具和方法

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

引入:

当我们做的某支付系统,以一组web service的方式,部署到生产环境之后,外界手机APP应用调用,发现其响应速度在0.5秒到6秒之间波动,客户已经为此纠结了2星期了。昨天我应邀帮他们搞定这问题,在许多同事的共同帮助下,终于把这个问题圆满解决。这里分享下全过程。


方法论:

模拟整体过程,分段解析,精确定位故障点。 此方法论适用于一切troubleshooting过程(就像在地板上找针,需要把地板划分为一个一个小格子,找过的格子就标记下,这样更容易找到针)

首先,需要对我们的请求过程做充分的了解,以登录接口为例,我们就有了以下的一个抽象的网络拓扑图:

wKiom1SCmqnR5Jv_AAEUy5R4vZE675.jpg

然后我们就开始分段排错,因为整个过程是同步调用并且是对称的,所以只考虑(1)->(11),而不考虑(12)->(20)


具体实践:

步骤,确定客户端到应用服务器的网络通信质量。

Python客户端测试机器去ping 应用服务器的外网地址,测试了1000次后,发现其pin还是挺稳定的,基本都在20毫秒之内,如下图:

wKiom1SCm6jBJreVAAZtvgkw47g484.jpg

 

所以一下子就排除了拓扑图中的 (1),(2),(3),(4),(5)这么多段。

 

 

步骤2:确定其原因不是由我们程序员代码的内部业务逻辑所导致。

为此,我们让开发人员在登录接口中屏蔽了一切和外部通信的部分,也就是不调用技术部接口(注释掉这段代码),只在Spring框架进入Service方法和服务完分别打印下服务器的当前时间戳,然后相减获得实际内部代码业务逻辑(比如请求分流,字符串拼接,数据库操作等),这时候再绘制耗时的曲线图,如下:

wKioL1SCnFfCbOoJAAH9KrGJgA4240.jpg

从这里看出,我们内部逻辑的总用时平均才77毫秒,虽然最多有420毫秒,但是对于300的样本容量来说,这只能算极个别的特例的噪音数据,所以我们代码业务逻辑耗时正常,这就排除了(6)(9)

 

然后我们隔离掉自身业务逻辑,又重新做了一个接口,让其只调用技术部的web service,这次发现其图很有规律(规律我后面会说),此图如下:

wKioL1SCnJ7gKO4zAA3_8c_Z-K0711.jpg

发现它最多还是会用时5秒,而且几乎类似脉冲。

 

 

步骤3:确定需要深入剖析的可疑点。

这样看来,可疑点有三段:

  1. (7)(8)。也就是应用服务器(Tomcat)通过Http(https) Connector转发请求到Spring容器,然后Spring容器通过Spring框架的DispatcherServlet转发请求到相应的bean的业务方法。

  2. (10)这段。也就是从Spring中的bean方法发送请求mapi.XXX.com这个web service调用中间的网络。

  3. (11)这段。也就是技术部接口本身调用服务的耗时。

 

当然,大家brainstorming之后还有一些假设。

  1. JAVA平台本身的性能问题。

  2. 代码中Web Service连接使用后未关闭导致连接过多响应变慢。

  3. 内存泄露导致服务器性能急剧下降。

  4. Full GC导致特定时间内服务器性能奇差无比。

 

当然了,D这个可以忽略,因为JAVA作为一种平台语言,其高效的性能已经被无数例子证明了,要有严重性能问题早被发现了,不会到现在刚好被我们运气好才发现。

 

 

步骤4:排除技术部接口本身的问题(11这段)。

这方面感谢技术部专家的协助,他们送来了技术部接口本身内部调用的时间开销原始数据,样本容量在1400个左右,我们用代码分析并画图,可以看到以下曲线图:

wKioL1SCnMGDyGofAAU7U3SH_KI208.jpg

从曲线分布可以看到,虽然有极个别的要13秒的(可以认为超时了),其他时间基本都控制在1-2秒左右。 因为他们最终接口也要调用新浪IP地址库这些外部接口,所以个别的噪音数据也是能容忍的。所以这张图说明技术部接口工作正常。

 

 

步骤5:排除Web Service连接未关闭的问题:

我特地去看了下代码,发现WebService连接并发送GET请求后使用完被优雅的关闭了,如下:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  static  HttpClientResponsegetMethod(String url) {
        HttpClient client = newHttpClient();
        StringBuilder resContentBuf = newStringBuilder();
        GetMethod method = newGetMethod(url);
        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, newDefaultHttpMethodRetryHandler( 3 false ));
        intstatusCode =  0 ;
        try  {
            statusCode = client.executeMethod(method);
            if  (statusCode == HttpStatus.SC_OK || statusCode ==  400 ) {
                resContentBuf.append(method.getResponseBodyAsString());
            }
        catch  (Exception e) {
            e.printStackTrace();
        finally  {
            method.releaseConnection();
        }
        logger.info( "HttpClientUtil.getMethod,the json data:" +resContentBuf.toString());
        return  assembleResponse(resContentBuf.toString(),statusCode);
     }

而且如果是连接未关闭的话,应该是开始快,随着测试次数变多越来越慢,而不是像步骤2的第二张图那样,第一次耗时那么长(5秒)

 

 

步骤6:排除内存泄露导致的性能下降问题。

为此,我让同事再次运行单独调用技术部接口的web service 300次,在此期间通过5个时间点分别截取heapdump来分析内存泄露。

Step 1 ps –ef  | grep java 获取登录接口的进程ID

             Step2: cd $JAVA_HOME\bin

           jmap -dump:format=b,file=/tmp/heapdump.hprof   <pid>   把运行的进程ID传入,然后用jmap工具来获得堆dump信息。

然后利用EclipseMATMemory Analyze Tool)插件来分析可能的leak point ,此插件下载地址在http://download.eclipse.org/mat/1.3/update-site/,在截取了5个巨大的heapdump文件之后,我对比了这5个点(开始,中间1,中间2,中间3,结束)的内存状况和可能的leak点,(主要关注对象的 retained Size, 它表示释放该对象会额外释放多少内存空间)

开始:

wKioL1SCnTLyCywQAAIIxyP8fsM580.jpg

中间1:(2,3略了,几乎相同)

wKiom1SCnKyjA5vQAAIV6Wkc-po192.jpg

 

结束:

wKioL1SCnU_Bt9t8AAH4Eua0jxY293.jpg

 

而且几个SuspensionPoint,经过对象引用树的逐层深入排查,也和我们的代码无关,所以可以排除内存泄露的情况。

 

 

步骤7:排除GC导致的可能导致的延时。

为此,我在Tomcat启动时候,加了如下的JAVA_OPTION: -XX:+PrintGCApplicationStoppedTime,这个选项的作用是打印垃圾回收期间程序暂停的时间。

正如我所料,执行一次Full GC大概需要41,这比才5秒的波峰延迟要高出太多。显然不可能。

我把性能参数做了一些优化:

首先,因为Tomcat7 支持NIO,它性能比IO要高很多,所以我在Connector上配置了NIO

1
2
3
4
5
6
7
8
9
10
  
  < Connector  port = "xxx" 
               protocol = "org.apache.coyote.http11.Http11NioProtocol"  
               connectionTimeout = "20000" 
               URIEncoding = "UTF-8" 
               useBodyEncodingForURI = "true" 
               maxThreads = "768" 
               minSpareThreads = "64" 
               enableLookups = "false" 
               redirectPort = "8443"  />

 

请求延迟依然没本质改变,还是5秒左右波峰,不增也不减。

 

步骤8:排除应用服务器(Tomcat)通过Http(https) Connector转发请求到Spring容器,然后Spring容器通过Spring框架的DispatcherServlet转发请求到相应的bean的业务方法(78这段)。

虽然打印出一个请求从应用服务器中间件到Spring再到bean业务方法的时间请求很困难,但是可以做其他实验。我们拿步骤3中实现的只包含这个内部处理逻辑的bean方法为例子。让手机客户端或者客户端测试代码去请求这个bean 方法,获得的时间曲线大概如下:

wKiom1SCnQCh5q64AA6nSlUQEOc849.jpg

其平均用时在591毫秒,而且最长也不到2秒,减去步骤1的平均用时77毫秒,可以判断,应用服务器中间件和框架层面的处理平均用时在500毫秒左右。这显然也排除了应用服务器和框架层面。

 

 

步骤9:查看应用服务器和技术部接口的通信。

因为其他部分都证明没问题了,所以用反证法得知这段肯定有问题。为了证明,前天我让同事写了2个新的webservice方法,一个是直接通过域名调用技术部接口的,一个是通过外网IP技术部接口的。

方法1publicstaticLoginUserInfoVo getUserInfoFromBLN(String loginName, String loginPassword)

方法2publicstaticLoginUserInfoVo getUserInfoFromBLNByIP(String loginName, String loginPassword){

非常可惜,第二个方法(通过IP的方法)无法调用成功,原因也很容易理解,肯定技术部接口也是有地址映射的,所以某个请求URL肯定是IP和端口组合然后映射到的地址,所以单纯靠IP替换无法发送成功。

 

昨天深入分析的时候,我又从步骤2的第二张图看出了一些规律:不仅此接口,几乎所有其他连接技术部的接口都是第一次调用很慢,大概5秒多,接下来的调用速度会很快。精确的过36秒后,出现第二次波峰,依次类推。因为每次都是36秒间隔,这个更像是在哪里配置的。但这个可以确定就是应用服务器到技术部接口的通信通道上。

 

最后我们找到了原因,原来在应用服务器上配置的DNS是114.114.114.114,这是个公共DNS服务。它导致我们首次请求技术部接口时候要往外网绕一圈才回来。这就解释了为什么所有图的第一次请求都要超过5秒。随后,这个DNS解析结果缓存在应用服务器上,并且缓存时间间隔在36秒左右。这就解释为什么接下来的36秒内每次请求速度都极快(在500毫秒以内),然后直到精确的36秒才出现第二个大于5秒的波峰。

 

步骤10:重做实验验证结论。

我们重更新了/etc/hosts文件,因为其解析优先级高于DNS解析,所以请求不再通过114.114.114.114从外网兜圈再回技术部接口。我们又把其他调用技术部接口的场景都做了测试。果然经过改动后,图形再也不是一开始的脉冲了,而且很稳定,最长也就1.5秒,平均下来也就600毫秒的样子,完全满足我们要求,说明我们这个问题完美解决了。





本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/1587045,如需转载请自行联系原作者
目录
相关文章
|
6月前
|
新零售 供应链 小程序
复购见单模式小程序开发系统|细节方案
人们之所以喜欢在网上购物,最主要的是价格便宜,而且还能够在短时间内就拿到手
预约抢单互助系统开发详细功能/需求方案/步骤功能/逻辑项目/源码案例
The development model of appointment and order grabbing mutual assistance system is a widely used development model on mutual assistance service platforms. It adopts a combination of appointment and order grabbing modes, allowing users to make appointments or actively participate in mutual assistanc
dapp互助预约排单系统开发步骤指南/案例设计/规则详细/方案逻辑/源码程序
-Determine the core functions and objectives of the system, understand user needs and expectations.
|
7天前
|
监控 安全 测试技术
构建高效的精准测试平台:设计与实现指南
在软件开发过程中,精准测试是确保产品质量和性能的关键环节。一个精准的测试平台能够自动化测试流程,提高测试效率,缩短测试周期,并提供准确的测试结果。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
38 1
|
7天前
|
监控 安全 测试技术
构建高效精准测试平台:设计与实现全攻略
在软件开发过程中,精准测试是确保产品质量的关键环节。一个高效、精准的测试平台能够自动化测试流程,提高测试覆盖率,缩短测试周期。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
27 0
|
28天前
|
敏捷开发 数据可视化 测试技术
解析软件项目管理:以板栗看板为例,其如何有效影响并优化软件开发流程
软件项目管理是一个复杂而重要的过程,涵盖了软件产品的创建、维护和优化。其核心目标是确保软件项目能够顺利完成,同时满足预定的质量、时间和预算目标。本文将深入探讨软件项目管理的内涵及其对软件开发过程的影响,并介绍一些有效的管理工具。
|
5月前
|
存储 小程序 前端开发
用云开发快速制作客户业务需求收集小程序丨实战
用云开发快速制作客户业务需求收集小程序丨实战
|
6月前
在线预约模式管理系统开发|功能方案|设计原理
商业模式是一种包含了一系列要素及其关系的概念性工具
|
6月前
|
C++ Python
量化交易系统开发详细步骤/需求功能/策略逻辑/源码指南
Developing a quantitative trading system involves multiple steps, and the following is a possible development process
|
6月前
|
Android开发 安全 iOS开发
量化交易/合约跟单系统开发策略需求/步骤逻辑/功能详细/成熟技术/源码指南
Developing a quantitative trading/contract tracking system requires detailed requirement planning. The following are possible requirement details: