Bootstrap

十六.SpringCloudAlibaba极简入门-整合Grpc代替OpenFeign

前言

他来了他来了,停了快2个月了终于又开始更新文章啦,这次带来的绝对是干货!!!。由于公司项目进行重构的时候考虑到,OpenFeign做为服务通信组件在高并发情况下有一定的性能瓶颈,所以将其替换为更高性能的通信组件Grpc,Grpc也是业界比较流行的服务通信组件,底层采用HTTP/2进行网络通信性能上高于基于Http/1.1的OpenFeign。 我将把Grpc的整合过程整理成文字分享给大家,喜欢请三连!!!,你的鼓励是我坚持下去的动力。

认识Grpc

OpenFeign基于 HTTP/1.1 协议。主要使用 JSON 或 XML 格式进行数据交换,这些格式更加人性化但分装臃肿效率较低。其主要用于 RESTful API 调用,支持声明式的 Web 服务客户端。

而 gRPC 是一个高性能、开源和通用的 RPC(远程过程调用)框架,基于 Protocol Buffers 序列化协议开发,在内部实现上,它采用了 HTTP/2 协议作为传输层协议的一部分来实现高效的双向流通信的能力等特性让它成为很多开发者热衷的工具库之一;gRPC 在处理大量数据时表现优异,适用于需要快速响应的应用场景,特别是在微服务架构中。

gRPC:

  • 强调高效的数据传输和低延迟通信。如金融交易、在线游戏等对延迟要求极高的场景
  • 适合构建高性能的分布式系统和服务间通信。在大规模微服务架构中,gRPC 可以显著提高系统的整体性能。
  • 支持流式传输,可以实现长连接和实时数据推送。

OpenFeign:

  • 注重简化开发者的代码编写工作。
  • 更加灵活,可以轻松地集成到现有的 Spring Boot 项目中。
  • 适用于轻量级的 RESTful API 调用。

总结来说,gRPC 和 OpenFeign 各有优势,选择哪种技术取决于具体的业务需求和技术栈。如果你需要高性能、低延迟的服务间通信,gRPC 是更好的选择;如果你希望简化开发流程并快速集成 RESTful API,OpenFeign 则更为合适。

案例实战

1.搭建工程

首先搭建一个SpringBoot父工程,父工程下分别有:grpc-api ,grpc-provider ,grpc-consumer 三个模块,分别代表:Grpc接口,生产者,消费者。父工程管理的依赖如下

<properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>21</java.version>
        <skipTests>true</skipTests>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
        <hutool.version>5.8.28</hutool.version>
        <lombok.version>1.18.32</lombok.version>
    </properties>

    <!--SpringBoot依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
    </parent>

    <dependencies>
        <!--   常用工具类     -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <!--SpringCloud 2020.* 禁用了bootstrap 而注册中心等配置文件需要 bootstrap.yml 为名的配置文件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!--注解配置器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>

2.API模块

api模块需要导入grpc相关依赖,以及编写proto文件,我们根据proto文件自动生成Grpc的API接口文件,需要导入的依赖如下

<dependencies>
        <!-- https://mvnrepository.com/artifact/net.devh/grpc-server-spring-boot-starter -->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
            <version>3.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>3.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.34.1:exe:${os.detected.classifier}</pluginArtifact>
                    <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
                    <clearOutputDirectory>false</clearOutputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

grpc-server-spring-boot-starter 和 grpc-client-spring-boot-starter 是Grpc的服务端和客户端的依赖,javax.annotation-api是对Grpc代码生成注解的支持,不导入该包会报错。然后导入了os-maven-plugin插件,以及 protobuf-maven-plugin 插件用来根据proto文件生成API代码的。

接着我们再API模块中main目录下创建一个proto目录,并创建一个文件User.proto,内容如下

syntax = "proto3";
/** 生产的代码合并到一个文件 **/
option java_multiple_files = false;
/** 生产代码输出的包路径 **/
option java_package = "cn.whale.api.User";


/** API接口 **/
service UserApi {

  /** Grpc API 接口 **/
  rpc getById(GetUserReq) returns (GetUserRep) {}
}

/** 响应对象 **/
message GetUserReq {
  /** Long 用户ID **/
  int64 id = 1;
}

/** 结果对象 **/
message GetUserRep {
  /** Long 用户ID **/
  int64 id = 1;
  /** String 用户名字 **/
  string name = 2;
}

该文件是用来定义Grpc Api接口的,具体请看上面的注释,需要注意的是定义Protobuf 的 message 对象的字段类型可不能使用Java的类型,int64 对应Long, string 对应 String 具体请看:https://protobuf.com.cn/programming-guides/proto3/#specifying-types

编写好proto文件后,对api模块进行compile(IDEA 右侧 - api模块 - lifecycle - compile) ,然后会自动在API模块中生成Grpc相关的 API接口。
在这里插入图片描述

3.提供者服务

