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

简介:

引入:

当我们做的某支付系统,以一组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,如需转载请自行联系原作者
目录
相关文章
|
7月前
|
安全
dapp互助预约排单系统开发步骤指南/案例设计/规则详细/方案逻辑/源码程序
-Determine the core functions and objectives of the system, understand user needs and expectations.
预约抢单互助系统开发详细功能/需求方案/步骤功能/逻辑项目/源码案例
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
|
26天前
|
安全
合约跟单/一键跟单系统开发流程步骤丨功能详细丨案例设计丨源码指南
Contract tracking system (also known as one click tracking system) is a system that allows investors to automatically follow excellent traders or strategies for trading. The following are the main functions and steps for developing a contract tracking/one click tracking system
|
1月前
|
机器学习/深度学习 测试技术 API
iOS系统下轻松构建自动化数据收集流程
iOS系统下轻松构建自动化数据收集流程
21 0
|
2月前
|
Android开发 安全 iOS开发
量化交易/合约跟单系统开发策略需求/步骤逻辑/功能详细/成熟技术/源码指南
Developing a quantitative trading/contract tracking system requires detailed requirement planning. The following are possible requirement details:
|
8月前
|
新零售 人工智能 供应链
链动2+1开发运营版丨链动2+1系统开发案例详情/方案项目/功能说明/逻辑规则/源码程序
新零售是指传统零售业与互联网技术相结合,通过数字化、数据化和智能化的手段,重新定义和升级传统零售业态的模式和经营方式。
|
8月前
|
安全 Go 区块链
区块链游戏链游系统开发功能详情丨方案逻辑丨开发项目丨案例分析丨源码规则
 In recent years, with the continuous development of blockchain technology, NFTs (non homogeneous tokens) and DAPPs (decentralized applications) have emerged in the gaming industry.
|
10月前
|
API Python
  SleeFi睡眠链游开发部署规则【分析】
最近出来的边睡边赚(Sleep to Earn),其实是类似gamefi概念的变体,Sleep to Earn让玩家用户透过睡觉作为玩游戏的方式,或完成游戏内任务的替代方式来赚钱。
|
11月前
|
运维 安全 fastjson
【干货】自动化批量挖洞流程 之 四工具联动
【干货】自动化批量挖洞流程 之 四工具联动
342 0
|
移动开发 小程序
分析实时云渲染在小程序中可行性应用
实时云渲染是可以应用于小程序中的,因为小程序其实从技术角度来说,X小程序的本质是H5网页,其实和我们在电脑上打开网页道理差不多,只不过小程序还有些其他的特定。而云流化在网页中打开是基础功能,因此在将需要流化的3D应用程序流化出的地址直接内嵌于小程序中,即可实现小程序中打开服务器端的3D应用并进行实时的交互。
144 0
分析实时云渲染在小程序中可行性应用