1.Grpc框架简介
Google开发的一个高性能、开源和通用的 RPC 框架,基于ProtoBuf(Protocol Buffers) 序列化协议开发,且支持众多开发语言。面向服务端和移动端,基于 HTTP/2 设计。
2.Grpc框架特性
- grpc可以跨语言使用。支持多种语言 支持C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等编程语言
- 基于 IDL ( 接口定义语言(Interface Define Language))文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
- 通信协议基于标准的 HTTP/2 设计,支持·双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
- 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
- 安装简单,扩展方便(用该框架每秒可达到百万个RPC)
3.Grpc执行流程
1、客户端(gRPC Stub)调用 A 方法,发起 RPC 调用。
2、对请求信息使用 Protobuf 进行对象序列化压缩(IDL)。
3、服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。
4、对响应结果使用 Protobuf 进行对象序列化压缩(IDL)。
5、客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。
4.Rpc与HttpAPI的优劣势
5. Grpc框架特性搭建
5.1.框架准备条件
①:JDK11(上文也提及基于 HTTP/2,JDK11之后才开始正式使用HTTP/2)
②:Protobuf插件(上文也提及基于protobuf序列化协议,用插件读.proto生成Stub代码)
5.2.工程搭建
①:spring-grpc:一个主工程做依赖管理
②:client:Grpc的客户端
③:server:Grpc的服务端
④:grpc-lib:通过.proto文件生成stub的java文件
5.3.各工程依赖
①:主工程依赖(此处的net.devh的两个starter集成了Grpc的一些繁琐配置,开包即用很方便)
另外感谢yindongnan作者为开源做出的贡献
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-grpc</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>client</module>
<module>server</module>
<module>grpc-lib</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.13.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.10.1.RELEASE</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.10.1.RELEASE</version>
</dependency>
</dependencies>
</project>
②:grpc-lib工程依赖(此处完全是抄别人的,主要是配置proto生成的stub路径等等)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-grpc</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>grpc-lib</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.4.1.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</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>
<!--设置grpc生成代码到指定路径-->
<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>
<!-- 设置多个源文件夹 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<!-- 添加主源码目录 -->
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.basedir}/src/main/gen</source>
<source>${project.basedir}/src/main/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
③:server端依赖(没什么特别的,client也雷同)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-grpc</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>client</artifactId>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>grpc-lib</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
5.4.编写proto文件
proto 3 语法不熟悉的可以去查一查
// 基于proto3版本
syntax = "proto3";
option java_multiple_files = true;
// 生成java代码的包路径
option java_package = "com.yuwenwen.grpc";
// grpc服务
service YuWenWenService{
// 一个查询接口
rpc CheckUser(ApiIn) returns (ApiOut) {}
}
// 定义接口入参数
message ApiIn{
string name = 1;
string code = 2;
int64 age =3;
}
// 定义接口返回参数
message ApiOut {
bool success = 1;
string auth=2;
}
5.5.compile生成Stub java代码
此处注意:在grpc-lib的工程下,java包和proto包保持平级。
compile代码之后就会按照指定的路径生成Stub代码
生成完的效果
5.6.Grpc服务端
①:application定义服务端的端口和名称
grpc:
server:
port: 8888
spring:
application:
name: grpc-server
server:
port: 8890
②:业务Service继承生成的java代码
在用工具生成的代码中(YuWenWenServiceGrpc.java)有很多内部类
选择继承 YuWenWenServiceImplBase.java并标注上starter中封装好的@GrpcService注解
注意:responseObserver.onNext方法被调用,可以向客户端输出数据,最后通过 responseObserver.onCompleted结束输出
package com.yu.service;
import com.yuwenwen.grpc.ApiIn;
import com.yuwenwen.grpc.ApiOut;
import com.yuwenwen.grpc.YuWenWenServiceGrpc.YuWenWenServiceImplBase;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class YuWenWenServiceImpl extends YuWenWenServiceImplBase {
@Override
public void checkUser(ApiIn request, StreamObserver<ApiOut> responseObserver) {
ApiOut rep = ApiOut.newBuilder().setAuth("admin").setSuccess(true).build();
responseObserver.onNext(rep);
responseObserver.onCompleted();
}
}
5.6.1. StreamObserver<T>深研究
这个接口有三个抽象方法,而且命名规则来看是用了观察者模式。
public interface StreamObserver<V> {
void onNext(V var1);
void onError(Throwable var1);
void onCompleted();
}
其中的onNext、onError以及onComplete⽅法均会调⽤内部的ServerCall实例发送消息。 具体发送逻辑实现这⾥不做深⼊,总之可以明确的是,这⾥的StreamObserver回调接⼝,其实现逻辑就是将消息发送⾄客户端,这就是观 察者的逻辑。再看下被观察者,也就是StreamObserver回调接⼝的调⽤⽅。其实就是实现类⾥返回值的⽣成的逻辑,我们需要根据 request取到参数,然后⽣成返回值,调⽤StreamObserver回调接⼝,来通知Grpc框架层发送返回值。⾄此服务端实现⽅法⾥的 StreamObserver已经清晰了:被观察的对象就是返回值,Grpc框架层是观察者,提供发送逻辑作为回调函数,实现类是被观察者,每⼀ 次返回值的⽣成都会调⽤回调函数通知Grpc。还有⼀点,StreamObserver接⼝的定义其实和stream息息相关。我们知道stream模式意 味着可以在⼀个连接中发送多条消息,所以该接⼝提供了onNext回调函数,该函数可以被多次调⽤,每⼀次对onNext的调⽤都代表⼀条消 息的发送。如果全部发送完了或者发送出错,那么就需要调⽤onError或者onComplete来告知对⽅本次stream已经结束。所以该接⼝的设 计也与stream的概念也完全契合。
③:启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.7.客户端
①:依赖Stub代码
RPC(Remote Procedure Call)的中文:远程过程调用
RPC框架追求的就是,两个服务之间的调用就如同调本地方法一样
package com.yuwenwen.controller;
import com.yuwenwen.grpc.ApiIn;
import com.yuwenwen.grpc.ApiOut;
import com.yuwenwen.grpc.YuWenWenServiceGrpc.YuWenWenServiceBlockingStub;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {
@GrpcClient("grpc-server")
YuWenWenServiceBlockingStub yuWenWenStub;
@RequestMapping(value = "/query")
public String queryUser() {
ApiOut apiOut = yuWenWenStub.checkUser(ApiIn.newBuilder().setAge(25).setCode("1124").build());
String result = apiOut.getSuccess() ? "successfully" : "failed";
return result;
}
}
②:application定义客户端和服务端的端口和名称
grpc:
client:
grpc-server:
// # gRPC服务端地址
address: static://localhost:8888
enableKeepAlive: true
keepAliveWithoutCalls: true
negotiationType: plaintext
spring:
application:
name: grpc-client
server:
port: 8891
③:启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
6.Postman测试
结果:
以上就可以完成一个简易版的Grpc框架以及服务之间的调用