为了账号安全,请及时绑定邮箱和手机立即绑定

protobuf太好用,java之父叫我改用grpc

标签:
JAVA Go

在一般项目架构中,前后端交互使用Json格式,后端服务间交互使用Protobuf格式。这样的原因是:

  1. 前端大多数框架对于Json格式的数据是可以直接渲染的
  2. 而后端数据交互一般是为了序列化和反序列化,考虑更多是并发,带宽等,又由于Google的gRPC框架集成了Protobuf,并且gRPC有跨语言、低带宽、HTTP/2等优点。目前主流的Go语言也是谷歌旗下的,Go+gRPC几乎是不二之选(你要是用thrift,当我在放屁)
    3.Spring Cloud的OpenFeign也是支持HTTP/2+Protobuf的,但是还是不能跨语言,这里就不展开说了。


Java版:

  1. 新建三个模块,login调sms,模拟一个登录发验证码,commons放公共的proto文件
<modules>
  <module>grpc-commons</module>
  <module>grpc-login</module>
  <module>grpc-sms</module>
</modules>
  1. 编写proto,一个SmsService接口、一个SmsRequest消息、一个SmsResponse消息。
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option java_package = "com.haowen.common.protobuf";
option java_outer_classname = "SmsProto";

option go_package = "../protobuf";

service SmsService {
	rpc SendSms (SmsRequest) returns (SmsResponse) {}
}

message SmsRequest {
  string phone = 1;
  string msg = 2;
}

message SmsResponse {
  string requestId = 1;
  bool isSuccess = 2;
  google.protobuf.Timestamp sentAt = 3;
}
  1. 因为要生成gRPC的Service类,所以需要借助protoc-gen-grpc-java插件,在cmomons模块的pom.xml添加插件
<dependencies>
  <!-- 用来兼容java17 -->
  <dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>1.3.5</version>
  </dependency>
</dependencies>
<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.7.1</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.21.7:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.54.1:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
  1. 点击编译,编辑会自动执行protoc-gen-grpc-java插件

image.png
target目录下就有我们生成的实体类和grpc的service类
image.png

  1. 接下来编写sms模块(server端),因为我添加了springboot的web,所以这里用@Service的形式来注入
@Service
public class SmsServiceImpl extends SmsServiceImplBase {
    @Override
    public void sendSms(SmsRequest request, StreamObserver<SmsResponse> responseObserver) {
        // 请求的参数
        System.out.println(request.getPhone());
        System.out.println(request.getMsg());
        // 返回的东西
        SmsResponse response = SmsResponse.newBuilder()
            .setRequestId(UUID.fastUUID().toString())
            .setIsSuccess(true)
            .setSentAt(Timestamps.fromMillis(System.currentTimeMillis()))
            .build();
        // 塞进去
        responseObserver.onNext(response);
        // 塞完,走吧
        responseObserver.onCompleted();
    }
}

启动类,gRPC的通信端口是90

public class GrpcSmsApp {
    private Server server;

    public static void main(String[] args) {
        SpringApplication.run(GrpcSmsApp.class, args);
    }

    /**
     * 启动grpc
     */
    @SneakyThrows
    @PostConstruct
    public void startGrpcServer() {
        server = ServerBuilder.forPort(90).addService(new SmsServiceImpl()).build().start();
    }

    @PreDestroy
    public void stopGrpcServer() {
        if (server != null) {
            server.shutdown();
        }
    }

}
  1. 接着写login模块(client端),创建连接并使用Bean进行管理。.newBlockingStub是最常用的阻塞请求。如需异步、双工请建立对应的stub
@Configuration
public class SmsService {
    @Bean
    SmsServiceGrpc.SmsServiceBlockingStub blockingStub() {
        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("localhost", 90)
                .usePlaintext() // 明文传输,生产用NettyChannelBuilder下的sslContext()
                .build();
        return SmsServiceGrpc.newBlockingStub(channel);
    }
}
  1. 写一个接口来测试
@RestController
@RequiredArgsConstructor
@RequestMapping("login")
public class LoginApi {
private final SmsServiceBlockingStub blockingStub;

    @PostMapping("sendLoginCode")
    String sendLoginCode(String phone) {
        SmsRequest request = SmsRequest.newBuilder()
                .setPhone(phone)
                .setMsg("你的验证码是:sb")
                .build();
        SmsResponse smsResponse = blockingStub.sendSms(request);
        if (!smsResponse.getIsSuccess()) {
            return "发送失败";
        }
        System.out.println("smsResponse = " + smsResponse);
        return smsResponse.getRequestId();
    }
}
  1. 用postman进行调用,正常发送和返回

image.png
login模块(client端)
image.png
sms模块(server端)
image.png

