gRPC是谷歌開源的一款RPC框架,基於HTTP/2協議,使用protocol buffer作爲接口定義語言(Interface Definition Language)和底層數據交換格式。gRPC最大的優點是能跨語言,因爲使用和語言無關的protobuf來定義接口,並且使用標準的http協議來進行傳輸,能使不同語言的服務端和客戶端互相兼容。
gRPC-java的github地址是https://github.com/grpc/grpc-java。它是gRPC的java語言實現。在它的源代碼中已經包含了一個example,裏面有最簡單的gRPC作爲rpc框架的demo。本文是基於它,探討單獨建立一個項目時,如何通過gRPC進行遠程過程調用。
一.下載gRPC源碼
可以直接下載github上的源碼,也可以先fork到自己的倉庫,再從自己的倉庫下載。推薦第二種。
二.通過Intellij新建一個基於maven的項目grpc-demo
三.pom文件中加入如下內容:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<grpc.version>1.19.0</grpc.version><!-- CURRENT_GRPC_VERSION -->
<protobuf.version>3.6.1</protobuf.version>
<protoc.version>3.6.1</protoc.version>
<!-- required for jdk9 -->
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
<scope>provided</scope> <!-- not needed at runtime -->
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-testing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.25.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<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:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireUpperBoundDeps/>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
以上內容是從grpc源代碼的example的pom中拷貝出來的。
三.編輯proto文件
在src/main下新建文件夾proto,並且新建文件grpc-demo.proto。添加如下內容:
syntax = "proto3";
package GrpcDemo;
option java_package="com.wts.grpc.demo";
option java_outer_classname="GrpcDemoProto";
option java_multiple_files=true;
service Greeter {
rpc sayHello(HelloRequest) returns (HelloReply){}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
proto文件的作用是通過它來自動生成實現rpc調用的輔助代碼。
簡單說一下這個文件的內容:
syntax指定了使用的是protobuf3這個版本;
java_package是指定了生成的輔助類所在的package;
java_outer_classname是生成的Protobuf描述類的類名;
java_multiple_files是指定是否將生成的類放到不同的文件中,如果選擇false,那麼所有類都會存在一個文件中(以內部類的形式)
service定義了要生成的rpc接口。
msssage則是定義了rpc接口要傳輸的對象。
四.編譯proto文件生成java類
在項目目錄下執行maven的compile命令(可以直接在idea的右側的maven選項卡中選擇compile)。會生成以下文件:
有了這些類,就可以實現rpc調用,將它們拷貝到源代碼目錄中對應的包下面。
四.編輯server和client類:
server:
package com.wts.grpc.demo;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;
public class GrpcDemoServer {
private static final Logger logger = Logger.getLogger(GrpcDemoServer.class.getName());
private Server server;
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GrpcDemoServer.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 IOException, InterruptedException {
final GrpcDemoServer server = new GrpcDemoServer();
server.start();
server.blockUntilShutdown();
}
static 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();
}
}
}
client:
package com.wts.grpc.demo;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GrpcDemoClient {
private static final Logger logger = Logger.getLogger(GrpcDemoClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public GrpcDemoClient(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build());
}
GrpcDemoClient(ManagedChannel channel) {
this.channel = channel;
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
public static void main(String[] args) throws InterruptedException {
GrpcDemoClient client = new GrpcDemoClient("localhost", 50051);
try {
String user = "world";
if (args.length > 0) {
user = args[0];
}
client.greet(user);
} finally {
client.shutdown();
}
}
}
這兩個類的代碼也是從grpc-java的example中拷貝過來的。
五.測試
執行GrpcDemoServer的main方法,控制檯輸出:
執行GrpcDemoClient的main方法,控制檯輸出
調用成功。
六.總結
本文根據grpc-java的源代碼中的示例,新建項目實現了基於gRPC的遠程調用。可以看到,因爲需要跨語言,所以gRPC需要藉助protobuf這種獨立的接口定義語言。定義好之後,通過不同語言的插件,可以生成對應語言的輔助類,通過輔助類可以實現遠程調用。gRPC的優點是跨語言,通過http2進行傳輸,擴展性強,有一定的前瞻性。確定是目前官方沒有自己的線程池,沒有負載均衡,也沒有很成熟的服務治理方案,需要使用者自己實現。