高性能RPC框架gRPC竟恐怖如斯~
RPC、gRPC、Thrift、HTTP,大家知道它們之間的聯(lián)系和區(qū)別么?這些都是面試常考的問題,今天帶大家先搞懂 RPC 和 gRPC。
在講述 gRPC 之前,我們需要先搞懂什么是 RPC。
不 BB,直接上文章目錄:
?
?
什么是 RPC ?
RPC(Remote Procedure Call Protocol)遠程過程調(diào)用協(xié)議,目標就是讓遠程服務調(diào)用更加簡單、透明。
RPC 框架負責屏蔽底層的傳輸方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二進制)和通信細節(jié),服務調(diào)用者可以像調(diào)用本地接口一樣調(diào)用遠程的服務提供者,而不需要關心底層通信細節(jié)和調(diào)用過程。
?
為什么要用 RPC ?
當我們的業(yè)務越來越多、應用也越來越多時,自然的,我們會發(fā)現(xiàn)有些功能已經(jīng)不能簡單劃分開來或者劃分不出來。
此時可以將公共業(yè)務邏輯抽離出來,將之組成獨立的服務 Service 應用,而原有的、新增的應用都可以與那些獨立的 Service 應用 交互,以此來完成完整的業(yè)務功能。
所以我們急需一種高效的應用程序之間的通訊手段來完成這種需求,RPC 大顯身手的時候來了!
常用的 RPC 框架
- gRPC:一開始由 google 開發(fā),是一款語言中立、平臺中立、開源的遠程過程調(diào)用(RPC)系統(tǒng)。
- Thrift:thrift 是一個軟件框架,用來進行可擴展且跨語言的服務的開發(fā)。它結合了功能強大的軟件堆棧和代碼生成引擎,以構建在 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 等,提供了搭建分布式系統(tǒng)及微服務常用的工具。
RPC 的調(diào)用流程
要讓網(wǎng)絡通信細節(jié)對使用者透明,我們需要對通信細節(jié)進行封裝,我們先看下一個 RPC 調(diào)用的流程涉及到哪些通信細節(jié):
?
RPC 的目標就是要 2~8 這些步驟都封裝起來,讓用戶對這些細節(jié)透明,下面是網(wǎng)上的另外一幅圖,感覺一目了然:
?
什么是 gRPC ?
gRPC 是一個高性能、通用的開源 RPC 框架,其由 Google 2015 年主要面向移動應用開發(fā)并基于 HTTP/2 協(xié)議標準而設計,基于 ProtoBuf 序列化協(xié)議開發(fā),且支持眾多開發(fā)語言。
由于是開源框架,通信的雙方可以進行二次開發(fā),所以客戶端和服務器端之間的通信會更加專注于業(yè)務層面的內(nèi)容,減少了對由 gRPC 框架實現(xiàn)的底層通信的關注。
如下圖,DATA 部分即業(yè)務層面內(nèi)容,下面所有的信息都由 gRPC 進行封裝。
?
?
gRPC 的特點
- 跨語言使用,支持 C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP 等編程語言;
- 基于 IDL 文件定義服務,通過 proto3 工具生成指定語言的數(shù)據(jù)結構、服務端接口以及客戶端 Stub;
- 通信協(xié)議基于標準的 HTTP/2 設計,支持雙向流、消息頭壓縮、單 TCP 的多路復用、服務端推送等特性,這些特性使得 gRPC 在移動端設備上更加省電和節(jié)省網(wǎng)絡流量;
- 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一種語言無關的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 調(diào)用的高性能;
- 安裝簡單,擴展方便(用該框架每秒可達到百萬個RPC)。
gRPC 交互過程
?
- 交換機在開啟 gRPC 功能后充當 gRPC 客戶端的角色,采集服務器充當 gRPC 服務器角色;
- 交換機會根據(jù)訂閱的事件構建對應數(shù)據(jù)的格式(GPB/JSON),通過 Protocol Buffers 進行編寫 proto 文件,交換機與服務器建立 gRPC 通道,通過 gRPC 協(xié)議向服務器發(fā)送請求消息;
- 服務器收到請求消息后,服務器會通過 Protocol Buffers 解譯 proto 文件,還原出最先定義好格式的數(shù)據(jù)結構,進行業(yè)務處理;
- 數(shù)據(jù)處理完后,服務器需要使用 Protocol Buffers 重編譯應答數(shù)據(jù),通過 gRPC 協(xié)議向交換機發(fā)送應答消息;
- 交換機收到應答消息后,結束本次的 gRPC 交互。
簡單地說,gRPC 就是在客戶端和服務器端開啟 gRPC 功能后建立連接,將設備上配置的訂閱數(shù)據(jù)推送給服務器端。
我們可以看到整個過程是需要用到 Protocol Buffers 將所需要處理數(shù)據(jù)的結構化數(shù)據(jù)在 proto 文件中進行定義。
Protocol Buffers
你可以理解?ProtoBuf 是一種更加靈活、高效的數(shù)據(jù)格式,與 XML、JSON 類似,在一些高性能且對響應速度有要求的數(shù)據(jù)傳輸場景非常適用。
ProtoBuf 在 gRPC 的框架中主要有三個作用:定義數(shù)據(jù)結構、定義服務接口,通過序列化和反序列化方式提升傳輸效率。
為什么 ProtoBuf 會提高傳輸效率呢?
我們知道使用 XML、JSON 進行數(shù)據(jù)編譯時,數(shù)據(jù)文本格式更容易閱讀,但進行數(shù)據(jù)交換時,設備就需要耗費大量的 CPU 在 I/O 動作上,自然會影響整個傳輸速率。
Protocol Buffers 不像前者,它會將字符串進行序列化后再進行傳輸,即二進制數(shù)據(jù)。
?
?
可以看到其實兩者內(nèi)容相差不大,并且內(nèi)容非常直觀,但是 Protocol Buffers 編碼的內(nèi)容只是提供給操作者閱讀的,實際上傳輸?shù)牟⒉粫赃@種文本形式,而是序列化后的二進制數(shù)據(jù),字節(jié)數(shù)會比 JSON、XML 的字節(jié)數(shù)少很多,速率更快。
gPRC 如何支撐跨平臺,多語言呢 ?
Protocol Buffers 自帶一個編譯器也是一個優(yōu)勢點,前面提到的 proto 文件就是通過編譯器進行編譯的,proto 文件需要編譯生成一個類似庫文件,基于庫文件才能真正開發(fā)數(shù)據(jù)應用。
具體用什么編程語言編譯生成這個庫文件呢?由于現(xiàn)網(wǎng)中負責網(wǎng)絡設備和服務器設備的運維人員往往不是同一組人,運維人員可能會習慣使用不同的編程語言進行運維開發(fā),那么 Protocol Buffers 其中一個優(yōu)勢就能發(fā)揮出來——跨語言。
從上面的介紹,我們得出在編碼方面 Protocol Buffers 對比 JSON、XML 的優(yōu)點:
- 標準的 IDL 和 IDL 編譯器,這使得其對工程師非常友好;
- 序列化數(shù)據(jù)非常簡潔,緊湊,與 XML 相比,其序列化之后的數(shù)據(jù)量約為 1/3 到 1/10;
- 解析速度非常快,比對應的 XML 快約 20-100 倍;
- 提供了非常友好的動態(tài)庫,使用非常簡單,反序列化只需要一行代碼。
Protobuf 也有其局限性:
- 由于 Protobuf 產(chǎn)生于 Google,所以目前其僅支持 Java、C++、Python 三種語言;
- Protobuf 支持的數(shù)據(jù)類型相對較少,不支持常量類型;
- 由于其設計的理念是純粹的展現(xiàn)層協(xié)議(Presentation Layer),目前并沒有一個專門支持 Protobuf 的 RPC 框架。
Protobuf 適用場景:
- Protobuf 具有廣泛的用戶基礎,空間開銷小以及高解析性能是其亮點,非常適合于公司內(nèi)部的對性能要求高的 RPC 調(diào)用;
- 由于 Protobuf 提供了標準的 IDL 以及對應的編譯器,其 IDL 文件是參與各方的非常強的業(yè)務約束;
- Protobuf 與傳輸層無關,采用 HTTP 具有良好的跨防火墻的訪問屬性,所以 Protobuf 也適用于公司間對性能要求比較高的場景;
- 由于其解析性能高,序列化后數(shù)據(jù)量相對少,非常適合應用層對象的持久化場景;
- 主要問題在于其所支持的語言相對較少,另外由于沒有綁定的標準底層傳輸層協(xié)議,在公司間進行傳輸層協(xié)議的調(diào)試工作相對麻煩。
基于 HTTP 2.0 標準設計
除了 Protocol Buffers 之外,從交互圖中和分層框架可以看到, gRPC 還有另外一個優(yōu)勢——它是基于 HTTP 2.0 協(xié)議的。
由于 gRPC 基于 HTTP 2.0 標準設計,帶來了更多強大功能,如多路復用、二進制幀、頭部壓縮、推送機制。
這些功能給設備帶來重大益處,如節(jié)省帶寬、降低 TCP 連接次數(shù)、節(jié)省 CPU 使用等,gRPC 既能夠在客戶端應用,也能夠在服務器端應用,從而以透明的方式實現(xiàn)兩端的通信和簡化通信系統(tǒng)的構建。
HTTP 1.X 定義了四種與服務器交互的方式,分別為 GET、POST、PUT、DELETE,這些在 HTTP 2.0 中均保留,我們看看 HTTP 2.0 的新特性:雙向流、多路復用、二進制幀、頭部壓縮。
性能對比
與采用文本格式的 JSON 相比,采用二進制格式的 protobuf 在速度上可以達到前者的 5 倍!
Auth0 網(wǎng)站所做的性能測試結果顯示,protobuf 和 JSON 的優(yōu)勢差異在 Java、Python 等環(huán)境中尤為明顯,下圖是 Auth0 在兩個 Spring Boot 應用程序間所做的對比測試結果。
?
?
結果顯示,protobuf 所需的請求時間最多只有 JSON 的 20% 左右,即速度是其 5 倍!
下面看一下性能和空間開銷對比。
?
從上圖可得出如下結論:
- XML序列化(Xstream)無論在性能和簡潔性上比較差。
- Thrift 與 Protobuf 相比在時空開銷方面都有一定的劣勢。
- Protobuf 和 Avro 在兩方面表現(xiàn)都非常優(yōu)越。
gRPC 實戰(zhàn)
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 greetingrpc 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() 方法,然后入?yún)?HelloRequest,返回值為 HelloReply,可以看到 proto 文件只定義了入?yún)⒑头祷刂档母袷?#xff0c;以及調(diào)用的接口,至于接口內(nèi)部的實現(xiàn),該文件完全不用關心。
文件 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,當我們直接執(zhí)行時,會生成左側文件,其中 GreeterGrpc 提供調(diào)用接口,Hello 開頭的文件功能主要是對數(shù)據(jù)進行序列化,然后處理入?yún)⒑头祷刂怠?/p>
可能有同學會問,你把文件生成到 target 中,我想放到 main.src 中,你可以把這些文件 copy 出來,或者也可以通過工具生成:
- 下載 protoc.exe 工具 ,下載地址:Releases · protocolbuffers/protobuf · GitHub
- 下載 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();}} }?
這個太簡單了,就是連接服務端口,調(diào)用 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() {@Overridepublic 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();}// 實現(xiàn) 定義一個實現(xiàn)服務接口的類private class GreeterImpl extends GreeterGrpc.GreeterImplBase {@Overridepublic 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());}} }?
主要是實現(xiàn) sayHello() 方法,里面對數(shù)據(jù)進行了簡單處理,入?yún)?“W orld”,返回的是 “Hello World”。
4. 啟動服務
先啟動 Server,返回如下:
再啟動 Client,返回如下:?
?同時 Server返回如下:
?
源碼地址:GitHub - lml200701158/rpc-study
寫在最后
這篇文章其實是我去年寫的,這次是重新整理,文章詳細講解了 RPC 和 gRPC,以及 gRPC 的應用示例,非常全面,后面會再把 Thrift 整理出來。
這個 Demo 看起來很簡單,我 TM 居然搞了大半天,一開始是因為不知道需要執(zhí)行 2 個不同的插件來生成 protobuf,以為只需要點擊 protobuf:compile 就可以,結果發(fā)現(xiàn) protobuf:compile-javanano 也需要點一下。
總結
以上是生活随笔為你收集整理的高性能RPC框架gRPC竟恐怖如斯~的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么说 HashMap 是无序的
- 下一篇: 指针 转 智能指针_智能指针-它们真的那