開始食用grpc(之一)
轉載請註明出處:https://www.cnblogs.com/funnyzpc/p/9501353.html
```
記一次和一鍋們壓馬路,路過一咖啡廳(某巴克),隨口就問隨行的鍋門:你能從那咖啡廳看到什麼?
當時的那家某巴克處於鬧市,也正值週末,屋外屋內喝咖啡的人幾近乎十分的安靜,使用電腦的,刷手機的、做作業的。。。而且大都是年輕人和中年人。
鍋門撂了句:一群屌絲唄 (;¬_¬)
。。。白了他一眼(¬_¬)
( ...其實想教唆他進去看看美女,歇歇腳來著 ๑乛◡乛๑ )
.......
許久之後,也就是最近看到詩人餘秀華的一句話後忽有所感,原句是:
“反正是揹負慢慢凋殘的孤獨,耀眼的孤獨,義無反顧的孤獨”
這才明白他們是在消費孤獨,也為孤獨所消費; 他們是,周圍的人是,還有 ( ∙̆ .̯ ∙̆ )
那~ 孤獨的結果是什麼呢 ?
```
這次講下大系統通訊必備的一項元件:rpc,rpc有很多 如 dubbo、thirft、feign、motan、grpc 等~,這其中有字串方式的也有二進位制流方式的;整體來說二進位制方式的一般會較字串方式的快許多,字元形式的的慢,但是簡單;而二進位制方式的 序列化和跨平臺較為麻煩些;我個人選取rpc時一般綜合考慮一下幾點:
A>傳輸方式是否是二進位制
B>是否長期支援
C>是否支援跨平臺,開源元件是否豐富
C+>是否支援非同步呼叫
D>有無明顯的bug或缺點
E>維護和開發是否有好
綜合下來,個人堅定之選擇grpc,這個初出茅廬(2015年釋出)的東東,十分強大:
>> http2流方式
>> 自帶非同步特性
>> 跨平臺(支援11種語言)
>> 無需zookeeper這類單獨的服務中心
>> 對開發人員比較友好
>> 服務呼叫方可設定請求頭
當然缺點也是存在的:需要單獨寫proto檔案(生成目標語言的一套語法定義)、變數為null時會賦予預設初始值、鏈式呼叫(還好呼叫介面較為單一,只是語法較為怪異)...
如果您在意以上缺點,可繞過本文哈~
ok,現在開始我開始講grpc,內容大致有四:
A->grpc的簡單配置 (本節)
A>簡單grpc編寫 (本節)
B>複雜grpc proto服務檔案編寫 (本節)
C>雙向流式呼叫方法及注意事項 (下一節)
D>grpc安全問題及攔截器 (下一節)
grpc的配置:
這裡我的工程是基於springboot,同時為簡化開發起見,我使用 grpc-spring-boot-starter ,開始之前先感謝這位開發者為簡化grpc的java平臺簡化了太多的開發,同時也為springcloud融合做了太多共享,非常感謝~!
這裡,首先得準備三個springboot模組,這三個模組包含:grpc proto3檔案生成模組、grpc 客戶端、grpc 服務端,我的工程結構大致是這樣子的(工程是多模組的):
這裡面的三個模組一看就懂,就不細講啦~,準備好這三個模組後,依次配置依賴包及引數:
服務端(preview-grpc-server):
pom.xml中依賴包配置>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>net.devh</groupId> <artifactId>grpc-client-spring-boot-autoconfigure</artifactId> <version>RELEASE</version> </dependency>
<!-- 由於我的工程是多模組的,若不作為jar包引入,也可以將preview-grpc-lib中的java檔案拷貝到當前工程內也可 -->
<dependency>
<groupId>com.github.carvechris</groupId>
<artifactId>preview-grpc-lib</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
配置檔案yml(如果是properties檔案也可參照此配置):
1 grpc: 2 server: 3 port: 2804 4 5 spring: 6 application: 7 name: preview-grpc-server
(注意:一定要定義應用名稱,在呼叫的時候會用到應用名稱的,這裡是:preview-grpc-server)
客戶端(preview-grpc-client):
pom.xml檔案依賴包配置>
<dependency> <groupId>net.devh</groupId> <artifactId>grpc-client-spring-boot-starter</artifactId> <version>1.4.0.RELEASE</version> </dependency>
<!-- 由於我的工程是多模組的,若不作為jar包引入,也可以將preview-grpc-lib中的java檔案拷貝到當前工程內也可 -->
<dependency>
<groupId>com.github.carvechris</groupId>
<artifactId>preview-grpc-lib</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
yml配置檔案引數:
1 grpc: 2 client: 3 preview-grpc-server: 4 host: 5 - 127.0.0.1 6 port: 7 - 2804 8 enableKeepAlive: true 9 keepAliveWithoutCalls: true
proto檔案生成模組(preview-grpc-lib)配置:
pom.xml檔案依賴包配置:
1 <!--依賴配置--> 2 <dependencies> 3 <dependency> 4 <groupId>io.grpc</groupId> 5 <artifactId>grpc-netty</artifactId> 6 <version>${grpc.version}</version> 7 </dependency> 8 <dependency> 9 <groupId>io.grpc</groupId> 10 <artifactId>grpc-protobuf</artifactId> 11 <version>${grpc.version}</version> 12 </dependency> 13 <dependency> 14 <groupId>io.grpc</groupId> 15 <artifactId>grpc-stub</artifactId> 16 <version>${grpc.version}</version> 17 </dependency> 18 </dependencies> 19 20 <!--proto3檔案生成java程式碼外掛配置--> 21 <build> 22 <extensions> 23 <extension> 24 <groupId>kr.motd.maven</groupId> 25 <artifactId>os-maven-plugin</artifactId> 26 <version>${os.plugin.version}</version> 27 </extension> 28 </extensions> 29 <plugins> 30 <plugin> 31 <groupId>org.xolstice.maven.plugins</groupId> 32 <artifactId>protobuf-maven-plugin</artifactId> 33 <version>${protobuf.plugin.version}</version> 34 <configuration> 35 <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact> 36 <pluginId>grpc-java</pluginId> 37 <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact> 38 </configuration> 39 </plugin> 40 </plugins> 41 </build>
配置完成,這裡需要特別說明一下:server模組和client模組的web服務各佔一個埠,另外,server模組還會給grpc單獨分配一個埠,就我的preview-grpc-server來說:
服務名稱(name)是:preview-grpc-server
服務佔用的埠是:2804
切記,不論是web服務還是grpc服務的埠都不能重複,同時一定要理清楚web服務和grpc服務所佔用的埠和ip。
簡單grpc服務(helloworld.proto)編寫:
這裡我先展示下我的生成模組的大致樣子:
需要說明的是:編寫的proto檔案均在proto目錄下,java目錄下是proto檔案生成的java程式碼,這裡的java檔案是從target目錄總複製到java目錄下的,包名一定要與proto裡面宣告的包名一致!
java程式碼生成模組proto3服務檔案(helloworld.proto)的編寫:
1 syntax = "proto3"; 2 3 // 是否拆分類檔案 4 option java_multiple_files = true; 5 // 生成的檔案所在的包 6 option java_package = "com.funnyzpc.xxx.grpc.lib.hello"; 7 // 輸出類主檔案(此配置可選) 8 option java_outer_classname = "HelloWorldProto"; 9 10 // 定義一個服務 11 service Simple { 12 // Sends a greeting 13 rpc SayHello (HelloRequest) returns (HelloReply) { 14 } 15 } 16 17 // 請求體定義 18 message HelloRequest { 19 string name = 1; 20 } 21 22 // 響應體定義 23 message HelloReply { 24 string message = 1; 25 }
現在開始使用idea提供的快捷功能生成客戶端和服務端java檔案(當然也可以使用mvn命令手動生成):
將生成的java檔案複製到應用目錄:
(注意:複製後一定要清理target目錄,不然檔案重複會報錯!)
在客戶端(preview-grpc-client)編寫一個grpc服務請求類(GrpcSimpleService.java):
1 @Service 2 public class GrpcSimpleService { 3 4 @GrpcClient("preview-grpc-server") 5 private Channel serverChannel; 6 7 public String sendMessage(String name) { 8 SimpleGrpc.SimpleBlockingStub stub = SimpleGrpc.newBlockingStub(serverChannel); 9 HelloReply response = stub.sayHello(HelloRequest.newBuilder().setName(name).build()); 10 return response.getMessage(); 11 } 12 }
在服務端(preview-grpc-server)編寫對應的grpc的服務類:
1 /** 2 * 簡單grpc服務類 3 */ 4 @GrpcService(SimpleGrpc.class) 5 public class GrpcServerService extends SimpleGrpc.SimpleImplBase { 6 private static final Logger LOG=LoggerFactory.getLogger(GrpcServerService.class); 7 8 @Override 9 public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) { 10 HelloReply reply = HelloReply.newBuilder().setMessage("Hello ===> " + req.getName()).build(); 11 responseObserver.onNext(reply); 12 responseObserver.onCompleted(); 13 } 14 15 }
上面的@GrpcService是grpc元件的註解,註解中必須宣告所使用的grpc(生成的類中的)服務類,同時還可以宣告所使用的攔截器(可選)
OK,現在新增一個控制器(在preview-grpc-client中編寫一個控制器),試試看:
完美,。。。可能有人proto檔案一知半解,接下來進入下一節。
複雜grpc proto服務檔案編寫:
首先,我先推薦兩個官方網站,若能理解官網內容,可繞過本節
grpc java平臺api及樣例>
https://grpc.io/docs/quickstart/java.html
protocol buffers,proto3檔案語法>
https://developers.google.com/protocol-buffers/docs/proto3?hl=zh-cn
一般在跨應用呼叫時,所傳遞的引數有時候為複雜物件,比如這樣{page_no:1,page_size:20,data:{name:XXX,type:2}},這裡就寫好的複雜層級物件講解下:
(MultiObject.proto)
1 syntax = "proto3"; 2 3 // option java_multiple_files = true; 4 option java_package = "com.github.carvechris.security.grpc.lib.multi"; 5 6 service MultiObjService{ 7 rpc queryObj (MultiObjReq) returns (MultiObjResp) { 8 9 } 10 11 } 12 13 message MultiObjReq{ 14 int32 page_no=1; 15 int32 page_size=2; 16 MultiObjDataReq data=3; 17 } 18 19 message MultiObjDataReq{ 20 string name=1; 21 int32 type=2; 22 } 23 24 message MultiObjResp{ 25 string req_str=1; 26 MultiObjFirstResp first=2; 27 } 28 message MultiObjFirstResp{ 29 string f_content=1; 30 MultiObjNextResp next=2; 31 32 } 33 message MultiObjNextResp{ 34 string n_content =1; 35 }
檔案中定義一個服務 必須以關鍵字 service 開始,上面的 MultiObjReq 與 MultiObjResp 分別為服務的請求和響應物件,這兩個物件在生成java檔案後,每個請求物件都是一個單獨的類(可在一個java中也可不在,若不在需要定義:option java_multiple_files = true;),不管是請求物件還是響應物件,都需要單獨宣告這個物件以及物件中的變數型別及所處的位置,就像這樣:
1 message MultiObjReq{ 2 int32 page_no=1; 3 int32 page_size=2; 4 MultiObjDataReq data=3; 5 }
自定義型別需要在單獨定義,比如"MultiObjDataReq";在上面這個例子中,定義的請求物件MultiObjReq的第一個欄位為 page_no,第一個為page_size,第三個為定義的一個物件,每個引數開始需標明當前欄位型別,如果這個欄位是自定義型別時無需定義資料型別(但是一定要有引數序號,當然也可以定義一個map型別);另外,通用欄位型別同go語言的資料型別(參照以上鍊接);注意,請求或響應物件定義時必須以關鍵字message開始。
另外,請注意,如果某個欄位是個列表(java中的List),需要在欄位或者物件前新增關鍵字 repeated ,這樣:
//返回體資料定義 message GrpcResp { string sign=3; string msg=4; repeated datas detail=5; } message datas{ uint64 id=1; string address=2; double isadmin=3; }
現在展示上面的MultiObject.proto檔案 所編寫的客戶端和服務端類:
客戶端(preview-grpc-client):
(GrpcMultiObjClientService.java)
1 @Service 2 public class GrpcMultiObjClientService { 3 4 @GrpcClient("preview-grpc-service") 5 private Channel serverChannel; 6 7 public Object testMultiObj() { 8 9 MultiObject.MultiObjDataReq reqData=MultiObject.MultiObjDataReq.newBuilder() 10 .setName("queryName") 11 .setType(33) 12 .build(); 13 14 MultiObject.MultiObjReq req=MultiObject.MultiObjReq.newBuilder() 15 .setPageNo(11) 16 .setPageSize(22) 17 .setData(reqData) 18 .build(); 19 20 MultiObjServiceGrpc.MultiObjServiceBlockingStub stb=MultiObjServiceGrpc.newBlockingStub(serverChannel); 21 MultiObject.MultiObjResp resp=stb.queryObj(req); 22 23 Map<String,Object> reMap=new HashMap<>(); 24 reMap.put("getFContent",resp.getFirst().getFContent()); 25 reMap.put("getNContent",resp.getFirst().getNext().getNContent()); 26 reMap.put("getReqStr",resp.getReqStr()); 27 return reMap; 28 } 29 30 }
服務端(preview-grpc-server):
(GrpcMultiObjService.java)
1 @GrpcService(MultiObjServiceGrpc.class) 2 public class GrpcMultiObjService extends MultiObjServiceGrpc.MultiObjServiceImplBase{ 3 private static final Logger LOG=LoggerFactory.getLogger(GrpcMultiObjService.class); 4 5 @Override 6 public void queryObj(MultiObject.MultiObjReq request,StreamObserver<MultiObject.MultiObjResp> responseObserver) { 7 8 LOG.info("MultiObjServiceGrpc>start"); 9 Map<String,Object> reqData=new HashMap<String,Object>(); 10 reqData.put("getPageNo",request.getPageNo()); 11 reqData.put("getPageSize",request.getPageSize()); 12 reqData.put("getName",request.getData().getName()); 13 reqData.put("getType",request.getData().getType()); 14 15 MultiObject.MultiObjNextResp next=MultiObject.MultiObjNextResp.newBuilder().setNContent("n_content").build(); 16 MultiObject.MultiObjFirstResp first=MultiObject.MultiObjFirstResp.newBuilder().setFContent("f_content").setNext(next).build(); 17 MultiObject.MultiObjResp resp=MultiObject.MultiObjResp.newBuilder().setReqStr(JSON.toJSONString(reqData)).setFirst(first).build(); 18 19 responseObserver.onNext(resp); 20 responseObserver.onCompleted(); 21 LOG.info("MultiObjServiceGrpc>end"); 22 } 23 24 }
現在是 2018-08-26 02:13:42
由於在雙向流編寫及測試環節碰到些問題,耽擱了許久,此次會將雙向流和安全及攔截器放在下一篇講,各位,晚安 (=。=)