在一般的项目架构中,前后端交互采用Json格式,后端服务间交互采用Protobuf格式。原因是:
- 大多数前端框架可以直接渲染Json格式的数据
- 后端数据交互一般是为了序列化和反序列化,更多的是考虑并发性、带宽等。因为Google的GRPC框架集成了Protobuf,GRPC有跨语言、低带宽、HTTP/2等优点。谷歌旗下也有主流的Go语言,Go+grpc几乎是最好的选择(如果你用thrift,当我放屁的时候)3.Spring Cloud的Openfeign也支持HTTP/2+Protobuf,但还是不能跨语言,这里就不说了。
Java版:
- login调整了三个新模块sms,在公共proto文件中模拟登录发送验证码,commons
<modules> <module>grpc-commons</module> <module>grpc-login</module> <module>grpc-sms</module></modules>
- 编写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;}
- 因为要生成grpc的Service类,所以需要使用protocce-gen-grpc-java插件,cmomons模块中的pomm.添加xml插件
<dependencies> <!-- 用于兼容java177 --> <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>
- 点击编译,编辑将自动执行protoc-gen-grpc-java插件
在target目录下,有我们生成的实体类和grpcservice类
- 然后编写sms模块(server端),因为我添加了springboot的web,所以这里以@service的形式注入
@Servicepublic 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(); } }}
- 然后写login模块(client端),创建连接并使用bean进行管理。.newblockingStub是最常用的阻塞请求。如需异步和双工,请建立相应的stub
@Configurationpublic class SmsService { @Bean SmsServiceGrpc.SmsServiceBlockingStub blockingStub() { ManagedChannel channel = ManagedChannelBuilder .forAddress("localhost", 90) .usePlaintext() // 明文传输,在NettyChanelbuilder下生产sslcontext() .build(); return SmsServiceGrpc.newBlockingStub(channel); }}
- 写一个接口进行测试
@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(); }}
- 调用postman,正常发送和返回
login模块(client端)
server端sms模块(server端)
go版
- 保留Java的sms模块,我们用Golang调用sms模块.将proto移动到go项目录下,安装protoc-gen-go-Go版本的Service层由Grpc插件生成。
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
它将在执行后生成
- 接下来,写一个调用方法,调用端口也是90
package mainimport ( "context" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "grpc/protobuf" "log")/*go get -u google.golang.org/grpcgo 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)}
- 运行main函数,从而实现简单的跨语言调用
为了使文章不会特别臃肿,本文省略了模块级别的创建。我相信如果你已经看到了,你会很聪明。如果您有好的建议,请在评论区留言。