使用gRPC代替SpringCloud微服务项目中的RPC框架OpenFeign

一. 前言

前段时间一直在忙着另一门课程的SpringCloud微服务项目,其中各个微服务之间使用的是OpenFeign进行服务之间的接口调用。这时候刚好在网络程序设计的课程中学习到了gRPC这个RPC框架,便想着在项目中使用gRPC替代部分的OpenFeign客户端,于是便有了这篇文章。

二. 代码仓库

github地址:CampusForum项目

三. 关于gRPC和OpenFeign

在介绍两者之前先来看看RPC。

RPC(Remote Procedure Call)是远程过程调用协议,是一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。它使得开发者可以像调用本地方法一样调用远程的过程,从而简化了分布式系统的开发过程。

gRPC和OpenFeign都是RPC框架,它们实现RPC的具体工具或者库,它提供了一种透明调用机制,让使用者不必显式的区分本地调用和远程调用。然而,它们在实现细节和使用场景上有所不同:

gRPC

  • gRPC是Google开发的一个高性能、开源的RPC框架,其协议设计在HTTP/2上实现,可以提供更好的性能。
  • gRPC支持多种语言,包括Java、C++、Python等,这使得gRPC可以在多种语言环境中使用。
  • gRPC使用Protocol Buffers作为接口定义语言,这不仅可以用于定义服务接口,还可以用于定义服务接口的消息格式。

OpenFeign

  • OpenFeign是Spring Cloud中的一个子项目,用于简化HTTP API的开发。
  • OpenFeign主要用于微服务架构中,它可以使HTTP请求像本地方法调用一样简单。
  • OpenFeign的一个重要特性是其声明式的编程风格,开发者只需要定义一个接口并在接口上添加注解,就可以完成一个HTTP API的开发。

接下来介绍在SpringCloud微服务项目使用gRPC替代OpenFeign的具体过程。

四. 使用gRPC替代OpenFeign

1. 原OpenFeign客户端

在项目目录的resource-service\resource-client\src\main\java\com\ustc\resource\client下,具体代码如下,其中的uploadAvatarImagegetUniversityName这两个接口将会由user-service中的user-server微服务进行调用。后面将会由gRPC来实现这两个接口的远程调用。

@FeignClient(value = "resource-server", fallback = ResourceClientFuse.class, contextId = "ResourceClient")
public interface ResourceClient {

