当前位置: 首页 > 科技观察

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

时间:2023-03-20 14:17:39 科技观察

背景springboot创建的默认springmvc项目集成了JAX-RS规范框架Jersey背景在之前的一次性能压力测试中,我们发现了一个细节问题,我们使用springboot创建webrest项目使用默认的springmvc作为webrest框架。这在使用上不是什么大问题,但是有一个细节问题影响性能,说实话很难找到这个问题。我们先来看一个springboot默认创建的springmvc工程的简单demo。我使用IDEA创建一个springboot项目。创建过程中没有特殊选项需要调整,一直到next。然后我们创建一个简单的控制器。包springboot.demo.controller;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importspringboot.demo.model.User;/***Createdbyplenon2017/11/25.*/@RestControllerpublicclassSpringMvcController{@RequestMapping("/user/{id}")publicUserhello(@PathVariableLongid){Useruser=newUser();user.setID(id);user.setUserName("mvc.");returnuser;}}创建一个简单的模型。packagespringboot.demo.model;importlombok.Data;importlombok.EqualsAndHashCode;/***Createdbyplenon2017/11/25.*/@Data@EqualsAndHashCodepublicclassUser{privateLongID;privateStringuserName;}然后开始访问这个controller,注意返回的http信息一个更多传输编码:分块。Transfer-Encoding:chunked在HTTP协议中是指无法计算Content-Length长度,需要分块传输。这是springmvc默认的复杂对象传输方式。如果我们返回一个简单的对象,就不会有这样的问题。Transfer-Encoding:chunked带来的性能问题是一次访问数据需要多个http请求,一个http请求的成本比较高。(我没有用抓包工具多次测试需要访问哪个对象大小,有兴趣的可以自己试试)集成JAX-RS规范框架Jersey可以从两个层面解决这个问题。一种是使用粗略的方法,在servlet容器层面解决,但是这样会带来一个后果,就是我们在计算复杂对象的大小时,会比较复杂,容易出错,同时也会影响项目未来区块传输功能,效果不是很好。另一种解决方案是在应用层面,更加灵活,易于扩展。我们可以集成一个restframework,最好是符合JAX-RS规范的。在本文中,我们集成了Jersey框架。如果通过__@Component__集成jersey,jersey会默认接管所有的webservlet请求处理,所以我们需要手动配置专门用来处理jerseyservlet的容器??。springboot解决了以往spring繁重的配置,提供了autoconfig功能。servlet本来是通过web.xml配置的,现在需要用代码配置。Springboot为我们提供了一种手动注册servletbean的方式。org.springframework.boot.web.servlet.ServletRegistrationBeanServletRegistrationBean允许我们注册servlets,我们来看完整的代码。包springboot.demo.config;importorg.glassfish.jersey.servlet.ServletContainer;importorg.glassfish.jersey.servlet.ServletProperties;importorg.springframework.boot.web.servlet.ServletRegistrationBean;importorg.springframework.context.annotation.Bean;importorg.springframework.stereotype.Component;/***Createdbyplenon2017/11/25.*/@ComponentpublicclassJerseyServletBeanConfig{@BeanpublicServletRegistrationBeanjerseyServlet(){ServletRegistrationBeanregistrationBean=newServletRegistrationBean(newServletContainer(),"/rest/v1/*");registrationBean.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS,JerseyResourceConfig.class.getName());returnregistrationBean;}}这个和web.xml中原来的配置是一样的,设置路由地址,设置Init初始化参数,以及对应的servlet类名。所有__"rest/v1/*"__请求都将由ServletContainerjerseyservlet容器处理。packagespringboot.demo.config;importorg.glassfish.jersey.server.ResourceConfig;importorg.glassfish.jersey.server.spring.scope.RequestContextFilter;importspringboot.demo.controller.JerseyController;/***Createdbyplenon2017/11/25.*/publicclassJerseyResourceConfigextendsResourceConfig{publicJerseyResourceConfig(){register(JerseyController.class);register(RequestContextFilter.class);}}ResourceConfig其实是一个jerseyApplication类型。这是在__jersey注册servlet时指定的。包springboot.demo.controller;importspringboot.demo.model.User;importjavax.ws.rs.GET;importjavax.ws.rs.Path;importjavax.ws.rs.PathParam;importjavax.ws.rs.Produces;importjavax.ws.rs.core.MediaType;/***Createdbyplenon2017/11/25.*/@Path("/user/")publicclassJerseyController{@Path("{id}")@GET@Produces(MediaType.APPLICATION_JSON)publicUserhello(@PathParam("id")Longid){Useruser=newUser();user.setID(id);user.setUserName("jersey.");returnuser;}}这是我们的应用代码Controller,使用JAX-RS规范注解即可设置它。这样就解决了sprngmvc和jerseyrest的通病,我们不需要把所有返回chunked的接口都改成JAX-RSrest服务,只需要修改有性能瓶颈的接口即可。