Spring Cloud Feign支持protobuf

背景

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) image
  • Size, Compressed size [light] in bytes image

可见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>
                        <!--也可以设置成局部变量,执行compiletest-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等资源使用明显降低。

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,这里有一个简单的Spring Cloud Feign集成Protobuf的示例: 1. 首先,您需要在pom.xml文件中添加以下依赖项: ```xml <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <version>1.37.0</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>1.37.0</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>1.37.0</version> </dependency> ``` 2. 创建Protobuf定义文件 在src/main/proto目录下创建一个名为`hello.proto`的文件,并添加以下内容: ```protobuf syntax = "proto3"; package io.github.xyz.spring.cloud.feign.protobuf.example.api; message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } service HelloService { rpc hello(HelloRequest) returns (HelloResponse); } ``` 3. 生成Java代码 在pom.xml文件中添加以下插件,用于生成Java代码: ```xml <build> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.12.4:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.37.0:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` 然后运行`mvn protobuf:compile`和`mvn protobuf:compile-custom`,将生成的Java代码放在`target/generated-sources/protobuf`目录下。 4. 创建Feign客户端 创建一个Feign客户端,用于调用HelloService服务。在使用Protobuf时,需要使用`@RequestLine`注解,以指定使用哪个HTTP方法,并使用`@Headers`注解,以指定使用哪种消息类型。 ```java @FeignClient(name = "hello-service", configuration = HelloClientConfiguration.class) public interface HelloClient { @RequestLine("POST /hello") @Headers({"Content-Type: application/x-protobuf", "Accept: application/x-protobuf"}) HelloResponse hello(HelloRequest request); } ``` 5. 创建Feign配置 创建一个Feign配置类,用于配置Feign客户端。在使用Protobuf时,需要将`Encoder`和`Decoder`设置为`ProtobufEncoder`和`ProtobufDecoder`,并将`Content-Type`和`Accept`设置为`application/x-protobuf`。 ```java @Configuration public class HelloClientConfiguration { @Bean public Encoder protobufEncoder() { return new ProtobufEncoder(); } @Bean public Decoder protobufDecoder() { return new ProtobufDecoder(); } @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } @Bean public Request.Options options() { return new Request.Options(5000, 10000); } } ``` 6. 创建服务端 创建一个服务端,用于提供HelloService服务。在使用Protobuf时,需要使用`@RequestBody`和`@ResponseBody`注解,以指定使用哪种消息类型。 ```java @RestController public class HelloController { @PostMapping("/hello") public HelloResponse hello(@RequestBody HelloRequest request) { return HelloResponse.newBuilder() .setMessage("Hello, " + request.getName() + "!") .build(); } } ``` 7. 配置Swagger 使用Swagger来测试Feign客户端。在Spring Boot应用中,可以使用Springfox Swagger来配置Swagger。 ```java @Configuration @EnableSwagger2 public class SwaggerConfiguration { @Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } } ``` 8. 启动应用 现在,您可以启动应用并访问http://localhost:8080/swagger-ui.html来测试Feign客户端了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值