spring boot 容易被忽视的后端服务 chunked 性能问题

标签(空格分隔): springboot springmvc chunked

作者:王清培(Plen wang) 沪江Java资深架构师


  • 背景
  • spring boot 创建的默认 spring mvc 项目
  • 集成 JAX-RS 规范框架 Jersey

背景

在之前的一次性能压测的时候我们发现一个细节问题,我们使用 spring boot 创建的 web rest 项目,使用默认 spring mvc 作为 web rest 框架。

这在使用上没有太大问题,但是有一个影响性能的细节问题被发现了,说实话这个问题很难被发现。

spring boot 创建的默认 spring mvc 项目

我们来看一个简单的 demo,我使用 IDEA 创建一个 spring boot 项目,创建过程中没有什么特别的选项需要调整,一路 next 。然后我们创建一个简单的 controller

package springboot.demo.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springboot.demo.model.User; /**
* Created by plen on 2017/11/25.
*/ @RestController
public class SpringMvcController { @RequestMapping("/user/{id}")
public User hello(@PathVariable Long id) { User user = new User();
user.setID(id);
user.setUserName("mvc."); return user;
}
}

再创建一个简单的 model

package springboot.demo.model;

import lombok.Data;
import lombok.EqualsAndHashCode; /**
* Created by plen on 2017/11/25.
*/
@Data
@EqualsAndHashCode
public class User {
private Long ID;
private String userName;
}

然后启动访问这个 controller ,注意看下返回的 http 信息里多了一个 Transfer-Encoding:chunkedTransfer-Encoding:chunkedHTTP 协议里的意思是无法计算 Content-Length 长度,需要分块传输。

这是 spring mvc 的默认 complex object 传输方式,如果我们返回的是一个简单的对象就不会有这个问题。

Transfer-Encoding:chunked 带来的性能问题就是访问一次数据在 __http__层面看确实是一次 http 请求,而通过 tcp 抓包工具查看会发现多了一次 tcp 传输。

集成 JAX-RS 规范框架 Jersey

解决这个问题两个层面都可以,一种是采用比较粗暴的方式在 servlet 容器层面解决,但是这个会带来一个后果就是当我们计算 complex object 大小的时候会比较复杂而且容易出错,也会影响项目未来的分块传输功能,效果不太好。

还有一种就是在应用层面解决,比较柔性也易于扩展,我们可以集成一个 rest 框架,最好是符合 JAX-RS 规范,本文我们集成 Jersey 框架。

jersey 集成如果通过 @Component 方式那么 jersey 会默认接管所有的 web servlet 请求处理,所以就需要我们手动的配置专门用来处理 jersey servlet 的容器。

spring boot 解决了以前 spring 繁重的配置,提供了 auto config 功能,原来通过 web.xml 配置 servlet 的,现在需要用代码来配置。spring boot 提供了让我们手动注册 servlet bean 的方式。

org.springframework.boot.web.servlet.ServletRegistrationBean

ServletRegistrationBean 可以让我们注册servlet,我们来看下完整代码。

package springboot.demo.config;

import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.servlet.ServletProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component; /**
* Created by plen on 2017/11/25.
*/
@Component
public class JerseyServletBeanConfig { @Bean
public ServletRegistrationBean jerseyServlet() { ServletRegistrationBean registrationBean = new ServletRegistrationBean(new ServletContainer(), "/rest/v1/*");
registrationBean.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, JerseyResourceConfig.class.getName()); return registrationBean;
}
}

这和原来在 web.xml 配置的是一样的,设置 routing 地址,设置 Init 初始化参数,对应的 servlet class name

所有的 "rest/v1/*" 请求都将被 ServletContainer jersey servlet 容器接管。

package springboot.demo.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
import springboot.demo.controller.JerseyController; /**
* Created by plen on 2017/11/25.
*/
public class JerseyResourceConfig extends ResourceConfig { public JerseyResourceConfig() {
register(JerseyController.class);
register(RequestContextFilter.class);
}
}

ResourceConfig 其实是一个 jersey Application 类型。这是 __jersey 注册 servlet 时规定的。

package springboot.demo.controller;

import springboot.demo.model.User;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType; /**
* Created by plen on 2017/11/25.
*/ @Path("/user/")
public class JerseyController { @Path("{id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
public User hello(@PathParam("id") Long id) { User user = new User();
user.setID(id);
user.setUserName("jersey."); return user;
}
}

这是我们应用代码 Controller ,使用 JAX-RS 规范的注解进行设置即可。

这样就解决了 sprng mvcjersey rest 共同存在的问题,我们也不需要将所有的返回 chunked 的接口都改成 JAX-RSrest 服务,只需要将有性能瓶颈的接口改造下即可。

最新文章

  1. [Android Pro] 常用的android工具类和库
  2. 完整成功配置wamp server小记
  3. Unity3D ShaderLab 立方体图的反射遮罩
  4. ListView的item选中效果
  5. 打包python脚本为exe可执行文件-pyinstaller和cx_freeze示例
  6. 在唯一密钥属性“value”设置为“Default.aspx”时,无法添加类型为“add”的重复集合项
  7. 【完结】利用 Composer 完善自己的 PHP 框架(三)——Redis 缓存
  8. 解决Maven不能下载“oracle、aspectjweaver、com.springsource.net.sf.cglib”jar
  9. UVA 12113 Overlapping Squares
  10. dojo 学习笔记
  11. 从源码分析java.lang.String.isEmpty()
  12. [转载]String.Empty、string=”” 和null的区别
  13. qr 生成二维码
  14. 客户关系管理系统CRM
  15. Sql server management studio: cannot find one or more components
  16. Ubuntu 16.04使用git
  17. 使用python 模仿mybinlog 命令 二进制分析mysql binlog
  18. 判断gridView是否有数据
  19. hdu4318阶梯博弈nim变形
  20. MongoDB的安装配置、基本操作及Perl操作MongoDB

热门文章

  1. Java集合源码分析(一)ArrayList
  2. OpenCV 学习笔记(模板匹配)
  3. 《SQL必知必会》学习笔记二)
  4. C# 7.0 特性
  5. mysql的读写分离
  6. Linux入门(17)——Ubuntu16.04显示内存CPU网速等(System Monitor)
  7. Spark 作业调度相关术语
  8. Asp.net MVC4高级编程学习笔记-模型学习第四课基架与模型绑定20171027
  9. Java基础笔记4
  10. Linux学习(四)单用户模式、救援模式、虚拟机克隆、linux互连(包括密匙登录)