go版

  1. 保留Java的sms模块,我们用Golang调用试一试,把sms.proto移动到go项目目录下,安装protoc-gen-go-grpc插件来生成Go版本的Service层。
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option java_package = "com.haowen.common.protobuf";
option java_outer_classname = "SmsProto";

option go_package = "../protobuf";

service SmsService {
  rpc SendSms (SmsRequest) returns (SmsResponse) {}
}

message SmsRequest {
  string phone = 1;
  string msg = 2;
}

message SmsResponse {
  string requestId = 1;
  bool isSuccess = 2;
  google.protobuf.Timestamp sentAt = 3;
}

// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
// protoc --go_out=. --go-grpc_out=.  sms.proto

分别执行,安装插件并生成proto的Go文件。

// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
// protoc --go_out=. --go-grpc_out=.  sms.proto

执行后会生成
image.png

  1. 接下来编写一个调用方法,同样调用端口是90
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "grpc/protobuf"
    "log"
)

/*
go get -u google.golang.org/grpc
go get -u google.golang.org/grpc/credentials
*/

const (
    address = ":90"
)

func main() {
    // 设置一个连接
    conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatalf("连接失败: %v", err)
    }
    defer func(conn *grpc.ClientConn) {
        err := conn.Close()
        if err != nil {
            log.Fatalf("关闭连接失败: %v", err)
        }
    }(conn)
    // 创建一个SmsService的客户端
    client := protobuf.NewSmsServiceClient(conn)

    response, err := client.SendSms(context.Background(), &protobuf.SmsRequest{
        Phone: "110",
        Msg:   "哈哈哈",
    })
    fmt.Println(response, err)
}

  1. 运行main函数,这样就实现了一个简单的跨语言调用

image.png

为了显得文章不会特别臃肿,本文省略了模块层级的创建,相信聪明如你已经一看就会啦,如果有更好的建议,欢迎在评论区留言。
https://juejin.cn/post/7222096611635576891在上篇文章中,我们介绍了如何使用 Redis 存储 Protobuf 格式的数据。本文将介绍在 RPC 中也用上 Protobuf。

在一般项目架构中,前后端交互使用Json格式,后端服务间交互使用Protobuf格式。这样的原因是:

  1. 前端大多数框架对于Json格式的数据是可以直接渲染的
  2. 而后端数据交互一般是为了序列化和反序列化,考虑更多是并发,带宽等,又由于Google的gRPC框架集成了Protobuf,并且gRPC有跨语言、低带宽、HTTP/2等优点。目前主流的Go语言也是谷歌旗下的,Go+gRPC几乎是不二之选(你要是用thrift,当我在放屁)
    3.Spring Cloud的OpenFeign也是支持HTTP/2+Protobuf的,但是还是不能跨语言,这里就不展开说了。


Java版:

  1. 新建三个模块,login调sms,模拟一个登录发验证码,commons放公共的proto文件
<modules>
  <module>grpc-commons</module>
  <module>grpc-login</module>
  <module>grpc-sms</module>
</modules>
  1. 编写proto,一个SmsService接口、一个SmsRequest消息、一个SmsResponse消息。
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option java_package = "com.haowen.common.protobuf";
option java_outer_classname = "SmsProto";

option go_package = "../protobuf";

service SmsService {
	rpc SendSms (SmsRequest) returns (SmsResponse) {}
}

message SmsRequest {
  string phone = 1;
  string msg = 2;
}

message SmsResponse {
  string requestId = 1;
  bool isSuccess = 2;
  google.protobuf.Timestamp sentAt = 3;
}
  1. 因为要生成gRPC的Service类,所以需要借助protoc-gen-grpc-java插件,在cmomons模块的pom.xml添加插件
<dependencies>
  <!-- 用来兼容java17 -->
  <dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>1.3.5</version>
  </dependency>
</dependencies>
<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.7.1</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.21.7:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.54.1:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
  1. 点击编译,编辑会自动执行protoc-gen-grpc-java插件

image.png
target目录下就有我们生成的实体类和grpc的service类
image.png

  1. 接下来编写sms模块(server端),因为我添加了springboot的web,所以这里用@Service的形式来注入
@Service
public class SmsServiceImpl extends SmsServiceImplBase {
    @Override
    public void sendSms(SmsRequest request, StreamObserver<SmsResponse> responseObserver) {
        // 请求的参数
        System.out.println(request.getPhone());
        System.out.println(request.getMsg());
        // 返回的东西
        SmsResponse response = SmsResponse.newBuilder()
            .setRequestId(UUID.fastUUID().toString())
            .setIsSuccess(true)
            .setSentAt(Timestamps.fromMillis(System.currentTimeMillis()))
            .build();
        // 塞进去
        responseObserver.onNext(response);
        // 塞完,走吧
        responseObserver.onCompleted();
    }
}