提供者服务首先是需要向Nacos注册(其他注册中心也行)的,然后导入api模块,编写Grpc的提供者服务,导入如下依赖

    <dependencies>
        <!--grpc api 模块 -->
        <dependency>
            <groupId>cn.whale</groupId>
            <artifactId>grpc-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!--nacos服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    
        <!--配置管理 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--grpc服务端依赖,api模块中引入了,这里其实可以不引-->
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-server-spring-boot-starter</artifactId>
            <version>3.0.0.RELEASE</version>
        </dependency>
    </dependencies>

接着对提供者服务进行配置,启动类正常写,没有什么新东西,bootstrap.yml配置如下

server:
  port: 8081 #tomcat 端口
grpc:
  server:
    port: 7071 #Grpc端口

  client: #Grpic客户端配置
    GLOBAL:
      negotiation-type: plaintext #协议类型,明文传输,也可以使用TLS进行加密传输,服务内部通信使用明文即可
      enable-keep-alive: true
      keep-alive-without-calls: true
spring:
  application:
    name: grpc-provider #服务名
  cloud:
    nacos:
      discovery: #服务注册
        server-addr: nacos地址:8848
      config: #配置管理
        server-addr: ${spring.cloud.nacos.discovery.server-addr}
        file-extension: yml

接下来编写提供者服务的Grpc实现,创建一个类UserApiImpl 继承UserApiGrpc,具体如下

package cn.whale.grpc;

import cn.whale.api.User.User;
import cn.whale.api.User.UserApiGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

/**
 * 标记Grpc提供者服务
 */
@GrpcService
public class UserApiImpl extends UserApiGrpc.UserApiImplBase {

    /**
     * 接口方法实现
     * @param request :请求对象
     * @param responseObserver : 消息流对象,用来响应结果
     */
    @Override
    public void getById(User.GetUserReq request, StreamObserver<User.GetUserRep> responseObserver) {
        long id = request.getId();
        //TODO :去数据查询数据
        //封装结果数据
        User.GetUserRep userRep = User.GetUserRep.newBuilder().setId(id).setName("zs").build();
        //响应结果
        responseObserver.onNext(userRep);
        responseObserver.onCompleted();
        //异常情况
        //responseObserver.onError(exception);

    }
}

  • @GrpcService :Grpc的服务端注解
  • UserApiGrpc.UserApiImplBase :根据Proto自动生成的API接口
  • getById :我们定义的API接口方法,具体请看代码注释

到这里提供者就编写完成了,启动提供者服务,可以看到Grpc的端口

在这里插入图片描述

4.消费者服务

消费者服务也是要做服务注册,然后引入api模块,以及Grpc客户端依赖,具体如下

<dependencies>
        <dependency>
            <groupId>cn.whale</groupId>
            <artifactId>grpc-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>3.0.0.RELEASE</version>
        </dependency>
    </dependencies>

编写启动类和创建bootstrap.yml文件

server:
  port: 8082
grpc:
  server:
    port: 7072
  client:
    GLOBAL:
      negotiation-type: plaintext
      enable-keep-alive: true
      keep-alive-without-calls: true
spring:
  application:
    name: grpc-consumer
  cloud:
    nacos:
      discovery:
        server-addr: nacos地址:8848
      config:
        server-addr: ${spring.cloud.nacos.discovery.server-addr}
        file-extension: yml

接着我们编写一个Conroller来实现和提供者的Grpc通信,代码如下

@RestController
@Slf4j
public class BusinessController {

    //指向提供者的服务名
    @GrpcClient(value = "grpc-provider")
    private UserApiGrpc.UserApiBlockingStub userApiBlockingStub;

    @GetMapping("/user")
    public String getUserById(){
        User.GetUserRep getUserRep = userApiBlockingStub.getById(User.GetUserReq.newBuilder().setId(1L).build());
        log.info("查询到用户信息,id =  {},name = {}",getUserRep.getId(),getUserRep.getName());
        return getUserRep.toString();
    }
}
  • @GrpcClient(value = “grpc-provider”) : 客户端注解,value指向了注册中心的提供者服务的名字,底层会自动进行负载均衡
  • UserApiGrpc.UserApiBlockingStub : Grpc 同步接口,它也支持异步调用UserApiGrpc.UserApiFutureStub

到这里消费者就编写完成了,启动消费者,通过浏览器访问/user,就可以拿到提供者返回的数据了

在这里插入图片描述

5.注意事项

最后说几个注意事项

  1. Grpc生成的对象只提供了Builder的方式设置值,所以没办法通过BeanUtils等工具进行对象之间的自动转换的,需要手动给Proto对象设置值。见生产者 User.GetUserRep
  2. 如果给Proto对象设置了空值会报错,所以在给对象设置值的时候建议先判断空值,然后给一个默认值,比如:String为null,就指定一个“”空字符串
  3. Grpc是无法返回一个null对象的,所以消费者端到底有没有拿到一个有效的结果不能直接用null来判断,需要取出具体的值来判断

就写到这把,剩下的坑大家自己去踩,如果文章对你有帮助请给个好评!!!

;