Spring Cloud Feign支持protobuf
背景Spring Cloud feign是伪RPC方式解决微服务间的调用。翻看FeignCloudFeign源码,可以看到Feign默认使用HttpUrlConnection; 代码在DefaultFeignLoadBalancedConfiguration 的Client.Default。在springboot中HttpMessageConverters 默认使用jackson2方式进行序列化和
背景
Spring Cloud feign是伪RPC方式解决微服务间的调用。翻看FeignCloudFeign源码,可以看到Feign默认使用HttpUrlConnection; 代码在DefaultFeignLoadBalancedConfiguration 的Client.Default。
在springboot中HttpMessageConverters 默认使用jackson2方式进行序列化和反序列化,详见 HttpMessageConvertersAutoConfiguration
正常情况下使用jackson2支持前后端开发基本没有什么问题,但是如果是微服务间频频通信,使用jackson2序列化和反序列化会占用不少系统资源,并且效率较差。 这里有个git地址来对比各种序列化和反序列化框架的性能 https://github.com/eishay/jvm-serializers/wiki,部分内容如下:
- Ser Time+Deser Time (ns)
- Size, Compressed size [light] in bytes
可见jackson在各种测试中都不占优势,但我们发现了protobuf性能均比较优益,考虑使用protobuf进行feign序列化和反序列化
客户端项目添加feign配置支持proto
@Configuration
public class MyProtoFeignConfiguration {
//Autowire the message converters.
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
//add the protobuf http message converter
@Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
//override the encoder
@Bean
public Encoder springEncoder(){
return new SpringEncoder(this.messageConverters);
}
//override the encoder
@Bean
public Decoder springDecoder(){
return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}
}
客户端项目声明feign client
@FeignClient(name = "FEIGN-SERVICE2-TEST", fallback = FeignTestProtoFallback.class, configuration = MyProtoFeignConfiguration.class)
public interface FeignProtoTestClient {
@RequestMapping(value = "/replyProto", method = POST, consumes = "application/x-protobuf", produces = "application/x-protobuf")
HeaderReply requestMessage(@RequestParam("name") String name);
}
创建proto文件
在srm/main下新建proto文件夹,创建test_grpc.prot文件如下:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.feign.test.feignservice2test.proto.dto";
//option java_outer_classname = "TestGrpcPerformance";
package com.feign.test.feignservice2test.proto.dto;
import "google/protobuf/timestamp.proto";
service TestPerformance {
rpc SayHello (TestRequest) returns (TestReply) {
}
}
message TestRequest {
string name = 1;
}
message HeaderReply {
repeated LineReply field1 = 1;
int32 field2 = 2;
google.protobuf.Timestamp field3 = 3;
string field4 = 4;
int32 field5 = 5;
string field6 = 6;
int32 field7 = 7;
string field8 = 8;
int32 field9 = 9;
string field10 = 10;
int32 field11 = 11;
string field12 = 12;
int32 field13 = 13;
string field14 = 14;
int32 field15 = 15;
string field16 = 16;
int32 field17 = 17;
string field18 = 18;
}
message LineReply {
repeated DistributionReply field1 = 1;
int32 field2 = 2;
google.protobuf.Timestamp field3 = 3;
string field4 = 4;
int32 field5 = 5;
string field6 = 6;
int32 field7 = 7;
string field8 = 8;
int32 field9 = 9;
string field10 = 10;
int32 field11 = 11;
string field12 = 12;
int32 field13 = 13;
string field14 = 14;
int32 field15 = 15;
string field16 = 16;
int32 field17 = 17;
string field18 = 18;
}
message DistributionReply {
string field1 = 1;
int32 field2 = 2;
google.protobuf.Timestamp field3 = 3;
string field4 = 4;
int32 field5 = 5;
string field6 = 6;
int32 field7 = 7;
string field8 = 8;
int32 field9 = 9;
string field10 = 10;
int32 field11 = 11;
string field12 = 12;
int32 field13 = 13;
string field14 = 14;
int32 field15 = 15;
string field16 = 16;
int32 field17 = 17;
string field18 = 18;
}
message TestReply {
HeaderReply field1 = 1;
int32 field2 = 2;
google.protobuf.Timestamp field3 = 3;
string name = 4;
}
添加POM依赖,以使用maven自动生成proto JAVA文件
添加pom依赖后,执行mvn install
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.M9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<extensions>true</extensions>
<configuration>
<!--默认值-->
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<!--默认值-->
<!--<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>-->
<outputDirectory>${project.build.sourceDirectory}</outputDirectory>
<!--设置是否在生成java文件之前清空outputDirectory的文件,默认值为true,设置为false时也会覆盖同名文件-->
<clearOutputDirectory>false</clearOutputDirectory>
<!--默认值-->
<temporaryProtoFileDirectory>${project.build.directory}/protoc-dependencies</temporaryProtoFileDirectory>
<!--更多配置信息可以查看https://www.xolstice.org/protobuf-maven-plugin/compile-mojo.html-->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
<!--也可以设置成局部变量,执行compile或test-compile时才执行-->
<!--<configuration>-->
<!--<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>-->
<!--<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>-->
<!--<temporaryProtoFileDirectory>${project.build.directory}/protoc-dependencies</temporaryProtoFileDirectory>-->
<!--</configuration>-->
</execution>
</executions>
</plugin>
</plugins>
</build>
客户端添加controller请求
@RestController
public class FeignProtoController {
public static final int THREAD_NUM = 5;
public static final int CALL_TIMES = 100000;
@Autowired
FeignProtoTestClient feignProtoTestClient;
@RequestMapping("/testProto")
public ResponseEntity testProto() {
HeaderReply headerReply;
Date beginDate = new Date();
System.out.println("begin:" + beginDate);
for (int i = 0; i < CALL_TIMES; i++) {
headerReply = feignProtoTestClient.requestMessage("world:" + i);
//System.out.println("name:" + headerReply.getField4());
}
Date endDate = new Date();
System.out.println("end:" + endDate);
System.out.println("time:" + (endDate.getTime() - beginDate.getTime()) / 1000 + " s");
return ResponseEntity.ok().build();
}
}
服务端添加proto支持
在spring application下添加如下:
@Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
服务端响应proto报文内容
需要添加跟客户端一样的proto和pom依赖,执行mvn install
service内容如下
@Component
public class FeignReplyProtoServiceImpl {
public HeaderReply reply(String name){
List<LineReply> lineReplyList = new ArrayList<LineReply>();
for (int i = 0; i < 10; i++) {
List<DistributionReply> distributionReplyList = new ArrayList<DistributionReply>();
DistributionReply distributionReply = null;
for (int j = 0; j < 10; j++) {
distributionReply = DistributionReply.newBuilder().setField1("我是第一列").setField2(2).
setField3(Timestamps.fromMillis(System.currentTimeMillis())).setField4("我是第四列").setField5(5).setField6("我是第六列").
setField7(7).setField8("我是第八列").setField9(9).setField10("我是第十列").
setField11(11).setField12("我是第十二列").setField13(13).setField14("我是第十四列").
setField15(15).setField16("我是第十六列").setField17(17).setField18("我是第十八列").
build();
distributionReplyList.add(distributionReply);
}
LineReply lineReply = LineReply.newBuilder().addAllField1(distributionReplyList).setField2(2).
setField3(Timestamps.fromMillis(System.currentTimeMillis())).setField4("我是第四列").setField5(5).setField6("我是第六列").
setField7(7).setField8("我是第八列").setField9(9).setField10("我是第十列").
setField11(11).setField12("我是第十二列").setField13(13).setField14("我是第十四列").
setField15(15).setField16("我是第十六列").setField17(17).setField18("我是第十八列").build();
lineReplyList.add(lineReply);
}
HeaderReply headerReply = HeaderReply.newBuilder().addAllField1(lineReplyList).setField2(2).setField3(Timestamps.fromMillis(System.currentTimeMillis()))
.setField4(name).setField5(5).setField6("我是第六列").
setField7(7).setField8("我是第八列").setField9(9).setField10("我是第十列").
setField11(11).setField12("我是第十二列").setField13(13).setField14("我是第十四列").
setField15(15).setField16("我是第十六列").setField17(17).setField18("我是第十八列").build();
return headerReply;
}
}
controller如下:
@RestController
public class FeignProtoServerController {
@Autowired
FeignReplyProtoServiceImpl feignReplyProtoService;
@RequestMapping("/replyProto")
public com.feign.test.feignservice2test.proto.dto.HeaderReply replyProto(String name) {
return feignReplyProtoService.reply(name);
}
}
执行客户端请求代码
使用postman发送请求 localhost:10001/testProto
经过10W次数请求测试,使用proto响应时间比feign用jackson2大概提升10%,但CPU等资源使用明显降低。
更多推荐
所有评论(0)