大家好,我是不才陳某~
RPC、gRPC、Thrift、HTTP,大家知道它們之間的聯系和區別么?這些都是面試常考的問題,今天帶大家先搞懂 RPC 和 gRPC,
在講述 gRPC 之前,我們需要先搞懂什么是 RPC,
不 BB,直接上文章目錄:

什么是 RPC ?
RPC(Remote Procedure Call Protocol)遠程程序呼叫協議,目標就是讓遠程服務呼叫更加簡單、透明,
RPC 框架負責屏蔽底層的傳輸方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二進制)和通信細節,服務呼叫者可以像呼叫本地介面一樣呼叫遠程的服務提供者,而不需要關心底層通信細節和呼叫程序,

為什么要用 RPC ?
當我們的業務越來越多、應用也越來越多時,自然的,我們會發現有些功能已經不能簡單劃分開來或者劃分不出來,
此時可以將公共業務邏輯抽離出來,將之組成獨立的服務 Service 應用,而原有的、新增的應用都可以與那些獨立的 Service 應用 互動,以此來完成完整的業務功能,
所以我們急需一種高效的應用程式之間的通訊手段來完成這種需求,RPC 大顯身手的時候來了!
常用的 RPC 框架
- gRPC:一開始由 google 開發,是一款語言中立、平臺中立、開源的遠程程序呼叫(RPC)系統,
- Thrift:thrift 是一個軟體框架,用來進行可擴展且跨語言的服務的開發,它結合了功能強大的軟體堆疊和代碼生成引擎,以構建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些編程語言間無縫結合的、高效的服務,
- Dubbo:Dubbo 是一個分布式服務框架,以及 SOA 治理方案,Dubbo自2011年開源后,已被許多非阿里系公司使用,
- Spring Cloud:Spring Cloud 由眾多子專案組成,如 Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分布式系統及微服務常用的工具,
RPC 的呼叫流程
要讓網路通信細節對使用者透明,我們需要對通信細節進行封裝,我們先看下一個 RPC 呼叫的流程涉及到哪些通信細節:

- 服務消費方(client)呼叫以本地呼叫方式呼叫服務;
- client stub接收到呼叫后負責將方法、引數等組裝成能夠進行網路傳輸的訊息體;
- client stub找到服務地址,并將訊息發送到服務端;
- server stub收到訊息后進行解碼;
- server stub根據解碼結果呼叫本地的服務;
- 本地服務執行并將結果回傳給 server stub;
- server stub將回傳結果打包成訊息并發送至消費方;
- client stub接收到訊息,并進行解碼;
- 服務消費方得到最終結果,
RPC 的目標就是要 2~8 這些步驟都封裝起來,讓用戶對這些細節透明,下面是網上的另外一幅圖,感覺一目了然:

什么是 gRPC ?
gRPC 是一個高性能、通用的開源 RPC 框架,其由 Google 2015 年主要面向移動應用開發并基于 HTTP/2 協議標準而設計,基于 ProtoBuf 序列化協議開發,且支持眾多開發語言,
由于是開源框架,通信的雙方可以進行二次開發,所以客戶端和服務器端之間的通信會更加專注于業務層面的內容,減少了對由 gRPC 框架實作的底層通信的關注,
如下圖,DATA 部分即業務層面內容,下面所有的資訊都由 gRPC 進行封裝,

gRPC 的特點
- 跨語言使用,支持 C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP 等編程語言;
- 基于 IDL 檔案定義服務,通過 proto3 工具生成指定語言的資料結構、服務端介面以及客戶端 Stub;
- 通信協議基于標準的 HTTP/2 設計,支持雙向流、訊息頭壓縮、單 TCP 的多路復用、服務端推送等特性,這些特性使得 gRPC 在移動端設備上更加省電和節省網路流量;
- 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一種語言無關的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 呼叫的高性能;
- 安裝簡單,擴展方便(用該框架每秒可達到百萬個RPC),
gRPC 互動程序

