grpc使用分析

Posted IT路人乙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了grpc使用分析相关的知识,希望对你有一定的参考价值。

这里为了探索grpc的使用方法,简单的假设工程有服务提供方和服务调用方,并且服务提供方简单的暴露一个SayHello的接口给服务调用方消费。项目划分如下

core-knowledge-rpc - 定义协议明细,接口实现

core-knowledge-rpc-callee - 服务提供方

core-knowledge-rpc-caller - 服务消费方

1 定义协议明细

core-knowlege-rpc负责定义协议明细和接口实现,工程目录结构如下

在工程中创建源文件目录src/main/proto目录存放.proto协议文件。

定义协议

这里定义了一个helloworld.proto文件,内容如下

syntax="proto3";

option java_multiple_files = true;
option java_package = "org.core.knowledge.delivery.grpc";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

文件第一句注明文件使用proto3的语法。协议中定义了一个Greeter服务,暴露SayHello接口,接收HelloRequest类型的输入,返回HelloReply类型的输出。HelloRequest类型携带了一个name的字符串信息;HelloReply类型返回一个message的字符串。

引入grpc依赖

这里grpc依赖netty网络通讯,pom依赖如下

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<grpc.version>1.25.0</grpc.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>${grpc.version}</version>
</dependency>

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>${grpc.version}</version>
</dependency>
</dependencies>

<build>
<extensions>
  <extension>
    <groupId>kr.motd.maven</groupId>
    <artifactId>os-maven-plugin</artifactId>
    <version>1.6.2</version>
  </extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
  <!-- use the Java 8 language features -->
  <source>1.8</source>
  <!-- want the compiled classes to be compatible with JVM 1.8 -->
  <target>1.8</target>
  <!-- The -encoding argument for the Java compiler. -->
  <encoding>UTF8</encoding>
</configuration>
  </plugin>
  <plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
<protocArtifact>com.google.protobuf:protoc:3.10.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.25.0:exe:${os.detected.classifier}</pluginArtifact>
    </configuration>
    <executions>
<execution>
<goals>
  <goal>compile</goal>
  <goal>compile-custom</goal>
</goals>
</execution>
    </executions>
  </plugin>
</plugins>
</build>

这里引入grpc的版本为1.25.0,依赖的netty打包到了grpc-netty-shaded库中。接着,执行mvn compile命令生成服务源码。在工程目录结构中,target/generated-sources/protobuf/javatarget/generated-sources/protobuf/grpc-java目录即为自动protoc工具自动生成的服务源文件。

实现Greeter接口

这里简单的将接收到的name字符串之前增加hello字符串返回,实现如下

GreeterImpl.java

public class GreeterImpl extends GreeterGrpc.GreeterImplBase{

@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello "+request.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}

}

2 服务提供方实现

core-knowledge-rpc-callee是服务提供方,依赖grpc将Greeter服务暴露到网络中。服务提供方主要依赖grpc库的api接口启动一个服务端ServerSocket,同时将服务实现GreeterImpl注册到grpc。详细实现如下

HelloWorldCallee.java

public class HelloWorldCallee {

private Server server;

private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl()).build().start();
Runtime.getRuntime().addShutdownHook(new Thread() {

@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldCallee.this.stop();
System.err.println("*** server shut down");
}

});
}

private void stop() {
if(server != null) {
server.shutdown();
}
}

private void blockUntilShutdown() throws InterruptedException {
if(server != null) {
server.awaitTermination();
}
}

public static void main(String[] args) throws InterruptedException, IOException {
HelloWorldCallee server = new HelloWorldCallee();
server.start();
server.blockUntilShutdown();
}
}

grpc提供ServerBuilder类构件Server并注册服务。当然,需要将服务实现引入到项目中,

<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core-knowledge-rpc</artifactId>
<version>${project.version}</version>
</dependency>

groupId和version值与服务提供方相同。

3 服务消费方实现

服务消费方只需要借助grpc的ManagedChannelBuilder.build()构件网络连接通道ManagedChannel;然后使用ManagedChannel构建GreeterBlockingStub实例即可调用远程服务sayHello方法。实现代码如下

public class HelloWorldCaller {

private ManagedChannel channel;
private GreeterGrpc.GreeterBlockingStub blockingStub;

public HelloWorldCaller(String host,int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext().build());
}

HelloWorldCaller(ManagedChannel channel){
this.channel = channel;
this.blockingStub = GreeterGrpc.newBlockingStub(channel);
}

public void shutdown() throws InterruptedException {
this.channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}

public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = this.blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
System.err.println(e.getMessage());
return ;
}
System.out.println("Greeting: "+response.getMessage());
}

public static void main(String[] args) throws InterruptedException {
HelloWorldCaller caller = new HelloWorldCaller("localhost", 50051);
@SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
try {
for(;;) {
System.out.print("please input message:");
String str = scanner.nextLine();
if("quit".equals(str)) break;
caller.greet(str);
}
}finally {
caller.shutdown();
}
}

}

main函数中,获取标准输入流中输入的字符串,作为远程服务的输入参数实现远程调用。调用结果如下

服务输入日志如下

16:37:33.332 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] INBOUND HEADERS: streamId=7 headers=GrpcHttp2RequestHeaders[:path: /helloworld.Greeter/SayHello, :authority: localhost:50051, :method: POST, :scheme: http, te: trailers, content-type: application/grpc, user-agent: grpc-java-netty/1.25.0, grpc-accept-encoding: gzip] streamDependency=0 weight=16 exclusive=false padding=0 endStream=false
16:37:33.333 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] INBOUND DATA: streamId=7 padding=0 endStream=true length=19 bytes=000000000e0a0c41726520796f75206f6b203f
16:37:33.335 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] OUTBOUND HEADERS: streamId=7 headers=GrpcHttp2OutboundHeaders[:status: 200, content-type: application/grpc, grpc-encoding: identity, grpc-accept-encoding: gzip] streamDependency=0 weight=16 exclusive=false padding=0 endStream=false
16:37:33.340 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] OUTBOUND DATA: streamId=7 padding=0 endStream=false length=25 bytes=00000000140a1248656c6c6f2041726520796f75206f6b203f
16:37:33.341 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] OUTBOUND HEADERS: streamId=7 headers=GrpcHttp2OutboundHeaders[grpc-status: 0] streamDependency=0 weight=16 exclusive=false padding=0 endStream=true

上面的示例显然: grpc很方便使用。可能你会问: 市面上已经存在很多的RPC框架,诸如JDK提供的RMI,基于HTTP协议二进制传输的Hessian,基于HTTP协议xml传输的Burlap及国产基于TCP协议的dubbo等,为毛还要使用grpc?RMI实现冗杂,Hessian和Burlap效率略低,dubbo加入了太多的业务属性不够单纯,grpc够单纯(连与spring集成的库都没有)够高效(依赖netty4+protobuffer)。选择grpc,为了代码优雅,你也就选择一片技术略高地。


以上是关于grpc使用分析的主要内容,如果未能解决你的问题,请参考以下文章

grpc-go客户端源码分析

grpc-go客户端源码分析

grpc client 源码分析

grpc Go Client 源码分析

grpc源码分析之域名解析

grpc框架源码分析