前言

  一个项目在经历开发、测试、上线后,当时的用户规模还比较小,所以刚刚上线的项目一般会表现稳定。但是随着时间的推移,用户数量的增加,qps的增加等因素会造成项目慢慢表现出网页半天无响应的状况。在之前的工作中也恰巧遇到这个过程,当时对项目进行了很多性能测试和调优,今天借助博客园,将这次性能调优的过程进行整理后写成随笔,希望给广大Java后端开发的工程师提供帮助,也借此机会,对性能调优进行一些总结工作,达到备忘的目的。

测试工具与环境

性能测试工具

  • Loadrunner:一种预测系统行为和性能的负载测试工具。通过以模拟上千万用户实施并发负载及实时性能监测的方式来确认和查找问题,LoadRunner能够对整个企业架构进行测试。企业使用LoadRunner能最大限度地缩短测试时间,优化性能和加速应用系统的发布周期。 LoadRunner可适用于各种体系架构的自动负载测试,能预测系统行为并评估系统性能。
  • VisualVM:JDK的一个集成的分析工具。监控应用程序的性能和内存占用情况、监控应用程序的线程、进行线程转储(Thread Dump)或堆转储(Heap Dump)、跟踪内存泄漏、监控垃圾回收器、执行内存和CPU分析,保存快照以便脱机分析应用程序;同时它还支持在MBeans上进行浏览和操作。尽管 VisualVM自身要在JDK6以上的运行,但是JDK1.4以上版本的程序它都能被它监控。
  • Jstack:用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
  • Jps:是JDK 1.5提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。
  • Awr:Oracle的性能报告。

硬件环境

  192.168.19.28部署了Tomcat的web服务、通过分库将数据写入到192.168.19.34和192.168.19.35这两个不同的Oracle数据库实例中。

注册接口调优

测试场景

  使用Loadrunner模拟1000并发用户,以每10秒钟递增5个用户。

测试过程

测试现象一

  1. 刚开始tps有800左右,192.168.19.28的cpu利用率达到10%,192.168.19.34的cpu利用率达到3%,192.168.19.35的cpu利用率达到3%。
  2. 并发用户数达到80时,tps降到500,192.168.19.28的cpu利用率达到20%左右,192.168.19.34的cpu利用率达到80%,192.168.19.35的cpu利用率达到3%。
  3. 并发用户数达到100时,tps降到300,192.168.19.28的cpu利用率30%左右,192.168.19.34的cpu利用率90%以上,192.168.19.35的cpu利用率3%。
  4. 并发用户数超过200时,192.168.19.28的cpu利用率45%左右,tps稳定在150——200之间。
  根据以上观测数据,发现注册用到的数据库实例只在192.168.19.34上(由于分库的原因),所以192.168.19.35没有什么cpu的变化。但是192.168.19.34的的cpu利用率在并发用户100时,就达到90%以上,这明显是不能接受的。于是将注册服务中的数据库操作全部注掉,只保留业务逻辑,进行测试。

测试现象二

  无论并发用户是多少,直到最大的1000,tps始终稳定在1400左右,192.168.19.28的cpu利用率最大到50%,192.168.19.34的cpu利用率6%,192.168.19.35的cpu利用率3%。
  这次将注册服务中的业务逻辑注掉,只保留一条关于表t_user_info的插入操作,继续测试。

测试现象三

  1. 刚开始tps有800左右,192.168.19.28的cpu利用率10%,192.168.19.34的cpu利用率3%,192.168.19.35的cpu利用率3%。
  2. 并发用户数达到80时,tps降到600,192.168.19.28的cpu利用率20%左右,192.168.19.34的cpu利用率80%,192.168.19.35的cpu利用率3%。
  3. 并发用户数达到100时,tps降到500,192.168.19.28的cpu利用率30%左右,192.168.19.34的cpu利用率90%以上,192.168.19.35的cpu利用率3%。
  4. 并发用户数超过200时,192.168.19.28的cpu利用率45%左右,最后tps稳定在300——350。

使用VisualVM

根据以上测试数据,发现当注册接口只存在业务逻辑,不进行数据库操作时的tps正常,当操作数据库甚至只是对表t_user_info进行插入时,tps下降明显,尤其是192.168.19.34的cpu利用率达到90%以上,所以我怀疑是数据库方面的原因。为了进一步排查原因,给Tomcat增加以下Jmx配置:

JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=10207 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"

启动VisualVM远程连接, 通过监控Tomcat内存(如图1所示),发现之前已经配置好的minor GC垃圾回收状态平稳,full GC只是在Tomcat启动时出现一次,之后再也没有出现。(可以启动jpstat服务,安装visual GC更加直观)

图1 VisualVM远程监控Tomcat

  通过转储Tomcat线程(使用Jps和Jstack,VisualVM转储Tomcat线程没有反应),发现有大量的有关logback线程的阻塞。内容如下:

"http-8080-1000" daemon prio= tid=0x00007fdbe48c4000 nid=0x7217 waiting for monitor entry [0x00007fdb743d5000]
java.lang.Thread.State: BLOCKED (on object monitor)
at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:)
- waiting to lock <0x00000007c01ed840> (a ch.qos.logback.core.spi.LogbackLock)
at ch.qos.logback.core.rolling.RollingFileAppender.subAppend(RollingFileAppender.java:)
at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:)
at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:)
at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:)
at ch.qos.logback.classic.Logger.callAppenders(Logger.java:)
at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:)
at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:)
at ch.qos.logback.classic.Logger.info(Logger.java:)
at com.uuzz.los.gateway.web.controller.RequestProcessorSupport.readHead(RequestProcessorSupport.java:)
at com.uuzz.los.gateway.web.controller.RequestProcessorSupport.process(RequestProcessorSupport.java:)
at sun.reflect.GeneratedMethodAccessor71.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:)
at java.lang.reflect.Method.invoke(Method.java:)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doInvokeMethod(HandlerMethodInvoker.java:)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:)
at java.lang.Thread.run(Thread.java:)
由此可见是logback的日志输出对文件写锁导致。最后将logback改为异步方式,此阻塞再没有出现。
但是依然有大量的CobarSqlMapClientTemplate执行的线程等待,具体如下:
"http-8080-995" daemon prio= tid=0x00007f2580897000 nid=0x7e1b waiting on condition [0x00007f24e3933000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000788b82410> (a java.util.concurrent.CountDownLatch$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:)
at com.alibaba.cobar.client.support.execution.DefaultConcurrentRequestProcessor.process(DefaultConcurrentRequestProcessor.java:)
at com.alibaba.cobar.client.CobarSqlMapClientTemplate.executeInConcurrency(CobarSqlMapClientTemplate.java:)
at com.alibaba.cobar.client.CobarSqlMapClientTemplate.queryForObject(CobarSqlMapClientTemplate.java:)
at com.alibaba.cobar.client.CobarSqlMapClientTemplate.queryForObject(CobarSqlMapClientTemplate.java:)
at com.alibaba.cobar.client.CobarSqlMapClientTemplate.queryForObject(CobarSqlMapClientTemplate.java:)
at com.uuzz.los.account.dao.impl.UserInfoDaoImpl.getSequence(UserInfoDaoImpl.java:)
at com.uuzz.los.account.service.impl.AbstractUserInfoService.insertUserInfoAndUserDetail(AbstractUserInfoService.java:)
at com.uuzz.los.account.service.impl.AbstractUserInfoService$.doInTransaction(AbstractUserInfoService.java:)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:)
at com.uuzz.los.account.service.impl.AbstractUserInfoService.register(AbstractUserInfoService.java:)
at com.uuzz.los.gwservice.service.standard.impl.UserServiceClientImpl.register(UserServiceClientImpl.java:)
at com.uuzz.los.gateway.facade.impl.standard.UserRegisterFacadeImpl.perform(UserRegisterFacadeImpl.java:)
at com.uuzz.los.gateway.facade.impl.standard.UserRegisterFacadeImpl.perform(UserRegisterFacadeImpl.java:)
at com.uuzz.los.gateway.facade.impl.BusinessFacadeSupport.handleResponse(BusinessFacadeSupport.java:)
at com.uuzz.los.gateway.facade.impl.BusinessFacadeSupport.doBusiness(BusinessFacadeSupport.java:)
at com.uuzz.los.gateway.facade.impl.BusinessFacadeSupport.doBusiness(BusinessFacadeSupport.java:)
at com.uuzz.los.gateway.web.controller.RequestProcessorSupport.process(RequestProcessorSupport.java:)
at sun.reflect.GeneratedMethodAccessor47.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:)
at java.lang.reflect.Method.invoke(Method.java:)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doInvokeMethod(HandlerMethodInvoker.java:)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:)
at java.lang.Thread.run(Thread.java:)

可见是数据库资源阻塞引起。

使用AWR

  最后结合Oracle导出的AWR执行报告,发现SQL软解析为0.43,SQL硬解析为0.01,逻辑读85020.13,物理读0.01,物理写6.83,事务回滚率0.00%,缓存命中率100%,执行计划的Library命中率99.99%,内存排序100.00%,软解析99.18%。说明程序中的SQL没有硬编码,缓存利用率高,如图2所示。

图2  AWR执行报告部分内容一

最后查看Top 5 Timed Events,如图3所示。

图3  AWR执行报告中占用时间最长的事件

发现等待耗时排在第一的是log file sync ,正常情况下,应当是CPU time排第一。结合之前登陆接口中的insert语句,可以知道产生大量的commit的事务,产生大量的日志写。日志写由于系统原因,并发操作缓慢,最终导致插入缓慢。由运维DBA解决日志写的慢等待问题后,性能大幅提升。