- 交換機在開啟 gRPC 功能后充當 gRPC 客戶端的角色,采集服務器充當 gRPC 服務器角色;
- 交換機會根據訂閱的事件構建對應資料的格式(GPB/JSON),通過 Protocol Buffers 進行撰寫 proto 檔案,交換機與服務器建立 gRPC 通道,通過 gRPC 協議向服務器發送請求訊息;
- 服務器收到請求訊息后,服務器會通過 Protocol Buffers 解譯 proto 檔案,還原出最先定義好格式的資料結構,進行業務處理;
- 資料處理完后,服務器需要使用 Protocol Buffers 重編譯應答資料,通過 gRPC 協議向交換機發送應答訊息;
- 交換機收到應答訊息后,結束本次的 gRPC 互動,
簡單地說,gRPC 就是在客戶端和服務器端開啟 gRPC 功能后建立連接,將設備上配置的訂閱資料推送給服務器端,
我們可以看到整個程序是需要用到 Protocol Buffers 將所需要處理資料的結構化資料在 proto 檔案中進行定義,
Protocol Buffers
你可以理解 ProtoBuf 是一種更加靈活、高效的資料格式,與 XML、JSON 類似,在一些高性能且對回應速度有要求的資料傳輸場景非常適用,
ProtoBuf 在 gRPC 的框架中主要有三個作用:定義資料結構、定義服務介面,通過序列化和反序列化方式提升傳輸效率,
為什么 ProtoBuf 會提高傳輸效率呢?
我們知道使用 XML、JSON 進行資料編譯時,資料文本格式更容易閱讀,但進行資料交換時,設備就需要耗費大量的 CPU 在 I/O 動作上,自然會影響整個傳輸速率,
Protocol Buffers 不像前者,它會將字串進行序列化后再進行傳輸,即二進制資料,

可以看到其實兩者內容相差不大,并且內容非常直觀,但是 Protocol Buffers 編碼的內容只是提供給操作者閱讀的,實際上傳輸的并不會以這種文本形式,而是序列化后的二進制資料,位元組數會比 JSON、XML 的位元組數少很多,速率更快,
gPRC 如何支撐跨平臺,多語言呢 ?
Protocol Buffers 自帶一個編譯器也是一個優勢點,前面提到的 proto 檔案就是通過編譯器進行編譯的,proto 檔案需要編譯生成一個類似庫檔案,基于庫檔案才能真正開發資料應用,
具體用什么編程語言編譯生成這個庫檔案呢?由于現網中負責網路設備和服務器設備的運維人員往往不是同一組人,運維人員可能會習慣使用不同的編程語言進行運維開發,那么 Protocol Buffers 其中一個優勢就能發揮出來——跨語言,
從上面的介紹,我們得出在編碼方面 Protocol Buffers 對比 JSON、XML 的優點:
- 標準的 IDL 和 IDL 編譯器,這使得其對工程師非常友好;
- 序列化資料非常簡潔,緊湊,與 XML 相比,其序列化之后的資料量約為 1/3 到 1/10;
- 決議速度非常快,比對應的 XML 快約 20-100 倍;
- 提供了非常友好的動態庫,使用非常簡單,反序列化只需要一行代碼,
Protobuf 也有其局限性:
- 由于 Protobuf 產生于 Google,所以目前其僅支持 Java、C++、Python 三種語言;
- Protobuf 支持的資料型別相對較少,不支持常量型別;
- 由于其設計的理念是純粹的展現層協議(Presentation Layer),目前并沒有一個專門支持 Protobuf 的 RPC 框架,
Protobuf 適用場景:
- Protobuf 具有廣泛的用戶基礎,空間開銷小以及高決議性能是其亮點,非常適合于公司內部的對性能要求高的 RPC 呼叫;
- 由于 Protobuf 提供了標準的 IDL 以及對應的編譯器,其 IDL 檔案是參與各方的非常強的業務約束;
- Protobuf 與傳輸層無關,采用 HTTP 具有良好的跨防火墻的訪問屬性,所以 Protobuf 也適用于公司間對性能要求比較高的場景;
- 由于其決議性能高,序列化后資料量相對少,非常適合應用層物件的持久化場景;
- 主要問題在于其所支持的語言相對較少,另外由于沒有系結的標準底層傳輸層協議,在公司間進行傳輸層協議的除錯作業相對麻煩,
基于 HTTP 2.0 標準設計
除了 Protocol Buffers 之外,從互動圖中和分層框架可以看到, gRPC 還有另外一個優勢——它是基于 HTTP 2.0 協議的,
由于 gRPC 基于 HTTP 2.0 標準設計,帶來了更多強大功能,如多路復用、二進制幀、頭部壓縮、推送機制,
這些功能給設備帶來重大益處,如節省帶寬、降低 TCP 連接次數、節省 CPU 使用等,gRPC 既能夠在客戶端應用,也能夠在服務器端應用,從而以透明的方式實作兩端的通信和簡化通信系統的構建,
HTTP 1.X 定義了四種與服務器互動的方式,分別為 GET、POST、PUT、DELETE,這些在 HTTP 2.0 中均保留,我們看看 HTTP 2.0 的新特性:雙向流、多路復用、二進制幀、頭部壓縮,
性能對比
與采用文本格式的 JSON 相比,采用二進制格式的 protobuf 在速度上可以達到前者的 5 倍!
Auth0 網站所做的性能測驗結果顯示,protobuf 和 JSON 的優勢差異在 Java、Python 等環境中尤為明顯,下圖是 Auth0 在兩個 Spring Boot 應用程式間所做的對比測驗結果,