启动类,gRPC的通信端口是90

public class GrpcSmsApp {
    private Server server;

    public static void main(String[] args) {
        SpringApplication.run(GrpcSmsApp.class, args);
    }

    /**
     * 启动grpc
     */
    @SneakyThrows
    @PostConstruct
    public void startGrpcServer() {
        server = ServerBuilder.forPort(90).addService(new SmsServiceImpl()).build().start();
    }

    @PreDestroy
    public void stopGrpcServer() {
        if (server != null) {
            server.shutdown();
        }
    }

}
  1. 接着写login模块(client端),创建连接并使用Bean进行管理。.newBlockingStub是最常用的阻塞请求。如需异步、双工请建立对应的stub
@Configuration
public class SmsService {
    @Bean
    SmsServiceGrpc.SmsServiceBlockingStub blockingStub() {
        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("localhost", 90)
                .usePlaintext() // 明文传输,生产用NettyChannelBuilder下的sslContext()
                .build();
        return SmsServiceGrpc.newBlockingStub(channel);
    }
}
  1. 写一个接口来测试
@RestController
@RequiredArgsConstructor
@RequestMapping("login")
public class LoginApi {
private final SmsServiceBlockingStub blockingStub;

    @PostMapping("sendLoginCode")
    String sendLoginCode(String phone) {
        SmsRequest request = SmsRequest.newBuilder()
                .setPhone(phone)
                .setMsg("你的验证码是:sb")
                .build();
        SmsResponse smsResponse = blockingStub.sendSms(request);
        if (!smsResponse.getIsSuccess()) {
            return "发送失败";
        }
        System.out.println("smsResponse = " + smsResponse);
        return smsResponse.getRequestId();
    }
}
  1. 用postman进行调用,正常发送和返回

image.png
login模块(client端)
image.png
sms模块(server端)
image.png

go版

  1. 保留Java的sms模块,我们用Golang调用试一试,把sms.proto移动到go项目目录下,安装protoc-gen-go-grpc插件来生成Go版本的Service层。
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option java_package = "com.haowen.common.protobuf";
option java_outer_classname = "SmsProto";

option go_package = "../protobuf";

service SmsService {
  rpc SendSms (SmsRequest) returns (SmsResponse) {}
}

message SmsRequest {
  string phone = 1;
  string msg = 2;
}

message SmsResponse {
  string requestId = 1;
  bool isSuccess = 2;
  google.protobuf.Timestamp sentAt = 3;
}

// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
// protoc --go_out=. --go-grpc_out=.  sms.proto

分别执行,安装插件并生成proto的Go文件。

// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
// protoc --go_out=. --go-grpc_out=.  sms.proto

执行后会生成
image.png

  1. 接下来编写一个调用方法,同样调用端口是90
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "grpc/protobuf"
    "log"
)

/*
go get -u google.golang.org/grpc
go get -u google.golang.org/grpc/credentials
*/

const (
    address = ":90"
)

func main() {
    // 设置一个连接
    conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatalf("连接失败: %v", err)
    }
    defer func(conn *grpc.ClientConn) {
        err := conn.Close()
        if err != nil {
            log.Fatalf("关闭连接失败: %v", err)
        }
    }(conn)
    // 创建一个SmsService的客户端
    client := protobuf.NewSmsServiceClient(conn)

    response, err := client.SendSms(context.Background(), &protobuf.SmsRequest{
        Phone: "110",
        Msg:   "哈哈哈",
    })
    fmt.Println(response, err)
}

  1. 运行main函数,这样就实现了一个简单的跨语言调用

image.png

为了显得文章不会特别臃肿,本文省略了模块层级的创建,相信聪明如你已经一看就会啦,如果有好的建议,欢迎在评论区留言。

Java版:

  1. 新建三个模块,login调sms,模拟一个登录发验证码,commons放公共的proto文件
<modules>
  <module>grpc-commons</module>
  <module>grpc-login</module>
  <module>grpc-sms</module>
</modules>
  1. 编写proto,一个SmsService接口、一个SmsRequest消息、一个SmsResponse消息。
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option java_package = "com.haowen.common.protobuf";
option java_outer_classname = "SmsProto";

option go_package = "../protobuf";

service SmsService {
	rpc SendSms (SmsRequest) returns (SmsResponse) {}
}

message SmsRequest {
  string phone = 1;
  string msg = 2;
}

message SmsResponse {
  string requestId = 1;
  bool isSuccess = 2;
  google.protobuf.Timestamp sentAt = 3;
}
  1. 因为要生成gRPC的Service类,所以需要借助protoc-gen-grpc-java插件,在cmomons模块的pom.xml添加插件
