基础知识理解
概念
Grpc是常用RPC框架的一种,为了更好的理解RPC,我们先从熟悉的http说起。
- Http和RPC的区别:
http接口是在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段;优点就是简单、直接、开发方便。利用现成的http协议 进行传输。
但是如果是一个大型的网站,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先(基于TCP协议的情况下)就是长链接,不必每次通信都要像http 一样去3次握手,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统 一化的操作。第三个来说就是安全性。最后就是最近流行的服务化架构、服务化治理,RPC框架是一个强力的支撑。 - RPC
RPC全称Remote Procedure Call,远程过程调用,类似于“动态代理”,只不过是代理方和被代理方位于了两个进程中。下面借用非常著名的一幅图来展示其调用过程。
gRPC:是常用RPC框架的一种,是Google的开源产品,是跨语言的通用型RPC框架,使用Go语言编写。 Java语言的应用同样使用了Netty做网络通信,Go采用了Goroutine做网络通信。序列化方式采用了Google自己开源的Protobuf。请求的调用和返回使用HTTP2的Stream。
交互流程
grpc使用Protobuf这种结构化数据存储格式进行通信和数据存储,具有语言无关、平台无关等特性。因此,我们可以将一个完整的grpc交互流程总结为4步:
- 通过.proto文件定义传输的接口和消息体
- 通过protocol编译器生成sever端和client端的stub程序
- 将请求封装成HTTP2的Stream
- 通过channel进行数据通信通道使用Socket进行数据传输
代码实现
语言:java
sever端:使用springboot框架整个Grpc,springboot版本2.2.7.RELEASE
client端: 不使用任何框架的java应用程序
代码流程
- 编写.proto文件定义服务
- 编译proto文件形成相应的java类
- 编写实现类集成相应的java类,实现服务逻辑
- 客户端实现调用, 测试
代码实现
- 引入所需依赖
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.lognet</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.30.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.23.0</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId><!--引入操作系统os设置的属性插件,否则${os.detected.classifier} 操作系统版本会找不到 -->
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.30.2:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>src/main/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- 编写.proto文件定义服务( protobuf 语法可参考博客:https://www.jitwxs.cn/60aca815.html)
在main目录下新建proto文件夹,(这里文件夹的位置不要更改,否则编译时会找不到报错)。
NameService.proto
//表示每个message、枚举和service都会被生成一个类,否则这些都会是java_outer_classname的内部类
option java_multiple_files = true;
package com.example.grpcdemo.grpc;
service NameService{
rpc getIdByName(Name) returns (Id){}
}
message Name{
string name = 1;//1:代表第一个参数
}
message Id{
string Id = 1;
}
- 编译proto文件
分别点击红框里的部分进行编译,不能只点一个。编译后形成
- 编写实现类集成相应的java类,实现服务逻辑
@GRpcService//注解一定要加
public class NameServiceImpl extends NameServiceGrpc.NameServiceImplBase
{
private Map<String, String> map = new HashMap<>();
private Logger logger = LoggerFactory.getLogger(NameServiceImpl.class);
public NameServiceImpl()
{
map.put("ss", "12");
map.put("zz", "34");
}
@Override
public void getIdByName(Name request, StreamObserver<Id> responseObserver)
{
logger.info("request is coming: " + request.getName());
Id id = Id.newBuilder().setId(getName(request.getName())).build();
responseObserver.onNext(id);// 用于向客户端返回结果
responseObserver.onCompleted();// 用于告诉客户端这次调用已经完成
}
public String getName(String name)
{
String id = map.get(name);
if (id == null)
return "00";
return id;
}
}
最后,配置文件中注明一下grpc调用的端口号:grpc.port=8087
至此,服务器端的代码完成,整体结构如下:
5. 客户端
同服务器端前3步一样,先导入依赖、将proto文件赋值过来,进行编译。
由于客户端我并未使用springboot框架,所以依赖的jar包有些许变化。
<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>grpc_client</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.23.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.23.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.30.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId><!--引入操作系统os设置的属性插件,否则${os.detected.classifier} 操作系统版本会找不到 -->
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<!-- <version>${protobuf-maven-plugin.version}</version>-->
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.30.2:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>src/main/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
写一个测试类
public static void main(String[] args) throws InterruptedException {
//根据ip和端口号建立连接
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8087).usePlaintext().build();
NameServiceGrpc.NameServiceBlockingStub nameServiceBlockingStub = NameServiceGrpc.newBlockingStub(channel);
args=new String[]{"ss","zz","ss","zz"};
for(String arg : args){
Name name = Name.newBuilder().setName(arg).build();
Id res = nameServiceBlockingStub.getIdByName(name);
System.out.println("get result from server: " + res.getId() + " as param is " + arg);
}
channel.shutdown().awaitTermination(2, TimeUnit.SECONDS);
}
}
以上亲测连接成功,有任何问题均可给我留言,在下刚刚接触这个,有不足之处希望见谅,欢迎指正。