結果顯示,protobuf 所需的請求時間最多只有 JSON 的 20% 左右,即速度是其 5 倍!
下面看一下性能和空間開銷對比,


從上圖可得出如下結論:
- XML序列化(Xstream)無論在性能和簡潔性上比較差,
- Thrift 與 Protobuf 相比在時空開銷方面都有一定的劣勢,
- Protobuf 和 Avro 在兩方面表現都非常優越,
gRPC 實戰
1. 專案結構
我們先看一下專案結構:

2. 生成 protobuf 檔案
檔案 helloworld.proto:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// 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;
}
這里提供了一個 SayHello() 方法,然后入參為 HelloRequest,回傳值為 HelloReply,可以看到 proto 檔案只定義了入參和回傳值的格式,以及呼叫的介面,至于介面內部的實作,該檔案完全不用關心,
檔案 pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>rpc-study</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>grpc-demo</artifactId>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.14.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.14.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.14.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.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.14.0: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-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
這里面的 build 其實是為了安裝 protobuf 插件,里面其實有 2 個插件我們需要用到,分別為 protobuf:compile 和 protobuf:compile-javanano,當我們直接執行時,會生成左側檔案,其中 GreeterGrpc 提供呼叫介面,Hello 開頭的檔案功能主要是對資料進行序列化,然后處理入參和回傳值,
可能有同學會問,你把檔案生成到 target 中,我想放到 main.src 中,你可以把這些檔案 copy 出來,或者也可以通過工具生成:
- 下載 protoc.exe 工具 ,下載地址:https://github.com/protocolbuffers/protobuf/releases
- 下載 protoc-gen-grpc 插件, 下載地址: http://jcenter.bintray.com/io/grpc/protoc-gen-grpc-java/

3. 服務端和客戶端
檔案 HelloWorldClient.java:
public class HelloWorldClient {
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
public HelloWorldClient(String host,int port){
channel = ManagedChannelBuilder.forAddress(host,port)
.usePlaintext(true)
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String 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("Message from gRPC-Server: "+response.getMessage());
}
public static void main(String[] args) throws InterruptedException {
HelloWorldClient client = new HelloWorldClient("127.0.0.1",50051);
try{
String user = "world";
if (args.length > 0){
user = args[0];
}
client.greet(user);
}finally {
client.shutdown();
}
}
}
這個太簡單了,就是連接服務埠,呼叫 sayHello() 方法,
檔案 HelloWorldServer.java:
public class HelloWorldServer {
private static final Logger logger = Logger.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() {
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();
}
}
// block 一直到退出程式
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
// 實作 定義一個實作服務介面的類
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage(("Hello " + req.getName())).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
System.out.println("Message from gRPC-Client:" + req.getName());
System.out.println("Message Response:" + reply.getMessage());
}
}
}
主要是實作 sayHello() 方法,里面對資料進行了簡單處理,入參為 “W orld”,回傳的是 “Hello World”,
4. 啟動服務
先啟動 Server,回傳如下:

再啟動 Client,回傳如下:

同時 Server回傳如下:

原始碼地址:https://github.com/lml200701158/rpc-study
寫在最后
這篇文章其實是我去年寫的,這次是重新整理,文章詳細講解了 RPC 和 gRPC,以及 gRPC 的應用示例,非常全面,后面會再把 Thrift 整理出來,
這個 Demo 看起來很簡單,我 TM 居然搞了大半天,一開始是因為不知道需要執行 2 個不同的插件來生成 protobuf,以為只需要點擊 protobuf:compile 就可以,結果發現 protobuf:compile-javanano 也需要點一下,
如果覺得作者寫的好,有所識訓的話,點個關注,推薦一波,文章首發于公眾號!!!轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/503449.html
標籤:Java
