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

开发 前端
在之前的一次性能压测的时候我们发现一个细节问题,我们使用 spring boot 创建的 web rest 项目,使用默认 spring mvc 作为 web rest 框架。这在使用上没有太大问题,但是有一个影响性能的细节问题被发现了,说实话这个问题很难被发现。
  • 背景
  • spring boot 创建的默认 spring mvc 项目
  • 集成 JAX-RS 规范框架 Jersey

背景

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

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

[[211303]]

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
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

再创建一个简单的 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; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

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

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

Transfer-Encoding:chunked带来的性能问题就是访问一次数据需要不止一次的 http 请求,而一次 http 请求的成本也是比较大的。

(我没有通过抓包工具来测试具体哪种对象大小需要访问几次,感兴趣的可以自己试下。)

集成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 
  • 1.

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; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

这和原来在 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); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

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
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

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

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

责任编辑:未丽燕 来源: 王清培技术博客
相关推荐

2012-11-28 15:53:16

灾难恢复

2019-08-30 12:01:48

2017-02-08 09:51:27

JavaScript细节

2017-08-15 17:09:31

Linux命令

2024-02-29 07:48:55

Python编程语言上下文管理器

2019-01-18 13:22:10

布线事项网络

2009-11-17 16:14:28

无线路由器

2009-11-09 09:57:39

交换机路由器

2023-11-07 12:25:22

2014-04-10 16:33:48

iOS 7新特性

2017-01-15 15:13:37

Android性能优化优化点

2010-01-28 10:11:18

IT金饭碗

2022-11-29 12:11:25

2023-10-24 18:05:00

2013-01-22 09:21:28

云计算成本私房云

2013-08-01 13:55:55

Android 4.3新特性

2010-01-22 15:45:57

局域网交换机

2023-08-08 14:28:02

2013-06-07 08:48:37

Android开发注意事项

2022-07-18 13:37:10

网络安全数据泄露
点赞
收藏

51CTO技术栈公众号