spring rest 容易被忽視的後端服務 chunked 效能問題
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:chunked
這是 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 mvc 和 jersey rest 共同存在的問題,我們也不需要將所有的返回 chunked 的介面都改成 JAX-RS 的 rest 服務,只需要將有效能瓶頸的介面改造下即可。