`
Finishx
  • 浏览: 4905 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

springmvc restful优化

 
阅读更多

SpringMVC RESTful 性能优化

使用RESTful风格的接口有如下优势:

  • 语言无关(这点对于我们Python+Java的后台系统很关键)
  • 开发效率高、调试方便
  • 接口的语义明确然而缺点也显而易见:基于HTTP的RPC在效率上不如传统的RPC。
    在ModelService中,我们使用SpringMVC框架来实现RESTful接口。但是,在最近一次对ModelService的更新中我们发现SpringMVC的RESTful接口性能存在问题。

RESTful:

@RequestMapping(path = "/list/cityId/{cityId}", method = RequestMethod.GET)
@ResponseBody
public String getJsonByCityId(@PathVariable Integer cityId)

非RESTful:

@RequestMapping(path = "/list/cityId", method = RequestMethod.GET)
@ResponseBody
public String getJsonByCityId(@RequestParam Integer cityId)

 

我们使用Apache JMeter对SpringMVC RESTful接口与非RESTful接口进行了性能测试:

RESTful接口:
测试结果

非RESTful接口:
测试结果

*并发量为200
*测试在同一台机器上进行,执行业务逻辑相同,仅接口不同。
*为了证明的确是SpringMVC造成的问题,我们使用了最简单的业务逻辑,直接返回字符串。

由结果可见,非RESTful接口的性能是RESTful接口的两倍,且请求的最大响应时间是35毫秒,有99%的请求在20毫秒内完成。相比之下,RESTful接口的最大响应时间是436毫秒。

由于ModelService是一个对并发性能要求极高的系统,且被多个上层业务系统所依赖,所有请求需在50ms内返回,若超时则会引起上层系统的read timeout,进而导致502。所以需要对这一情况进行优化。

方案一:将所有的url修改为非RESTful风格(不使用@PathVariable)

这是最直接的方式,也是最能保证效果的方式。但是这么做需要修改的是ModelService中已有的全部100+个接口,同时也要修改客户端相应的调用。修改量太大,而且极有可能由于写错URL导致404。更令人不爽的是这种修改会导致接口没有了RESTful风格。故该方案只能作为备选。

方案二:对SpringMVC进行改造

根据实际现象以及测试的结果,几乎可以确定的是问题出在SpringMVC的RESTful路径查找中。所以我们对SpringMVC中的相关代码进行了调查。

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
(spring-webmvc-4.2.3.RELEASE) 

 

路径匹配的过程中有如下代码:

List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
   addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
   // No choice but to go through all mappings...
   addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

 

        SpringMVC首先对HTTP请求中的path与已注册的RequestMappingInfo(经解析的@RequestMapping)中的path进行一个完全匹配来查找对应的HandlerMethod,即处理该请求的方法,这个匹配就是一个Map#get方法。若找不到则会遍历所有的RequestMappingInfo进行查找。

        这个查找是不会提前停止的,直到遍历完全部的RequestMappingInfo。在遍历过程中,SpringMVC首先会根据@RequestMapping中的headers, params, produces, consumes, methods与实际的HttpServletRequest中的信息对比,剔除掉一些明显不合格的RequestMapping。如果以上信息都能够匹配上,那么SpringMVC会对RequestMapping中的path进行正则匹配,剔除不合格的。

        接下来会对所有留下来的候选@RequestMapping进行评分并排序。最后选择分数最高的那个作为结果。所以使用非RESTful风格的URL时,SpringMVC可以立刻找到对应的HandlerMethod来处理请求。但是当在URL中存在变量时,即使用了@PathVariable时,SpringMVC就会进行上述的复杂流程。

        从结果可见,这段匹配逻辑对性能的影响很大,URL数量越多,SpringMVC的性能越差,初步验证了我们从源码中得出的结论。在最近一次ModelService的更新中,接口数量翻了一倍,导致性能下降了一半,这也符合我们的结论。考虑到未来ModelService的接口必定会持续增加,我们肯定不能容忍在请求压力不断增加的情况下ModelService的性能反而不断下降的情况。所以现在我们要做的就是防止SpringMVC执行这种复杂的匹配逻辑,找到一种方式可以绕过它。

通过继承

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

我们可以实现自己的匹配逻辑。由于ModelService已经服务化,所以每个接口都有一个服务名,通过这个服务名即可直接找到对应的方法,并不需要通过@RequestMapping匹配的方式。而在服务消费端,由于服务消费端是通过服务名进行的方法调用,所以在服务消费端可以很直接地获取到服务名,把服务名加到HTTP请求的header中并不需要对代码进行大量的修改。

最终方案:

服务端:

  1. 在每个@RequestMapping中添加接口对应服务名的信息。
  2. 实现自己定义的HandlerMethod查询逻辑,在HandlerMethod注册时记录与之对应的服务名,在查询时通过HTTP请求头中的服务名查表获得HandlerMethod。

客户端:

  1. 调用服务时将服务名加入到HTTP请求头中

分析:

  • 这样的查询时间复杂度是O(1)的,典型的空间换时间。理论上使用这样的查找逻辑的效率和非RESTful接口的效率是一样的。
  • 由于HandlerMethod的注册是在服务启动阶段完成的,且在运行时不会发生改变,所以不用考虑注册的效率以及并发问题。
  • SpringMVC提供了一系列的方法可以让我们替换它的组件,所以该方案的可行性很高。

实现细节:

我们要建立一个HandlerMethod与服务名的映射,保存在一个Map中。注意到在@RequestMapping中有一个name属性,这个属性并没有被SpringMVC用在匹配逻辑中。该属性是用来在JSP中直接生成接口对应的URL的,但是在AbstractHandlerMethodMapping.MappingRegistry中已经提供了一个name与Handler Method的映射,直接拿来用即可。所以我们只需要在每个接口的@RequestMapping中添加name属性,值为接口的服务名。在SpringMVC启动时会自动帮我们建立起一个服务名与Handler Method的映射。我们只要在匹配时从HTTP请求头中获取请求的服务名,然后从该Map中查询到对应的HandlerMethod返回。如果没有查询到则调用父类中的原匹配逻辑,这样可以保证不会对现有的系统造成问题。

*小细节:

因为RESTful接口存在@PathVariable,我们还需要调用handleMatch方法来将HTTP请求的path解析成参数。然而这个方法需要的参数是RequestMappingInfo,并不是HandlerMethod,SpringMVC也没有提供任何映射,所以我们还是要自己实现一个HandlerMethod => RequestMappingInfo的反向查询表。重写AbstractHandlerMethodMapping#registerMapping方法即可在@RequestMapping的注册阶段完成映射的建立。

1:自定义的MappingHandlerMapping

public class actMappingHandlerMapping extends RequestMappingHandlerMapping {
    private static Map<String, HandlerMethod> NAME_HANDLER_MAP = new HashMap<String, HandlerMethod>();
    private static Map<HandlerMethod, RequestMappingInfo> MAPPING_HANDLER_MAP = new HashMap<HandlerMethod, RequestMappingInfo>();

    @Override
    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        RequestMapping rMapping = AnnotationUtils.getAnnotation(method, RequestMapping.class);
        NAME_HANDLER_MAP.put(rMapping.name(), handlerMethod);
        MAPPING_HANDLER_MAP.put(handlerMethod, mapping);
        System.out.println("======================name=" + rMapping.name() + "=handlerMethod="
                + handlerMethod.toString());
        super.registerHandlerMethod(handler, method, mapping);
    }

    @Override
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        String api = request.getHeader("api");
        HandlerMethod handlerMethod = NAME_HANDLER_MAP.get(api);
        if (StringUtils.isNotBlank(api) && handlerMethod != null) {
            handleMatch(MAPPING_HANDLER_MAP.get(handlerMethod), lookupPath, request);
            return handlerMethod;
        }
        return super.lookupHandlerMethod(lookupPath, request);
    }

}
 2:Spring-servlet.xml配置添加
<bean name="handlerAdapter"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
	<property name="webBindingInitializer">
		<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
	                <property name="conversionService" ref="conversionService" />
		</bean>
	</property>
	<property name="messageConverters">
		<list>
			<ref bean="stringHttpMessageConverter" />
			<ref bean="fastJsonHttpMessageConverter" />
		</list>
	</property>
</bean>
<bean name="conversionService"
class="org.springframework.format.support.DefaultFormattingConversionService" />
<bean name="handlerMapping" class="com.mact.flter.actMappingHandlerMapping" />
 
 3:对ajax支持可能需要这个拦截器
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
            ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        httpResponse.setHeader("Access-Control-Allow-Headers", "api");
        if ("OPTIONS".equals(httpRequest.getMethod())) {
            httpResponse.setStatus(204);
            httpResponse.setHeader("Cache-Control", "no-cache");
        }
        filterChain.doFilter(request, response);
    }
 

 

感谢达达技术分析

 

分享到:
评论

相关推荐

    springmvc 实现restful

    在实际开发中,还需要考虑安全性、性能优化、版本控制等问题,确保RESTful服务的质量和稳定性。 总结来说,Spring MVC提供了强大的工具和支持,使得实现RESTful服务变得简单而高效。通过合理的资源设计和HTTP方法的...

    springmvc之restful风格CRUD

    在IT行业中,Spring MVC是一个广泛使用的Java Web框架,它为构建高效、可维护的Web应用程序提供了强大的支持。本文将深入探讨如何在Spring ...记得在实际开发中根据项目需求进行调整和优化,保持代码简洁和可维护性。

    spring 3.0 应用springmvc 构造RESTful URL 详细讲解

    在Spring 3.0框架中,Spring MVC是一个强大...`RestUrlRewriteFilter.java`可能是一个定制的过滤器,用于进一步优化和管理RESTful URL。通过合理地利用这些特性,开发者可以构建出高效、灵活且易于维护的RESTful服务。

    springmvc的RESTFul实现小案例

    在本教程中,我们将深入探讨如何...这只是一个简化的示例,实际项目中还需要考虑更多因素,如性能优化、异常处理、安全性等。通过不断学习和实践,你可以掌握更高级的RESTful设计原则和技巧,提升你的Web服务开发能力。

    前后端分离springmvc和RESTful理解.docx

    【前后端分离SpringMVC和RESTful理解】 1. **MVC模式详解** - MVC全称为Model-View-Controller,是一种将业务逻辑、数据处理和用户界面分离开的设计模式。 - Model(模型)是数据载体,通常用Java中的POJO类表示...

    Dubbo-Zookeeper-Netty-SpringMVC, 使用dubbo注册服务,netty做服务器,springmvc提供restful接口.zip

    最后,SpringMVC是Spring框架的一个模块,专门用于构建Web应用,特别是RESTful风格的API。在本项目中,SpringMVC被用来构建RESTful接口,提供HTTP服务,使得外部系统可以通过HTTP请求调用服务。SpringMVC的模型-视图...

    SpringMVC的RESTFulCRUD

    下面将详细介绍SpringMVC中RESTful CRUD的相关知识点。 首先,理解REST(Representational State Transfer,表述性状态转移)的概念。REST是一种网络应用程序的设计风格和开发方式,强调通过统一的资源标识符(URI...

    springMVC整合FastJson实现RestFul风格API涉及jar包

    3. **优化与最佳实践** - **使用FastJson配置**:可以通过FastJson提供的`JSONConfig`进行配置,比如设置日期格式、忽略空值等。 - **错误处理**:在处理JSON序列化和反序列化异常时,应捕获`JSONException`并给...

    SpringMVC专栏8、RESTful案例static静态资源

    4. **优化静态资源加载**: - 使用CDN(内容分发网络)服务可以提高静态资源的加载速度,尤其是对于地理位置分布广泛的用户。 - 压缩和合并CSS与JavaScript文件可以减少HTTP请求次数,提高页面加载速度。 - 使用...

    SpringMVC完整文档PDF

    此外,它可能还会讨论最佳实践,如如何有效地测试SpringMVC应用,以及如何优化性能。 通过学习这些文档,你可以全面了解SpringMVC的工作原理,掌握如何利用它来构建高效、可维护的Web应用程序。理解MVC模式能帮助你...

    SpringMVC5.0jar包集合

    6. **RESTful API增强**:SpringMVC 5.0进一步完善了对RESTful风格的支持,如使用`@GetMapping`、`@PostMapping`等简化HTTP方法的映射,以及`@RequestBody`和`@ResponseBody`用于JSON数据的交换。 7. **模板引擎...

    jetbrick-springmvc jar包(包含源码)

    5. **异常处理**:提供了一套统一的异常处理机制,将系统异常转化为用户友好的错误页面,便于进行异常管理和用户体验优化。 6. **国际化**:内置了便捷的国际化支持,可以轻松实现多语言环境下的应用开发。 7. **...

    图床 类似 七牛 restful + cxf + springmvc + webservice

    在实际开发中,我们需要考虑如何高效地管理和检索这些图片,例如使用文件名哈希来避免重名,以及采用适当的文件夹结构以优化存储和检索性能。 总的来说,构建一个类似七牛的图床服务,需要结合RESTful API的设计...

    springMVC使用文档

    - **RESTful 支持**:SpringMVC 对 RESTful 风格的 URL 请求提供了良好的支持,这使得构建 RESTful Web 服务变得非常简单。 #### SpringMVC 与 Struts2 的比较 - **入口机制**:SpringMVC 使用 Servlet 作为入口,...

    SpringMVC 4.0

    11. **MVC配置优化**:在SpringMVC 4.0中,可以使用Java配置替代XML配置,通过@Configuration和@Bean注解来定义bean和它们之间的关系,使得配置更清晰、更易于维护。 12. **Servlet 3.0支持**:SpringMVC 4.0充分...

    SpringMVC3.1实例源码

    Spring 3.1引入了一些重要的改进和优化,使得开发更高效,性能更卓越。 1. **SpringMVC架构** SpringMVC采用Model-View-Controller设计模式,负责处理HTTP请求,分发到相应的控制器,执行业务逻辑,并将结果渲染到...

    springMVC+spring4.0+mybatis3.1.1+restful

    3.1.1版本可能包含了一些bug修复和性能优化,让开发者在实际项目中能更好地利用MyBatis进行数据操作。 4. **RESTful接口**: REST(Representational State Transfer)是一种网络应用程序的设计风格和开发方式,基于...

    自定义的springMVC

    - **RESTful风格的URL**:通过配置SpringMVC,可以轻松实现RESTful风格的URL,以提供更加清晰的API。 以上就是关于自定义SpringMVC的一些关键知识点。在实际开发中,理解并灵活运用这些组件,可以极大地提高Web应用...

    精通springMVC

    此外,SpringMVC还支持RESTful风格的URL设计,方便构建Web服务。 总的来说,“精通SpringMVC”这本书很可能会涵盖SpringMVC的基本概念、配置、控制器设计、模型数据管理、视图解析、请求处理机制以及与其他Spring...

Global site tag (gtag.js) - Google Analytics