<dependencies>
  <!-- 用来兼容java17 -->
  <dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>1.3.5</version>
  </dependency>
</dependencies>
<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.7.1</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.21.7:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.54.1:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
  1. 点击编译,编辑会自动执行protoc-gen-grpc-java插件

target目录下就有我们生成的实体类和grpc的service类

  1. 接下来编写sms模块(server端),因为我添加了springboot的web,所以这里用@Service的形式来注入
@Service
public class SmsServiceImpl extends SmsServiceImplBase {
    @Override
    public void sendSms(SmsRequest request, StreamObserver<SmsResponse> responseObserver) {
        // 请求的参数
        System.out.println(request.getPhone());
        System.out.println(request.getMsg());
        // 返回的东西
        SmsResponse response = SmsResponse.newBuilder()
            .setRequestId(UUID.fastUUID().toString())
            .setIsSuccess(true)
            .setSentAt(Timestamps.fromMillis(System.currentTimeMillis()))
            .build();
        // 塞进去
        responseObserver.onNext(response);
        // 塞完,走吧
        responseObserver.onCompleted();
    }
}

启动类,gRPC的通信端口是90

public class GrpcSmsApp {
    private Server server;

    public static void main(String[] args) {
        SpringApplication.run(GrpcSmsApp.class, args);
    }

    /**
     * 启动grpc
     */
    @SneakyThrows
    @PostConstruct
    public void startGrpcServer() {
        server = ServerBuilder.forPort(90).addService(new SmsServiceImpl()).build().start();
    }

    @PreDestroy
    public void stopGrpcServer() {
        if (server != null) {
            server.shutdown();
        }
    }

}
  1. 接着写login模块(client端),创建连接并使用Bean进行管理。.newBlockingStub是最常用的阻塞请求。如需异步、双工请建立对应的stub
@Configuration
public class SmsService {
    @Bean
    SmsServiceGrpc.SmsServiceBlockingStub blockingStub() {
        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("localhost", 90)
                .usePlaintext() // 明文传输,生产用NettyChannelBuilder下的sslContext()
                .build();
        return SmsServiceGrpc.newBlockingStub(channel);
    }
}
  1. 写一个接口来测试
@RestController
@RequiredArgsConstructor
@RequestMapping("login")
public class LoginApi {
private final SmsServiceBlockingStub blockingStub;

    @PostMapping("sendLoginCode")
    String sendLoginCode(String phone) {
        SmsRequest request = SmsRequest.newBuilder()
                .setPhone(phone)
                .setMsg("你的验证码是:sb")
                .build();
        SmsResponse smsResponse = blockingStub.sendSms(request);
        if (!smsResponse.getIsSuccess()) {
            return "发送失败";
        }
        System.out.println("smsResponse = " + smsResponse);
        return smsResponse.getRequestId();
    }
}
  1. 用postman进行调用,正常发送和返回

login模块(client端)

sms模块(server端)

go版

  1. 保留Java的sms模块,我们用Golang调用试一试,把sms.proto移动到go项目目录下,安装protoc-gen-go-grpc插件来生成Go版本的Service层。
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option java_package = "com.haowen.common.protobuf";
option java_outer_classname = "SmsProto";

option go_package = "../protobuf";

service SmsService {
  rpc SendSms (SmsRequest) returns (SmsResponse) {}
}

message SmsRequest {
  string phone = 1;
  string msg = 2;
}

message SmsResponse {
  string requestId = 1;
  bool isSuccess = 2;
  google.protobuf.Timestamp sentAt = 3;
}

// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
// protoc --go_out=. --go-grpc_out=.  sms.proto

分别执行,安装插件并生成proto的Go文件。

// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
// protoc --go_out=. --go-grpc_out=.  sms.proto

执行后会生成

  1. 接下来编写一个调用方法,同样调用端口是90
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "grpc/protobuf"
    "log"
)

/*
go get -u google.golang.org/grpc
go get -u google.golang.org/grpc/credentials
*/

const (
    address = ":90"
)

func main() {
    // 设置一个连接
    conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatalf("连接失败: %v", err)
    }
    defer func(conn *grpc.ClientConn) {
        err := conn.Close()
        if err != nil {
            log.Fatalf("关闭连接失败: %v", err)
        }
    }(conn)
    // 创建一个SmsService的客户端
    client := protobuf.NewSmsServiceClient(conn)

    response, err := client.SendSms(context.Background(), &protobuf.SmsRequest{
        Phone: "110",
        Msg:   "哈哈哈",
    })
    fmt.Println(response, err)
}
  1. 运行main函数,这样就实现了一个简单的跨语言调用

为了显得文章不会特别臃肿,本文省略了模块层级的创建,相信聪明如你已经一看就会啦,如果有更好的建议,欢迎在评论区留言。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消