gRPC的学习及简单使用
RPC(Remote Procedure Call)远程过程调用。
参考连接
gRPC 是什么
gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
gRPC 默认使用 protocol buffers 作为接口定义语言,来描述服务接口和有效载荷消息结构,且支持众多开发语言。gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链接功能,从而有助于节省带宽、降低的TCP链接次数、节省CPU使用、和电池寿命。
gRPC具有以下重要特征
- 强大的IDL特性
gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。不过,当前gRPC仅支持 Protobuf ,且不支持在浏览器中使用。由于gRPC的设计能够支持支持多种数据格式,所以读者能够很容易实现对其他数据格式(如XML、JSON等)的支持。 - 支持多种语言
gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前,在GitHub上已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中 grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言,grpc-java已经支持Android开发。 - 基于HTTP/2标准设计
由于gRPC基于HTTP/2标准设计,所以相对于其他RPC框架,gRPC带来了更多强大功能,如双向流、头部压缩、多复用请求等。这些功能给移动设备带来重大益处,如节省带宽、降低TCP链接次数、节省CPU使用和延长电池寿命等。同时,gRPC还能够提高了云端服务和Web应用的性能。gRPC既能够在客户端应用,也能够在服务器端应用,从而以透明的方式实现客户端和服务器端的通信和简化通信系统的构建。
应用
gRPC已经应用在Google的云服务和对外提供的API中,其主要应用场景如下:
- 低延迟、高扩展性、分布式的系统
- 同云服务器进行通信的移动应用客户端
- 设计语言独立、高效、精确的新协议
- 便于各方面扩展的分层设计,如认证、负载均衡、日志记录、监控等
简单使用HelloWorld(java 版本)
我是在IDEA中写的代码,参看 https://github.com/grpc/grpc-java 添加依赖
<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>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.20.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.20.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.20.0</version>
</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.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protoSourceRoot>${basedir}/src/main/resources/proto</protoSourceRoot> <!--指定proto文件路径-->
<!--<outputDirectory>${project.build.sourceDirectory}</outputDirectory>-->
<!--设置是否在生成java文件之前清空outputDirectory的文件,默认值为true,设置为false时也会覆盖同名文件-->
<clearOutputDirectory>true</clearOutputDirectory>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.20.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
我多配置了一个 protoSourceRoot 属性,我把proto文件放在/resources/proto文件夹下面了。
helloworld.proto文件内容:
syntax = "proto3";
package example;
option java_package = "cn.lhcz.lhczrpc.demo";
option java_outer_classname = "HelloWorldServiceProto";
option java_multiple_files = true;
option objc_class_prefix = "HLW";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
参数说明:
java_multiple_files=true,将会生成多个Java文件,默认为false,只生成一个。
java_package 指定生成的文件的包。
然后即可使用maven插件来生成java类了。我的IDEA版本为2018.1.5。要使用Lifecycle下面的compile命令来生成,要不然没有GreeterGrpc文件。(我看别人都是用的Plugins下面的命令来生成,我自己则一直没有GreeterGrpc文件)。
然后就可以写代码了。
HelloWorldServer.java
public class HelloWorldServer {
private static final Logger logger = LoggerFactory.getLogger(HelloWorldServer.class.getName());
private int port = 50051;
private Server server;
private void start() throws IOException {
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() {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
/** 原子Integer */
// public AtomicInteger count = new AtomicInteger(0);
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
// System.out.println("call sayHello");
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
try {
Thread.sleep(10*1000);
} catch (InterruptedException e){
e.printStackTrace();
}
responseObserver.onNext(reply);
responseObserver.onCompleted();
// System.out.println(count.incrementAndGet() + Thread.currentThread().getName());
}
}
}
HelloWorldClient.java
public class HelloWorldClient {
private static final Logger logger = LoggerFactory.getLogger(HelloWorldClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
/**
* Construct client connecting to HelloWorld server at {@code host:port}.
*/
public HelloWorldClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/**
* Say hello to server.
*/
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.error("RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
/**
* Greet server. If provided, the first element of {@code args} is the name to use in the
* greeting.
*/
/*public static void main(String[] args) throws Exception {
HelloWorldClient client = new HelloWorldClient("localhost", 50051);
try {
String user = "world";
if (args.length > 0) {
user = args[0];
}
client.greet(user);
} finally {
client.shutdown();
}
}*/
public static void main(String[] args) throws Exception{
HelloWorldClient client = new HelloWorldClient("localhost", 50051);
try {
for (int i=0;i<10;i++) {
new Thread(new MyThread(client, ""+i)).start();
}
} finally {
//client.shutdown();
}
}
}
MyThread.java
public class MyThread implements Runnable{
private HelloWorldClient client;
private String message;
public MyThread(){}
public MyThread(HelloWorldClient client, String message){
this.client = client;
this.message = message;
}
@Override
public void run() {
client.greet(message);
}
}
先跑HelloWorldServer,再跑HelloWorldClient即可看到效果。