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:chunked 。Transfer-Encoding:chunked 在 HTTP 协议里的意思是无法计算 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 mvc 和 jersey rest 共同存在的问题,我们也不需要将所有的返回 chunked 的接口都改成 JAX-RS 的 rest 服务,只需要将有性能瓶颈的接口改造下即可。




 本文转自 王清培 51CTO博客,原文链接:http://blog.51cto.com/wangqingpei557/2044336,如需转载请自行联系原作者



相关文章
|
7月前
|
JSON 前端开发 Java
构建健壮的Spring MVC应用:JSON响应与异常处理
构建健壮的Spring MVC应用:JSON响应与异常处理
35 0
|
2月前
|
Java Apache 开发者
【微服务】5、声明式 HTTP 客户端 —— Feign
【微服务】5、声明式 HTTP 客户端 —— Feign
46 0
|
4月前
|
负载均衡 前端开发 Java
字节后端面试题(前端发送请求到后端的过程(MVC),网关gateway作用,怎么解决跨域,各微服务组件作用)
字节后端面试题(前端发送请求到后端的过程(MVC),网关gateway作用,怎么解决跨域,各微服务组件作用)
142 0
|
5月前
|
缓存 Java 关系型数据库
Spring Boot实现RESTful接口架构实战(包括REST的讲解、定义、REST服务测试)
Spring Boot实现RESTful接口架构实战(包括REST的讲解、定义、REST服务测试)
55 0
|
7月前
|
Java Maven Spring
spring boot项目访问外部http请求的解决方案
spring boot项目访问外部http请求的解决方案
88 1
|
9月前
|
JSON API 网络架构
深度剖析 REST 内容协商
在 REST 架构中,内容协商(Content Negotiation)是一种机制,它可以使客户端和服务器之间协商确定传输的数据格式、编码方式、语言等内容。本文将详细介绍什么是 REST 内容协商,以及它的工作原理和应用场景。
|
11月前
|
XML JSON 缓存
【基于HTTP的远程调用框架 一】深度详解Retrofit2框架概念和使用
【基于HTTP的远程调用框架 一】深度详解Retrofit2框架概念和使用
316 0
|
11月前
|
Java 数据库连接 Android开发
【基于HTTP的远程调用框架 一】深度详解Retrofit2框架概念和使用(下)
【基于HTTP的远程调用框架 一】深度详解Retrofit2框架概念和使用(下)
69 0
|
12月前
|
JSON 负载均衡 Java
基于Feign远程调用、自定义Feign配置、性能优化及最佳实践
基于Feign远程调用、自定义Feign配置、性能优化及最佳实践
465 0
|
12月前
|
负载均衡 网络协议 Java
2023年主流的选择仍是Feign, http客户端Feign还能再战
2023年主流的选择仍是Feign, http客户端Feign还能再战,Feign并没有过时。Feign是一个基于HTTP的客户端,它可以帮助开发人员轻松地编写RESTful API客户端。它提供了一种简单的方式来定义和使用RESTful服务,而不需要手动编写HTTP请求和解析响应。Feign还提供了负载均衡和服务发现的支持,使得它在微服务架构中非常有用。虽然现在有一些新的HTTP客户端库出现,但Feign仍然是一个非常流行和可靠的选择
304 0