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 服務,只須要將有性能瓶頸的接口改造下便可。

相關文章
相關標籤/搜索