JAVA

聊聊对 gRPC的理解!

转载:滴滴2面: 聊聊对 gRPC的理解!

在 淘宝1面: HTTP 与 RPC的区别!文章中,我们对比了 HTTP 和 RPC,这篇文章我们来分析一道滴滴的面试题:聊聊对 gRPC的理解!文章会解答:gRPC是什么?它是如何工作的?我们要如何编写 gRPC的代码?

1. 什么是 gRPC?


简单来说,gRPC是一个高性能、开源和通用的远程过程调用(RPC)框架,由Google开发。它基于HTTP/2协议,使用Protocol Buffers(protobuf)作为接口描述语言,支持多种编程语言。gRPC让不同服务之间的通信变得简单、高效,广泛应用于微服务架构中。

2. 为什么选择 gRPC?


在微服务架构中,服务之间频繁通信是不可避免的。传统的REST API使用JSON进行数据传输,虽然易于理解,但在性能和效率上存在一些瓶颈。gRPC则通过以下优势脱颖而出:

  1. 高效的二进制传输:使用protobuf序列化,比JSON更轻量,传输更快。
  2. 多语言支持:支持包括Java、C++, Python等多种语言,便于跨语言开发。
  3. 内建的负载均衡、认证和流控:减少了开发者的配置负担。
  4. 基于HTTP/2:支持多路复用、流控、压缩等特性,提升通信效率。

3. gRPC 的工作原理


让我们深入了解一下gRPC的工作机制。

3.1 Protocol Buffers(protobuf)

gRPC依赖protobuf来定义服务接口和消息结构。protobuf是一种高效的二进制序列化工具,它通过.proto文件描述服务和数据结构,然后生成对应语言的代码。

示例:定义一个简单的gRPC服务

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// helloworld.proto
syntax = "proto3";
option java_package = "com.yuanjava.grpc";
option java_outer_classname = "HelloWorldProto";
service Greeter {
// 定义一个SayHello的RPC方法
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
// helloworld.proto syntax = "proto3"; option java_package = "com.yuanjava.grpc"; option java_outer_classname = "HelloWorldProto"; service Greeter { // 定义一个SayHello的RPC方法 rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
// helloworld.proto
syntax = "proto3";

option java_package = "com.yuanjava.grpc";
option java_outer_classname = "HelloWorldProto";

service Greeter {
// 定义一个SayHello的RPC方法
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
string message = 1;
}

3.2 服务端与客户端

在gRPC中,服务端实现接口定义的方法,客户端通过生成的代码调用这些方法。通信过程透明化,开发者不需要关心底层的HTTP/2细节。

服务端示例(Java):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
String greeting = "Hello, " + req.getName();
HelloReply reply = HelloReply.newBuilder().setMessage(greeting).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
public class GreeterImpl extends GreeterGrpc.GreeterImplBase { @Override public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) { String greeting = "Hello, " + req.getName(); HelloReply reply = HelloReply.newBuilder().setMessage(greeting).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } }
public class GreeterImpl extends GreeterGrpc.GreeterImplBase {
    @Override
    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
        String greeting = "Hello, " + req.getName();
        HelloReply reply = HelloReply.newBuilder().setMessage(greeting).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }
}

客户端示例(Java):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class GreeterClient {
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public GreeterClient(Channel channel) {
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply reply = blockingStub.sayHello(request);
System.out.println(reply.getMessage());
}
}
public class GreeterClient { private final GreeterGrpc.GreeterBlockingStub blockingStub; public GreeterClient(Channel channel) { blockingStub = GreeterGrpc.newBlockingStub(channel); } public void greet(String name) { HelloRequest request = HelloRequest.newBuilder().setName(name).build(); HelloReply reply = blockingStub.sayHello(request); System.out.println(reply.getMessage()); } }
public class GreeterClient {
    private final GreeterGrpc.GreeterBlockingStub blockingStub;

    public GreeterClient(Channel channel) {
        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }

    public void greet(String name) {
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply reply = blockingStub.sayHello(request);
        System.out.println(reply.getMessage());
    }
}

3.3 HTTP/2 特性

gRPC基于HTTP/2协议,这带来了许多优势:

  • 多路复用:在一个TCP连接上,可以发送多个请求和响应,减少延迟。
  • 头部压缩:减少带宽使用,加快传输速度。
  • 流控制:更好地管理数据流,避免拥塞。

4. gRPC 的实际应用


接下来,让我们通过一个实际的例子,看看如何在Java中使用gRPC。

4.1 步骤一:定义服务

使用上面的helloworld.proto文件,定义我们的gRPC服务。

4.2 步骤二:生成代码

使用protoc编译器生成Java代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
protoc --java_out=. --grpc-java_out=. helloworld.proto
protoc --java_out=. --grpc-java_out=. helloworld.proto
protoc --java_out=. --grpc-java_out=. helloworld.proto

4.3 步骤三:实现服务端

在服务端,实现GreeterImpl类,并在main()方法中启动服务。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class HelloWorldServer {
public static void main(String[] args) throws IOException, InterruptedException {
Server server = ServerBuilder.forPort(50051)
.addService(new GreeterImpl())
.build()
.start();
System.out.println("Server started, listening on 50051");
server.awaitTermination();
}
}
public class HelloWorldServer { public static void main(String[] args) throws IOException, InterruptedException { Server server = ServerBuilder.forPort(50051) .addService(new GreeterImpl()) .build() .start(); System.out.println("Server started, listening on 50051"); server.awaitTermination(); } }
public class HelloWorldServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        Server server = ServerBuilder.forPort(50051)
            .addService(new GreeterImpl())
            .build()
            .start();

        System.out.println("Server started, listening on 50051");
        server.awaitTermination();
    }
}

4.4 步骤四:实现客户端

在客户端,实现GreeterClient类,并在main()方法中启动客户端。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class HelloWorldClient {
public static void main(String[] args) throws Exception {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();
GreeterClient client = new GreeterClient(channel);
client.greet("World");
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
public class HelloWorldClient { public static void main(String[] args) throws Exception { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) .usePlaintext() .build(); GreeterClient client = new GreeterClient(channel); client.greet("World"); channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); } }
public class HelloWorldClient {
    public static void main(String[] args) throws Exception {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
            .usePlaintext()
            .build();

        GreeterClient client = new GreeterClient(channel);
        client.greet("World");

        channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
    }
}

4.5 步骤五:运行示例

启动服务端:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
java HelloWorldServer
java HelloWorldServer
java HelloWorldServer

启动客户端:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
java HelloWorldClient
java HelloWorldClient
java HelloWorldClient

我们可以在客户端看到输出:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Hello, World
Hello, World
Hello, World

5. 总结


这篇文章,我们详细地介绍了gRPC并且通过一个完整的代码示例进行了演示,gRPC作为一个高性能、功能丰富的RPC框架,在现代分布式系统中扮演着重要角色。作为后端人员,建议去掌握其原理。