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

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

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

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


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

背景

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

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

spring boot 建立的默认 spring mvc 项目

咱们来看一个简单的 demo,我使用 IDEA 建立一个 spring boot 项目,建立过程当中没有什么特别的选项须要调整,一路 next 。而后咱们建立一个简单的 controllerspring-mvc

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

再建立一个简单的 modelspringboot

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 传输方式,若是咱们返回的是一个简单的对象就不会有这个问题。mvc

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

集成 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 服务,只须要将有性能瓶颈的接口改造下便可。

相关文章
相关标签/搜索