spring rest 容易被忽視的後端服務 chunked 性能問題

spring boot 容易被忽視的後端服務 chunked 性能問題

標籤(空格分隔): springboot springmvc chunked


  • 背景
  • 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 服務,只需要將有性能瓶頸的接口改造下即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章