    /**
     * 上传图片接口
     *
     * @param file 图片
     * @return 图片链接
     */
    @PostMapping(value = "/private/resource/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    RestResult<String> uploadBlogImage(@RequestPart("file") MultipartFile file);

    /**
     * 上传头像接口
     *
     * @param file 头像图片文件
     * @param name 图片命名
     * @return 访问链接
     */
    @PostMapping(value = "/private/resource/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    RestResult<String> uploadAvatarImage(@RequestPart("file") MultipartFile file, @RequestParam("name") String name);

    /**
     * 通过院校代码获取名称
     *
     * @param schoolCode 院校代码
     * @return 高校名称
     */
    @GetMapping("/resource/university/name")
    RestResult<String> getUniversityName(@RequestParam("schoolCode") Integer schoolCode);

}

2. proto接口定义

对于gRPC,首先需要进行proto接口的定义。在本项目中,我把接口放在了common\common-grpc模块,该模块需要在pom文件中添加如下的依赖。

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <protobuf.version>3.21.7</protobuf.version>
        <protobuf-plugin.version>0.6.1</protobuf-plugin.version>
        <grpc.version>1.52.1</grpc.version>
</properties>

<dependencies>
     <dependency>
          <groupId>io.grpc</groupId>
          <artifactId>grpc-stub</artifactId>
          <version>${grpc.version}</version>
      </dependency>
      <dependency>
          <groupId>io.grpc</groupId>
          <artifactId>grpc-protobuf</artifactId>
          <version>${grpc.version}</version>
      </dependency>
      <dependency>
          <groupId>jakarta.annotation</groupId>
          <artifactId>jakarta.annotation-api</artifactId>
          <version>1.3.5</version>
          <optional>true</optional>
      </dependency>
</dependencies>

然后再添加如下的插件,gRPC是使用Protocol Buffers来定义接口的,所以我们只要编写对应的proto文件,即可通过下面的插件将编写的proto文件自动转化为对应的java类。

<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.7.0</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>${protobuf-plugin.version}</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

接着就是proto文件的编写,proto必须放在模块的src/main/proto文件夹下,因为插件会默认转换该文件夹下的proto文件,否则的话还要在配置插件时指定proto文件的位置。

创建一个resource.proto文件,接口定义如下:

syntax = "proto3";  //protocol buffers 的版本

option java_multiple_files = true;  //设置为 true,表示每一个 message 文件都会有一个单独的 class 文件
option java_package = "com.ustc.common.grpc";  //用于标识生成的 java 文件的 packagev
option java_outer_classname = "ResourceProto";  //用于指定 proto 文件生成的 java 类的 outerclass 类名。

package resource;  //用来定义 message 的包名,包名是用来唯一标识 message 的

//根据Feign客户端定义的服务接口
service ResourceActions {
  rpc getUniversityName (SchoolCode) returns (University);
  rpc uploadAvatarImage (AvatarImage) returns (ImageUrl);
}

message SchoolCode {
  int32 code = 1;
}

message University {
  string name = 1;
}

//由于protobuf中不支持java的MultipartFile类型,因此使用字节数组来传输MultipartFile
//这样在传输过程中MultipartFile的文件类型ContentType也会丢失,因此需要添加contentType参数
message AvatarImage{
  bytes image = 1;
  string name = 2;
  string contentType = 3;
}

message ImageUrl {
  string url = 1;
}

最后使用compilecompile-custom插件来生成对应的java对象。由于compile用来编译消息对象,compile-custom则依赖消息对象,生成接口服务。因此要先使用compile插件,再使用compile-custom插件。
在这里插入图片描述

3. gRPC服务端

在服务的调用方resource-service\resource-server中编写gRPC服务端代码。

首先还是需要在pom文件中导入如下的服务端依赖。第一个依赖是前面的common-grpc模块,需要大家导入自己对应的proto接口的模块。

<!--        grpc服务端的api-->
 <dependency>
     <groupId>com.ustc</groupId>
     <artifactId>common-grpc</artifactId> # 导入自己的proto接口的模块
     <version>1.0-SNAPSHOT</version>
 </dependency>
 <dependency>
     <groupId>net.devh</groupId>
     <artifactId>grpc-server-spring-boot-starter</artifactId>
     <version>2.14.0.RELEASE</version>
 </dependency>
 <!--        grpc服务端的api-->

然后在服务端提供gRPC方法的实现。这里需要使用@GrpcService注解去标注服务。

@GrpcService
public class ResourceActionsImpl extends ResourceActionsGrpc.ResourceActionsImplBase {

    @Resource
    private UniversityService universityService;

    @Resource
    private ImageService imageService;

    // 获得大学名称
    @Override
    public void getUniversityName(SchoolCode request, StreamObserver<University> responseObserver){
        String universityName = universityService.getUniversityName(request.getCode());
        responseObserver.onNext(University.newBuilder().setName(universityName).build());
        responseObserver.onCompleted();
    }

    // 上传头像
    @Override
    public void uploadAvatarImage(AvatarImage request, StreamObserver<ImageUrl> responseObserver){
        // 将request中的字节数组转化为MultipartFile文件
        InputStream inputStream = new ByteArrayInputStream(request.getImage().toByteArray());
        MultipartFile image = null;
        try {
            image = new MockMultipartFile(request.getName(), request.getName(), request.getContentType(), inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 调用图片服务进行上传
        String url = imageService.uploadAvatar(image, request.getName());
        // 传回URL参数
        responseObserver.onNext(ImageUrl.newBuilder().setUrl(url).build());
        responseObserver.onCompleted();
    }
}

最后,需要在application.yaml 中进行配置,将当前服务注册到 nacos 配置中心里。

grpc:
  server:
    port: 40001
spring:
  application:
    name: @artifactId@  # artifactId为resource-server
  cloud:
    nacos:
      server-addr: xxx.xxx.xxx.xxx:端口号 # nacos地址

4. gRPC客户端

在服务的调用方user-service\user-server中编写gRPC客户端代码。

首先需要在pom文件中导入如下的客户端依赖。第一个也是前面的common-grpc模块,注意这里的第二个依赖和服务端导入的第二个依赖是不同的。一个是grpc-server-spring-boot-starter,另一个是grpc-client-spring-boot-starter

<!--        grpc客户端的api-->
<dependency>
    <groupId>com.ustc</groupId>
    <artifactId>common-grpc</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-client-spring-boot-starter</artifactId>
    <version>2.14.0.RELEASE</version>
</dependency>
<!--        grpc客户端的api-->

然后就是客户端代码的编写,客户端在调用远程服务时,需要使用注解@GrpcClient("对应的服务端名称")才能将stub注入。

@Service
public class GrpcClientService {
    @GrpcClient("resource-server")
    ResourceActionsGrpc.ResourceActionsBlockingStub resourceActionsBlockingStub;

    // 获得大学名称
    public String getUniversityName(int schoolCode){
        University university = resourceActionsBlockingStub.getUniversityName(
                SchoolCode.newBuilder().setCode(schoolCode).build()
        );
        return university.getName();
    }

    // 上传头像
    public String uploadAvatarImage(MultipartFile file, String name){
        // 将MultipartFile转化为需要传输的ImageUrl类型后,调用远程服务
        ImageUrl imageUrl = null;
        try {
            imageUrl = resourceActionsBlockingStub.uploadAvatarImage(
                    AvatarImage.newBuilder()
                            .setImage(ByteString.copyFrom(file.getBytes()))
                            .setName(name)
                            .setContentType(file.getContentType())
                            .build()
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return imageUrl.getUrl();
    }

}

最后也需要将客户端在nacos上注册服务。

spring:
  application:
    name: @artifactId@ # artifactId为user-server
  cloud:
    nacos:
      server-addr: xxx.xxx.xxx.xxx:端口号 # nacos地址
grpc:
  client:
    resource-server:  # 这个需要是服务端的名称
      negotiation-type: plaintext # 表示这个服务的通信不使用TLS加密

5. 服务测试

最后,使用postman对gRPC替换的两个服务进行测试,user-server分别在登录服务和头像上传服务中使用到了gRPC的远程调用。通过图片可以看到,服务均能正常运行。说明确实可以使用gRPC在微服务项目中进行远程调用。
在这里插入图片描述

在这里插入图片描述

五. 总结

通过此次学习,成功将网络程序设计这门课程中学习到的gRPC的知识运用到了具体的项目中,使用了SpringBoot+Nacos+gRPC实现了微服务之间的远程调用。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值