如需转载,请标明本文作者及出处——作者:jiaan.gja,本文原创首发:博客园,原文链接:http://www.cnblogs.com/jiaan-geng/p/5093133.html

记一次Web服务的性能调优的更多相关文章

  1. 高性能 Java 计算服务的性能调优实战

    作者:vivo 互联网服务器团队- Chen Dongxing.Li Haoxuan.Chen Jinxia 随着业务的日渐复杂,性能优化俨然成为了每一位技术人的必修课.性能优化从何着手?如何从问题表 ...

  2. web移动端性能调优及16ms优化

    本文只是一个索引,收集了网络上大部分关于调试及优化方面的文章,从中挑选了一些比较好的文章分享给大家. 移动端性能不及桌面浏览器性能的10分之1,特别是在android设备良莠不齐的情况下,性能显得尤为 ...

  3. web前端性能调优(二)

    项目经过第一波优化之后APP端已基本已经符合我们的要求了,但是TV端还是反应比较慢,页面加载和渲染都比较慢了一点,我觉的还是有必要在进行一些优化,经过前面的优化,我们的优化空间已经小了一部分,不过还是 ...

  4. 性能调优之提高 ASP.NET Web 应用性能的 24 种方法和技巧

    性能调优之提高 ASP.NET Web 应用性能的 24 种方法和技巧   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对 ...

  5. 提高 web 应用性能之 CSS 性能调优

    简介 Web 开发中经常会遇到性能的问题,尤其是 Web 2.0 的应用.CSS 代码是控制页面显示样式与效果的最直接“工具”,但是在性能调优时他们通常被 Web 开发工程师所忽略,而事实上不规范的 ...

  6. Web服务端性能提升实践

    随着互联网的不断发展,日常生活中越来越多的需求通过网络来实现,从衣食住行到金融教育,从口袋到身份,人们无时无刻不依赖着网络,而且越来越多的人通过网络来完成自己的需求. 作为直接面对来自客户请求的Web ...

  7. [转]提高 web 应用性能之 CSS 性能调优

    简介 Web 开发中经常会遇到性能的问题,尤其是 Web 2.0 的应用.CSS 代码是控制页面显示样式与效果的最直接“工具”,但是在性能调优时他们通常被 Web 开发工程师所忽略,而事实上不规范的 ...

  8. web前端性能调优

    最近2个月一直在做手机端和电视端开发,开发的过程遇到过各种坑.弄到快元旦了,终于把上线了.2个月干下来满满的的辛苦,没有那么忙了自己准备把前端的性能调优总结以下,以方便以后自己再次使用到的时候得于得心 ...

  9. 记一次sql server 性能调优,查询从20秒至2秒

    一.需求 需求很简单,就是需要查询一个报表,只有1个表,数据量大约60万左右,但是中间有些逻辑. 先说明一下服务器配置情况:1核CPU.2GB内存.机械硬盘.Sqlserver 2008 R2.Win ...

随机推荐

  1. oracle11g rac asm存储数据迁移

    OS:rh6.4 ORACLE 11g RAC ASM OCR和VOTING DISK在crs磁盘组,控制文件.数据文件.参数文件在DATA组. 1.备份数据库 RUN {ALLOCATE CHANN ...

  2. Mono addin 学习笔记 2

    下面分析用xml描述文件的方式来进行插件定义 定义扩展点如下: public interface ISnippetProvider { string GetText (string shortcut) ...

  3. 使用CSS和jQuery实现对话框

    因为项目中要显示一些对话框,但用alert显得太丑,后从网上找了一些插件,但有觉得不好用,因此自己试用CSS和jQuery写了一个对话框,代码如下: <!DOCTYPE html> < ...

  4. openldap主机访问控制(基于用户组)

    建立组织单元 cat << _EOF_ | ldapadd -x -W -H ldaps://master.local -D cn=manager,dc=suntv,dc=tv dn: o ...

  5. Inside The C++ Object Model - 03

    object Lessons 1.C++中布局以及存取时间上的的额外负担是由virtual引起的:virtual function.virtual base class.或是由于多继承引起的. 2.C ...

  6. jquery源码学习之queue方法

    队列模块的代码结构 静态方法jQuery下有queue,dequeue,_queueHooks这三种方法:静态方法不建议直接在外部调用: 实例方法.queue,.dequeue,.clearQueue ...

  7. MySQL主从同步的延迟原理

    1. MySQL数据库主从同步延迟原理. 答:谈到MySQL数据库主从同步延迟原理,得从mysql的数据库主从复制原理说起,mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生binlo ...

  8. c++子类调用基类方法的一个例子

        Base.h #pragma once   class Base { public:     Base(void);     ~Base(void);     bool CreatClone( ...

  9. cannot access the system temp folder

    cannot access the system temp folder. please, make sure your application have full control rights on ...

  10. device tree 生成device node 到 platform_device

    http://blog.csdn.net/lichengtongxiazai/article/details/38942033 http://blog.csdn.net/mcgrady_tracy/a ...