web性能测试你问我答之性能问题定位

阿凡达2018-08-21 09:19

今天我们不细讲Spring MVC的架构,只是总结下性能测试过程中使用到的小经验以及遇到的性能问题。

架构图:

技巧总结

1. 快速找到url对应的Controller在哪里

eclipse中:ctrl-H,搜索找到的file search,通过url信息在java文件中搜索。

找到了Controller层就找到了整个url处理的方法入口,再通过类调用信息查看Service层的处理过程以及DAO层的处理过程。

2. 快速找到一个DAO层的方法对应的具体SQL

  • 每一个DAO类上面都有 @Repository("name") ,ctrl-shift-r查找repository对应的xml文件,然后方法中指定的id名,查找对应的sql

    getSqlSession().selectOne("name", schemeId);
    getSqlSession().update("name", map);
    getSqlSession().insert("name", coupon);
    
  • 或者也可以通过上面的方法,直接ctrl-h,基于id名在xml文档中搜索。

3. 阅读代码时常用的快捷键

eclipse IDE:

  • ctrl-o:显示当前类中的所有方法
  • ctrl-shift-r:基于类名进行查找
  • ctrl-h:搜索,Java Search,File Search等
  • ctrl-alt-h:或者右键open call hierarchy,查找当前方法的调用者信息

4. 查找对应的配置信息

  1. 在服务器上,配置信息都存储在classpath下,对应的tomcat实例,基本上都是在/webroot-online-Ins1/WEB-INF/classes目录下,可以通过文件名称找对应的配置信息,例如db.properties配置的都是数据库相关的信息。 找一些不很清楚存储位置的文件也可以 grep -R info * 进行查找
  2. 在本地eclipse中,基本上可以在代码上通过resource、conf等目录进行查找,实在不行也是可以ctrl-h 或者 ctrl-shift-r进行搜索的

Spring MVC遇到的问题

问题一:freemarker带来的性能开销

问题表象

接口响应时间太长

定位方法

  1. 分析接口的执行路径:nginx-tomcat-controller,发现controller的执行时间很短,但是tomcat层的执行时间很长
  2. 了解MVC的整个调用路径:controller执行完成后还是会去执行view层,生成对应的前端展示页面
  3. 通过spring的配置我们看到,该产品中使用的view层处理是freemarker

    <bean id="viewResolver" >
    <property name="contentType" value="text/html;charset=UTF-8"/>
    <property name="suffix" value=".ftl"/>
    <property name="exposeRequestAttributes" value="true"/>
    <property name="exposeSessionAttributes" value="true"/>
    <property name="allowRequestOverride" value="true"/>
    <property name="allowSessionOverride" value="true"/>
    
  4. 通过btrace采集freemarker层的处理时间,发现的确是大部分时间花在了view层上

    @OnMethod(
        clazz="+org.springframework.web.servlet.view.freemarker.FreeMarkerView",
        method="processTemplate",
        location=@Location(value=Kind.RETURN)
    )
    public static void onProcessorAction(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long d) {
        log = Strings.append(log, str(d/1000000));
        println(log);
        log = Strings.newStringBuilder(false);
    }
    

问题原因

导致该问题的主要原因是:页面ftl的代码过于复杂,导致freemarker解析模板类时,性能开销过多。

解决方法:

优化ftl文件,减少过多的逻辑处理。

问题二: Redirect导致的内存泄露

问题表象:

混合接口运行5-6个小时以上,出现频繁Full GC的现象。

定位方法:

  1. jmap dump出来内存信息
  2. 通过MAT分析内存泄露在哪里

问题原因:

  1. 通过上面的MAT分析结果,可以看到问题主要由于RedirectView引起的:org.sringframework.web.servlet.view.RedirectView_redirect:http://**
  2. SpringMVC会把redirect:xxx当成是一个view定义,每次都会缓存这个redirect的信息,如果重定向的是同个url则没有问题,但是如果每次的url都是不一样的,就会导致内存泄露。
  3. spring的buglist中可以找到相同情况的说明:https://jira.springsource.org/browse/SPR-10065

解决方法:

推荐使用RedirectAttributes管理参数。

In the example from the referenced blog post, the same redirect URL can be expressed as a URI template, which would ensure the same view name is used as the cache key every time.

In other words instead of:
    return "redirect:form.html?entityId=" + entityId;
Do this:
    return "redirect:form.html?entityId={entityId}";

In the above example, entityId can be a model attribute or if it is present as a URI variable on the current request, then it'll work fine.
In Spring 3.1+ it is actually preferable to use RedirectAttributes:

@RequestMapping(method = RequestMethod.POST)
public String onPost(RedirectAttributes attrs) {
    ...
    attrs.addAttribute(entityId, 123);
    return "redirect:form.html;   // resulting URL has entityId=123
}

The above would also work before Spring 3.1 as long as entityId is in the model. However, using RedirectAttributes is preferable. See the reference documentation for more detail on that.

问题三: 远程调用的连接池配置

问题表象:

  1. 接口TPS不高,TPS 不超过 20
  2. CPU使用率不高: 不超过20%
  3. 响应时间比较长,达到了1.5s

定位方法:

  1. 可以通过javaMethod排查,到底时间消耗在哪里?
  2. 可以通过jstack查看下,进程在做些什么。

  1. netstat查看到,到后端服务的链接只有2个

问题原因:

  1. 从上面堆栈看到,进程都处于等待链接的状态,是由于连接池不够用导致的
  2. 代码原因:Spring未配置的httpclient的连接池

解决方法:

   <bean id="HessianService" >
        <property name="httpInvokerRequestExecutor">
                <property name="httpClient">
                    <bean >
                            <bean >
                                <property name="params">
                                    <bean >
                                        <property name="maxTotalConnections" value="50000"/>
                                        <property name="connectionTimeout" value="15000"/>
                                        <property name="soTimeout" value="15000"/>
                                        <property name="defaultMaxConnectionsPerHost" value="50000"/>
                                    </bean>
                                </property>
                            </bean>
                    </bean>
                </property>
        </property>
    </bean>

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者侯本文授权发布。