微服務通訊方式——gRPC
微服務設計的原則是單一職責、輕量級通訊、服務粒度適當,而說到服務通訊,我們熟知的有MQ通訊,還有REST、Dubbo和Thrift等,這次我來說說gRPC,
谷歌開發的一種資料交換格式,說不定哪天就需要上了呢,多學習總是件好事。
準備:
Idea2019.03/Gradle6.0.1/Maven3.6.3/JDK11.0.4/Proto3.0/gRPC1.29.0
難度: 新手--戰士--老兵--大師
目標:
- 實現四種模式下的gRPC通訊
說明:
為了遇見各種問題,同時保持時效性,我儘量使用最新的軟體版本。原始碼地址,其中的day28:https://github.com/xiexiaobiao/dubbo-project
1 介紹
先引用一小段官方介紹:
Protocol Buffers - Google's data interchange format. Protocol Buffers (a.k.a., protobuf) are Google's language-neutral, platform-neutral, extensible
mechanism for serializing structured data.
Protocol Buffers,即protobuf,類比如JSON,XML等資料格式,實現了對結構化資料序列化時跨語言,跨平臺,可擴充套件的機制。通過預定義.proto
檔案,
來協商通訊雙方的資料交換格式、介面方法,這即是“Protocol”的原譯。.proto
grpc使用http2.0作為通訊方式,先說http2.0,它是對1.0的增強,而不是替代,特點:
- 二進位制傳輸:相比1.0版的文字,2.0使用二進位制幀傳輸資料;
- 多路複用:一個TCP連線中包含多個幀組成的流,再解析為不同的請求,從而可同時傳送多個請求,避免1.0版的隊頭阻塞;
- 首部壓縮:1.0版的Header部分資訊很多,2.0使用HPACK壓縮格式減少資料量;
- 服務端推送:服務端預先主動推送客戶端必要的資訊,從而減少延遲;
適用場景:定製化介面及資料交換格式,追求高效能,通訊對寬頻敏感
缺點:大多數HTTP Server尚不支援http2.0,Nginx目前只能將其降級為1.0處理;沒有連線池、服務發現和負載均衡的實現;
2 實戰步驟
A 普通模式
2.1 建立gradle型別專案,命名為GPRC-project
,我上傳的Git程式碼中還包含了maven型別的專案,按照官方說明製作,執行方法略異,見其中.txt檔案說明。
2.2 編寫.proto檔案,GPRC-project\src\main\proto\helloworld.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.biao.grpc.helloworld";
option java_outer_classname = "HelloWorldProto";
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相關引數:
-
java_multiple_files
是否單獨生成在.proto檔案中定義的頂級message、enum、和service,否則只生成一個包裝了內部類的外部類;java_package
編譯生成的java類檔案的包位置;java_outer_classname
外部類名稱;java_generic_services是否生成各語言版本的基類(已過時); -
service Greeter {...}
定義服務和包含的方法; -
message
定義訊息體結構,這裡定義了一個 String型別,且只有一個字串型別的value成員,該成員編號為1來代替名字,這也是protobuf體積小的原因之一,別的資料描述語言(json、xml)都是通過成員名字標識,而Portobuf通過唯一編號,只是不便於查閱。
2.3 編寫GPRC-project\build.gradle
,包含依賴引入和gradle編譯配置:
buildscript { repositories { mavenCentral() maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} } dependencies { // protobuf 編譯外掛,會在右側gradle--other中,新增Proto相關的任務(共6個) classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' } } //plugins { // id 'java' // id 'com.google.protobuf' // id 'com.google.protobuf' version '0.8.8' //} apply plugin: 'java' apply plugin: 'com.google.protobuf' group 'com.biao.grpc' version '1.0-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} } dependencies { //這裡必須引入lib目錄的j2ee相關jar,否則即使每次手動加入jar依賴,但啟動應用時gradle會reimport, // 導致一直提示因少依賴而無法解析,這也是gradle引入第三方jar的方式 compile fileTree(dir: "lib", include: "*.jar") testCompile group: 'junit', name: 'junit', version: '4.12' implementation 'io.grpc:grpc-netty-shaded:1.29.0' implementation 'io.grpc:grpc-protobuf:1.29.0' implementation 'io.grpc:grpc-stub:1.29.0' } sourceSets { main { proto { // .proto檔案目錄 srcDir 'src/main/proto' } java { // include self written and generated code, 原始碼生成到一個單獨的目錄 srcDirs 'src/main/java','generated-sources/main/java' } } // remove the test configuration - at least in your example you don't have a special test proto file /* test { proto { srcDir 'src/test/proto' } proto { srcDir 'src/test/java' } }*/ } protobuf { // Configure the protoc executable protoc { // Download from repositories ,從倉庫下載, artifact = "com.google.protobuf:protoc:3.11.0" } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0' } } //'src' 改為'generated-sources',則會變更.proto檔案對應的java類檔案生成目錄 generateProtoTasks.generatedFilesBaseDir = 'src/main/java' generateProtoTasks { // all() returns the collection of all protoc tasks all()*.plugins { grpc {} } // In addition to all(),you may get the task collection by various // criteria: // (Java only) returns tasks for a sourceSet ofSourceSet('main') } }
以上程式碼解析:
- Moduleapply plugin: 'com.google.protobuf' 引入protobuf-gradle-plugin,作為protobuf 編譯外掛,會在右側gradle--other中,自動新增proto相關的任務(共6個)
- compile fileTree(dir: "lib", include: "*.jar") 這裡必須引入lib目錄的j2ee相關jar,否則即使每次手動加入jar依賴,但啟動應用時gradle會reimport,導致一直提示因少依賴而無法解析,這也是gradle引入第三方jar的正確方式
- implementation 'io.grpc:grpc-protobuf:1.29.0',gradle新版語法,implementation 僅僅對當前的Module提供介面,對外隱藏不必要的介面,而compile(新版升級為 api )依賴的庫將會完全參與編譯和打包,
- protobuf {...}中則宣告protoc-gen-grpc-java外掛來源的.proto檔案源目錄及生成目標目錄,
2.4 執行右側 task :gradle --> other --> generateProto
,則自動生成類檔案和介面檔案(XXXGrpc
),並且很貼心的是,如果原.proto
檔案有註釋,
生成的檔案中會自動帶上原註釋內容。可以看到,helloworld.proto 和 stream.proto 生成的對應的檔案,前者為 6 個,後者為 1 個,因java_multiple_files引數不同。
生成檔案後,將檔案移動到src/main/java對應的包下面,並將build.gradle與自動生成檔案的部分註釋掉,否則啟動應用時,又會自動生成,導致IDE提示類重複。
2.5 看下原始碼,包com.biao.grpc.helloworld
下面生成了對應於helloworld.proto
的類和介面,包括服務、請求訊息結構體和響應訊息結構體。
com.biao.grpc.helloworld.GreeterGrpc
中 getSayHelloMethod
方法即約定了RPC的方法、請求/響應資料型別,並獲取方法全名:
@io.grpc.stub.annotations.RpcMethod( fullMethodName = SERVICE_NAME + '/' + "SayHello", requestType = com.biao.grpc.helloworld.HelloRequest.class, responseType = com.biao.grpc.helloworld.HelloReply.class, methodType = io.grpc.MethodDescriptor.MethodType.UNARY) public static io.grpc.MethodDescriptor<com.biao.grpc.helloworld.HelloRequest, com.biao.grpc.helloworld.HelloReply> getSayHelloMethod() { io.grpc.MethodDescriptor<com.biao.grpc.helloworld.HelloRequest, com.biao.grpc.helloworld.HelloReply> getSayHelloMethod; if ((getSayHelloMethod = GreeterGrpc.getSayHelloMethod) == null) { synchronized (GreeterGrpc.class) { if ((getSayHelloMethod = GreeterGrpc.getSayHelloMethod) == null) { GreeterGrpc.getSayHelloMethod = getSayHelloMethod = io.grpc.MethodDescriptor.<com.biao.grpc.helloworld.HelloRequest, com.biao.grpc.helloworld.HelloReply>newBuilder() .setType(io.grpc.MethodDescriptor.MethodType.UNARY) .setFullMethodName(generateFullMethodName(SERVICE_NAME, "SayHello")) .setSampledToLocalTracing(true) .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( com.biao.grpc.helloworld.HelloRequest.getDefaultInstance())) .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( com.biao.grpc.helloworld.HelloReply.getDefaultInstance())) .setSchemaDescriptor(new GreeterMethodDescriptorSupplier("SayHello")) .build(); } } } return getSayHelloMethod; }
2.6 建立server端:
public class HelloWorld_Server { private static final Logger logger = Logger.getLogger(HelloWorld_Server.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"); HelloWorld_Server.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 HelloWorld_Server server = new HelloWorld_Server(); server.start(); server.blockUntilShutdown(); } // 實現 定義一個實現服務介面的類 private class GreeterImpl extends com.biao.grpc.helloworld.GreeterGrpc.GreeterImplBase { @Override public void sayHello(com.biao.grpc.helloworld.HelloRequest req, StreamObserver<com.biao.grpc.helloworld.HelloReply> responseObserver){ com.biao.grpc.helloworld.HelloReply reply = com.biao.grpc.helloworld.HelloReply. newBuilder() .setMessage(("Hello "+req.getName())) .build(); responseObserver.onNext(reply); responseObserver.onCompleted(); System.out.println("Message from gRPC-Client:" + req.getName()); } } }
以上程式碼解析:通過GreeterImpl擴充套件GreeterGrpc.GreeterImplBase具體實現了gRPC服務的方法,作為服務端響應請求的業務邏輯。
2.7 建立client端:
public class HelloWorld_Client { private final ManagedChannel channel; private final com.biao.grpc.helloworld.GreeterGrpc.GreeterBlockingStub blockingStub; private static final Logger logger = Logger.getLogger(HelloWorld_Client.class.getName()); public HelloWorld_Client(String host,int port){ channel = ManagedChannelBuilder.forAddress(host,port) .usePlaintext() .build(); blockingStub = com.biao.grpc.helloworld.GreeterGrpc.newBlockingStub(channel); } public void shutdown() throws InterruptedException { channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } public void greet(String name){ com.biao.grpc.helloworld.HelloRequest request = com.biao.grpc.helloworld.HelloRequest .newBuilder() .setName(name) .build(); com.biao.grpc.helloworld.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 { HelloWorld_Client client = new HelloWorld_Client("127.0.0.1",50051); try{ String user = "world"; if (args.length > 0){ user = args[0]; } client.greet(user); }finally { client.shutdown(); } } }
以上程式碼解析:在greet方法中,引用HelloRequest和HelloReply,併發起gRPC業務請求。
2.8 先執行com.biao.grpc.helloworld.HelloWorld_Server
,再執行com.biao.grpc.helloworld.HelloWorld_Client
, 輸出以下為成功:
B 流模式
gRPC有四種通訊模式:
- 普通模式:一次請求對應一次響應,和普通方法請求一樣;
- 客戶端流模式:客戶端使用流模式傳入多個請求物件,服務端返回一個響應結果;
- 服務端流模式:一個請求物件,服務端使用流模式傳回多個結果物件;
- 雙向流模式:客戶端流式和服務端流式組合;
前面有說過,gRPC使用http/2通訊,資料傳輸使用二進位制幀,幀是HTTP2.0通訊的最小單位,而訊息由一或多個幀組成,流是比訊息更大的通訊單位,
是TCP連線中的一個虛擬通道。每個資料流以訊息的形式傳送,訊息中的幀可以亂序傳送,然後再根據每個幀首部的流識別符號重新組裝為流。
前面的helloworld算第1種,我這裡寫了後 3 種模式,根據stream.proto開發,使用同一個server端:com.biao.grpc.stream.StreamServer
,其中實現
了客戶端流模式、服務端流模式和雙向流模式3種通訊模式的具體方法實現,
public class StreamServer { private static int port = 8883; private static io.grpc.Server server; public void run() { ServiceImpl serviceImpl = new ServiceImpl(); server = io.grpc.ServerBuilder.forPort(port).addService(serviceImpl).build(); try { server.start(); System.out.println("Server start success on port:" + port); server.awaitTermination(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } // 實現 定義一個實現服務介面的類 private static class ServiceImpl extends StreamServiceGrpc.StreamServiceImplBase { @Override public void serverSideStreamFun(Stream.RequestData request, StreamObserver<Stream.ResponseData> responseObserver) { // TODO Auto-generated method stub System.out.println("請求引數:" + request.getText()); for (int i = 0; i < 10; i++) { responseObserver.onNext(Stream.ResponseData.newBuilder() .setText("你好" + i) .build()); } responseObserver.onCompleted(); } @Override public StreamObserver<Stream.RequestData> clientSideStreamFun(StreamObserver<Stream.ResponseData> responseObserver) { // TODO Auto-generated method stub return new StreamObserver<Stream.RequestData>() { private Stream.ResponseData.Builder builder = Stream.ResponseData.newBuilder(); @Override public void onNext(Stream.RequestData value) { // TODO Auto-generated method stub System.out.println("請求引數:" + value.getText()); } @Override public void onError(Throwable t) { // TODO Auto-generated method stub } @Override public void onCompleted() { // TODO Auto-generated method stub builder.setText("資料接收完成"); responseObserver.onNext(builder.build()); responseObserver.onCompleted(); } }; } @Override public StreamObserver<Stream.RequestData> twoWayStreamFun(StreamObserver<Stream.ResponseData> responseObserver) { // TODO Auto-generated method stub return new StreamObserver<Stream.RequestData>() { @Override public void onNext(Stream.RequestData value) { // TODO Auto-generated method stub System.out.println("請求引數:" + value.getText()); responseObserver.onNext(Stream.ResponseData.newBuilder() .setText(value.getText() + ",歡迎你的加入") .build()); } @Override public void onError(Throwable t) { // TODO Auto-generated method stub t.printStackTrace(); } @Override public void onCompleted() { // TODO Auto-generated method stub responseObserver.onCompleted(); } }; } } public static void main(String[] args) { StreamServer server = new StreamServer(); server.run(); } }
另外我寫了3個client端,程式碼分析,略!執行後即可看到效果,我這裡給個雙向流的結果例子:
附:手動編譯
為了加強動手能力,我這裡也做個手動編譯生成java程式碼的步驟:
Java程式碼生成編譯器下載: https://repo.maven.apache.org/maven2/io/grpc/protoc-gen-grpc-java/
下載安裝protobuf,https://github.com/protocolbuffers/protobuf/releases?after=v3.0.0-alpha-4
To install protobuf, you need to install the protocol compiler (used to compile .proto files) and the protobuf runtime for your chosen programming language.
要使用protobuf,需先安裝協議編譯器(protocol compiler),用於編譯.proto檔案,並且作為protobuf的執行時環境。其實安裝protobuf等價於安裝其編譯環境protoc。
環境變數設定,將protoc的解壓目錄新增到Path下:
CMD下使用protoc --version
命令輸出如下即為成功:
手動編譯:進入 .proto
檔案所在目錄, 使用protoc.exe生成訊息結構體,下圖中標號 2 :
protoc <待編譯檔案> --java_out=<輸出檔案儲存路徑>
使用protoc-gen-grpc-java生成服務介面:下圖中標號 3 :
protoc *.proto --plugin=protoc-gen-grpc-java=C:\protobuf-3.0-beta\protoc-gen-grpc-java-1.9.1-windows-x86_64.exe --grpc-java_out=./
全文完!
我的其他文章:
- 1 分散式任務排程系統
- 2 Dubbo學習系列之十八(Skywalking服務跟蹤)
- 3 Spring優雅整合Redis快取
- 4 SOFARPC模式下的Consul註冊中心
- 5 八種控制執行緒順序的方法
只寫原創,敬請關注
相關推薦
微服務通訊方式——gRPC
微服務設計的原則是單一職責、輕量級通訊、服務粒度適當,而說到服務通訊,我們熟知的有MQ通訊,還有REST、Dubbo和Thrift等,這次我來說說gRPC, 谷歌開發的一種資料交換格式,說不定哪天就需要上了呢,多學習總是件好事。 準備: Idea2019.03/Gradle6.0.1/Maven
八:對微服務通訊方式RPC vs REST的理解
微服務專欄地址 目錄 1. 簡介 微服務的服務都是獨立程序,服務之間的通訊的效率、穩定性等等關乎著系統是否能高效、穩定執行。常見的通訊方式有RPC及REST,從以下幾個方面去理解微服務的服務通訊方式以及選擇: 關於RPC 1.1 什麼是
SAP雲平臺以微服務的方式提供了Document的CRUD(增刪改查)操作。該微服務基於標準的CMI
SAP. SCP SAP雲平臺 DocumentService 微服務 SAP雲平臺以微服務的方式提供了Document的CRUD(增刪改查)操作。該微服務基於標準的CMIS協議(Content Management Interoperability Service)。 同標準的CMI
Ceilometer 21、openstack元件之間通訊以及元件內服務通訊方式分析
1 openstack元件之間通訊以及元件內各服務通訊方式 1.1 openstack各元件之間的通訊方式 據我所瞭解的幾個元件,各個元件之間大部分是通過呼叫其他元件的rest api方式進行通訊, 而rest api的框架大部分是通過: wsgi + pecan 的形式搭建起來的,本質
SpringCloud微服務呼叫方式之Ribbon和Feign方式
微服務呼叫方式之Ribbon的用法 匯入Ribbon的依賴 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId&
微服務通訊的設計模式
https://mp.weixin.qq.com/s/zH1AbVmeB40MiiGXxQRnNQ 在我的上一篇部落格中,我談到了微服務的設計模式。現在我想更深入地探討微服務架構中最重要的模式:微服務之間的相互通訊。我仍然記得我們過去開發單一應用時通訊是一項艱鉅的任務。在那時我們必須小心的設計資
go微服務系列(四) - gRPC入門
- [1. 前言](#head1) - [2. gRPC與Protobuf簡介](#head2) - [3. 安裝](#head3) - [4. 中間檔案演示](#head4) - [4.1 編寫中間檔案](#head5) - [4.2 執行protoc命令編譯成go中間檔案](#head6) - [5.
微服務通訊之feign的註冊、發現過程
## 前言 feign 是目前微服務間通訊的主流方式,是springCloud中一個非常重要的元件。他涉及到了負載均衡、限流等元件。真正意義上掌握了feign可以說就掌握了微服務。 ## 一、feign的使用 feign 的使用和dubbo的使用本質上非常相似。dubbo的理念是:像呼叫本地方法一樣呼叫遠
微服務通訊之feign整合負載均衡
## 前言 書接上文,feign介面是如何註冊到容器想必已然清楚,現在我們著重關心一個問題,feign呼叫服務的時候是如何抉擇的?上一篇主要是從讀原始碼的角度入手,後續將會逐步從軟體構架方面進行剖析。 ### 一、ReflectiveFeign.FeignInvocationHandler 從上文知道fe
微服務通訊之ribbon實現原理
## 前言 上一篇我們知道了feign呼叫實現負載均衡是通過整合ribbon實現的。也較為詳細的瞭解到了整合的過程。現在我們看一下ribbo是如何實現負載均衡的。寫到這裡我尚未去閱讀原始碼,我在這裡盲猜一下: 他肯定是有一個從註冊中心拉取配置的模組,一個選擇呼叫服務的模組。然後我們就帶著這樣的指導思想去看原始
微服務通訊之feign的配置隔離
### 前言 由上文我們知道針對某一個Feign介面,我們可以給他設定特定的配置類。那如果現在有一個服務,我們只想對A服務配置一個攔截器攔截請求而不影響其他服務,那應該怎麼做呢? ### 一、feign介面配置 由前面的文章我們知道了feign的代理過程以及呼叫過程。現在我們看一下feign都有哪些配置?
gRPC-微服務間通訊實踐
##微服務間通訊常見的兩種方式 由於微服務架構慢慢被更多人使用後,迎面而來的問題是如何做好微服務間通訊的方案。我們先分析下目前最常用的兩種服務間通訊方案。 ###gRPC(rpc遠端呼叫) 場景:A服務主動發起請求到B服務,同步方式 範圍:只在微服務間通訊應用 ###EventBus(基於訊息佇列的整合事
Chris Richardson微服務翻譯:構建微服務之微服務架構的進程通訊
標記 pac blog ural action 客戶端 靈活 dso 不兼容 Chris Richardson 微服務系列翻譯全7篇鏈接: 微服務介紹 構建微服務之使用API網關 構建微服務之微服務架構的進程通訊(本文) 微服務架構中的服務發現 微服務之事件驅動的數據管理
微服務spring cloud—Hystrix簡介和通過方式整合H
Hystrix簡介 Hystrix是由Netflix開源的延遲和容錯庫,用於隔離訪問遠端系統、服務或者第三方庫,防止級聯失敗,從而提升系統的可用性與容錯性。Hystrix主要通過以下幾點實現延遲和容錯。 包裹請求:使用HystrixCommand(或HystrixObservable
GO-Grpc微服務開發二 服務呼叫for php
GO-Grpc微服務開發二 服務呼叫for php 參考文件列表 一.環境搭建 1.安裝grpc擴充套件 2.下載protoc命令 3.安裝PHP GRpc SDK 二.通過protoc檔案
GO-Grpc微服務開發四 服務呼叫for php
GO-Grpc微服務開發四 服務呼叫for php 參考文件列表 一.環境搭建 1.安裝grpc擴充套件 2.下載protoc命令 3.安裝PHP GRpc SDK 二.通過protoc檔案
GO-Grpc微服務開發二 服務編寫
GO-Grpc微服務開發二 服務編寫 服務編寫 1.定義proto檔案 2.將定義的proto編譯為go檔案 3.編寫服務 微服務執行 (本地環境示例) 啟動consul 啟動並註冊服務
GO-Grpc微服務開發一 概覽
GO-Grpc微服務開發一 概覽 概覽 專案地址 主要依賴/工具 目錄結構 概覽 專案地址 [email protected]:juelite/micro-srv.git 主要依賴/工具
微服務之間是如何獨立通訊的?
微服務通訊機制 系統中的各個微服務可被獨立部署,各個微服務之間是鬆耦合的。每個微服務僅關注於完成一件任務並很好地完成該任務。 圍繞業務能力組織服務、自動化部署、智慧端點、對語言及資料的去集中化控制。 將元件定義為可被獨立替換和升級的軟體單元。 以業務能力為出發點組織服務的策
基於場景選擇微服務的API正規化:REST、GraphQL、Webhooks和gRPC
看過了太多關於REST的熱愛和斷言,我們有時會忘記,這隻諸多選擇之一。REST對於相當大範疇的API來說是一個非常好的標準,但在一些需要API設計風格更細緻入微的場景,還有其他的標準可供選擇。 為了幫助API開發者瞭解使用哪種API設計風格以及在什麼情況下使用,我們把REST與其他三種選擇放在一起進行了一個