grpc實戰——服務端流式呼叫
本文地址:
傳送門:
還記得很久之前Sunny有和大家聊過如何用grpc實現一個簡單的名稱解析服務,當時用的grpc簡單呼叫。這次我們本著從易到難的原則,對上次的更進一步,實現服務端流式呼叫。之後還會繼續出客戶端流式呼叫和雙向流式呼叫的文章,喜歡的朋友可以繼續關注。
這次我們的背景還是構建一個名稱解析服務,但是有所不同的是,我們這次一個名稱可能對應多個ip(這在實際生活中也有應用,比如DNS負載均衡)。
服務端
首先還是來看pom.xml檔案,這次我們和上次有所不同,我直接使用grpc-all依賴一次性匯入所需的依賴,具體依賴部分程式碼如下:
io.grpc grpc-all ${grpc.version}
這裡我們版本還是和之前選擇的一樣,為1.12.0。其他部分和之前的一樣,想了解的童鞋可以看看原始碼也可以看傳送門中的上一篇文章。
然後就是比較重要的proto檔案了,定義了我們的服務,這裡和grpc簡單服務略有不同:
syntax = "proto3"; option java_multiple_files = true; option java_package = "io.grpc.examples.nameservers"; option java_outer_classname = "NameProto"; option objc_class_prefix = "NSS"; package nameservers;// 定義服務service NameServers { // 服務中的方法,用於根據Name型別的引數獲得一系列ip,以流的方式返回 rpc getIpsByName (Name) returns (stream Ip) {} }//定義Name訊息型別,其中name為其序列為1的欄位message Name { string name = 1; }//定義Ip訊息型別,其中ip為其序列為1的欄位message Ip { string ip = 1; }
細心的童鞋可能發現了,這個基本上和上次一模一樣,除了服務的名稱還有配置項可能有所不同,其他就是在returns中,Ip前面多了一個stream。這個就是我們所說的服務端流式的服務定義方式了。接著還是利用maven外掛來進行編譯得到相應的java程式碼。
圖片.png
這裡我們還是從服務端開始寫,也是分為兩個部分,一個用於開啟遠端呼叫服務,接收客戶端發來的呼叫請求,類名為NameServer;另一個則是實現真正的服務,類名為NameServersImplBaseImpl。這裡NameServer程式碼變化不大,直接貼上程式碼:
public class NameServer { private Logger logger = Logger.getLogger(NameServer.class.getName()); private static final int DEFAULT_PORT = 8088; private int port;//服務埠號 private Server server; public NameServer(int port) { this(port,ServerBuilder.forPort(port)); } public NameServer(int port, ServerBuilder> serverBuilder){ this.port = port; //構造伺服器,新增我們實際的服務 server = serverBuilder.addService(new NameServersImplBaseImpl()).build(); } private void start() throws IOException { server.start(); logger.info("Server has started, listening on " + port); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { NameServer.this.stop(); } }); } private void stop() { if(server != null) server.shutdown(); } //阻塞到應用停止 private void blockUntilShutdown() throws InterruptedException { if (server != null) { server.awaitTermination(); } } public static void main(String[] args) throws IOException, InterruptedException { NameServer nameServer; if(args.length > 0){ nameServer = new NameServer(Integer.parseInt(args[0])); }else{ nameServer = new NameServer(DEFAULT_PORT); } nameServer.start(); nameServer.blockUntilShutdown(); } }
實際提供服務的程式碼有所改變,首先,我們之前用map來儲存Name和Ip的對映關係,現在因為存在同Name多Ip的情況,我們改用list來儲存,如果資料實際儲存於資料庫就看具體欄位的約束。然後增加了一個類DataType用於表示Name和Ip資料對。在實際的服務中對每次請求都需要遍歷一遍list,將Name符合的Ip返回給客戶端,這裡的返回就是以流的方式返回的。
public class NameServersImplBaseImpl extends NameServersGrpc.NameServersImplBase { //記錄名稱內容的list,實際專案中應該放置在資料庫 private Listlist = new ArrayList (); //構造方法中加入一些條目 public NameServersImplBaseImpl() { list.add(new DataType(Name.newBuilder().setName("Sunny").build(),Ip.newBuilder().setIp("125.216.242.51").build())); list.add(new DataType(Name.newBuilder().setName("Sunny").build(),Ip.newBuilder().setIp("126.216.242.51").build())); list.add(new DataType(Name.newBuilder().setName("David").build(),Ip.newBuilder().setIp("117.226.178.139").build())); list.add(new DataType(Name.newBuilder().setName("David").build(),Ip.newBuilder().setIp("117.227.178.139").build())); list.add(new DataType(Name.newBuilder().setName("Tom").build(),Ip.newBuilder().setIp("111.222.336.11").build())); list.add(new DataType(Name.newBuilder().setName("Tom").build(),Ip.newBuilder().setIp("111.333.336.11").build())); list.add(new DataType(Name.newBuilder().setName("Tom").build(),Ip.newBuilder().setIp("111.222.335.11").build())); } @Override public void getIpsByName(Name requestName, StreamObserver responseObserver) { Iterator iter = list.iterator(); while (iter.hasNext()){ DataType data = iter.next(); if(requestName.equals(data.getName())){ System.out.println("get " + data.getIp() + " from " + requestName); responseObserver.onNext(data.getIp()); } } responseObserver.onCompleted(); } }
DataType類如下:
class DataType{ private Name name; private Ip ip; public DataType(Name name, Ip ip) { this.name = name; this.ip = ip; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } public Ip getIp() { return ip; } public void setIp(Ip ip) { this.ip = ip; } }
上面程式碼中,list使用迭代器的方式遍歷,這個大家也可以使用下標的方式,大家需要關注的是,這裡使用了Name的equals方法,Sunny怎麼就敢用equals方法,確定Name類中有重寫equals方法嗎?確實重寫了,我們來看它的equals方法長什麼樣。
public boolean equals(final java.lang.Object obj) { if (obj == this) { return true; } if (!(obj instanceof io.grpc.examples.nameservers.Name)) { return super.equals(obj); } io.grpc.examples.nameservers.Name other = (io.grpc.examples.nameservers.Name) obj; boolean result = true; result = result && getName() .equals(other.getName()); result = result && unknownFields.equals(other.unknownFields); return result; }
不出意料,首先比較是不是同一個物件,如果是那還說啥,直接true。如果傳入的這個物件不是Name類,那麼就呼叫父類的equals方法。緊接著進行了一次型別強轉。然後定義了一個boolean型別的變數result,實現起來也比較巧妙,利用&&的方法來確保符合所有兩個條件,第一個就是兩個訊息中name欄位是否equals——這裡是String型別的,實際呼叫了String的equals。然後還要比較unknownFields是否也equals,這裡unknownFields是Name父類中的一個UnknownFieldSet型別的成員變數。根據名字大概可以猜到這就是未知的欄位,我們這裡的unknownFields中維護的fields物件應該就是null了。因此實際上判斷兩個Name型別是否equals,只需要判斷它們維護的name這個String型別的值是否equals。
到這裡,服務端基本完成了。
客戶端
首先是pom.xml,這裡我們和服務端一樣用grpc-all依賴。然後是proto生成java類,這裡我們採取和上一篇不同的方法,上一篇我們是用proto檔案重新編譯,然後生成的。這裡,我們直接將服務端生成的程式碼複製到客戶端的目錄中。
圖片.png
客戶端NameClient中的程式碼也有所不同,它需要接收一系列的服務端發過來的流訊息。話不多說,我們直接上程式碼:
public class NameClient { private static final String DEFAULT_HOST = "localhost"; private static final int DEFAULT_PORT = 8088; private ManagedChannel managedChannel; //服務存根,用於客戶端本地呼叫 private NameServersGrpc.NameServersBlockingStub nameServiceBlockingStub; public NameClient(String host, int port) { this(ManagedChannelBuilder.forAddress(host,port).usePlaintext(true).build()); } public NameClient(ManagedChannel managedChannel) { this.managedChannel = managedChannel; this.nameServiceBlockingStub = NameServersGrpc.newBlockingStub(managedChannel); } public void shutdown() throws InterruptedException { managedChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } public ListgetIpsByName(String n){ List result = new ArrayList (); Name name = Name.newBuilder().setName(n).build(); Iterator iterator = nameServiceBlockingStub.getIpsByName(name); while (iterator.hasNext()){ result.add(iterator.next()); } return result; } public static void main(String[] args) { NameClient nameClient = new NameClient(DEFAULT_HOST,DEFAULT_PORT); for(String arg : args){ List result = nameClient.getIpsByName(arg); for(int i=0;i 還是一樣,上一篇說過的我們就不再老生常談了,想了解的童鞋請移步傳送門。我們來看不一樣的地方,getIpsByName方法有所變化:
public ListgetIpsByName(String n){ List result = new ArrayList (); Name name = Name.newBuilder().setName(n).build(); Iterator iterator = nameServiceBlockingStub.getIpsByName(name); while (iterator.hasNext()){ result.add(iterator.next()); } return result; } 這裡我們的返回值不是簡單的Ip了,而是一個Ip型別的迭代器,這裡我們還是選擇再得到迭代器後把結果包裝進一個list中,然後返回這個list。相應地,這樣在main函式中呼叫getIpsByName的方法的時候因為返回值有所不同了,所以也需要有相應的改變。
執行驗證結果
我們首先執行服務端NameServer,得到結果:
Jun 10, 2018 6:24:19 PM com.sunny.NameServer start資訊: Server has started, listening on 8088服務在8088埠上啟動了,只要有客戶端連線這個埠併傳送請求即可。
然後啟動客戶端,注意在啟動前加入程式引數Sunny David Tom圖片.png
啟動客戶端NameClient,得到了我們請求的結果:
get result from server: ip: "125.216.242.51" as param is Sunny get result from server: ip: "126.216.242.51" as param is Sunny get result from server: ip: "117.226.178.139" as param is David get result from server: ip: "117.227.178.139" as param is David get result from server: ip: "111.222.336.11" as param is Tom get result from server: ip: "111.333.336.11" as param is Tom get result from server: ip: "111.222.335.11" as param is Tom同樣在服務端的控制檯,我們也可以看到我們列印的服務呼叫資訊:
get ip: "125.216.242.51" from name: "Sunny"get ip: "126.216.242.51" from name: "Sunny"get ip: "117.226.178.139" from name: "David"get ip: "117.227.178.139" from name: "David"get ip: "111.222.336.11" from name: "Tom"get ip: "111.333.336.11" from name: "Tom"get ip: "111.222.335.11" from name: "Tom"至此,一個grpc服務端流式呼叫就做完了。
再次說明一下,點選可以看到本專案的原始碼,裡面也包括了上一篇的原始碼。歡迎大家fork實踐體驗。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/151/viewspace-2803708/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Dapr + .NET Core實戰(十二)服務呼叫之GRPCRPC
- GRPC 服務呼叫實踐(一)RPC
- java版gRPC實戰之三:服務端流JavaRPC服務端
- C# 實現 gRPC 服務和呼叫C#RPC
- Go gRPC 系列三:流式客戶端和服務端GoRPC客戶端服務端
- 使用Golang搭建gRPC服務提供給.NetCore客戶端呼叫GolangRPCNetCore客戶端
- grpc套路(四)php通過grpc呼叫golang的grpc介面服務RPCPHPGolang
- grpc套路服務端編寫RPC服務端
- Go基於gRPC實現客戶端連入服務端GoRPC客戶端服務端
- .NET Core微服務開發服務間呼叫篇-GRPC微服務RPC
- gRPC學習之四:實戰四類服務方法RPC
- openlayer呼叫wms服務端服務端
- Java使用HttpClient實現遠端服務呼叫JavaHTTPclient
- SpringCloud Alibaba實戰(8:使用OpenFeign服務呼叫)SpringGCCloud
- gRPC 之流式呼叫原理 http2 協議分析(四)RPCHTTP協議
- [菜鳥SpringCloud入門]第四章:遠端呼叫服務實戰SpringGCCloud
- Go+gRPC-Gateway(V2) 微服務實戰,小程式登入鑑權服務(六):客戶端基礎庫 TS 實戰GoRPCGateway微服務客戶端
- Google遠端過程呼叫-GRPCGoRPC
- ②SpringCloud 實戰:引入Feign元件,發起服務間呼叫SpringGCCloud元件
- 【.NET6】gRPC服務端和客戶端開發案例,以及minimal API服務、gRPC服務和傳統webapi服務的訪問效率大對決RPC服務端客戶端APIWeb
- Next.js 服務端渲染框架實戰JS服務端框架
- Go+gRPC-Gateway(V2) 微服務實戰,小程式登入鑑權服務(五):鑑權 gRPC-Interceptor 攔截器實戰GoRPCGateway微服務
- Go gRPC 系列二:一元客戶端與服務端GoRPC客戶端服務端
- C# netCore Grpc服務 (2)配置 ,proto以及四種呼叫方式C#NetCoreRPC
- SpringCloud 實戰:禁止直接訪問後端服務SpringGCCloud後端
- Go語言高併發與微服務實戰專題精講——遠端過程呼叫 RPC——服務端註冊實現原理分析Go微服務RPC服務端
- spring cloud feign實現遠端呼叫服務傳輸檔案SpringCloud
- 服務端呼叫微信小程式OCR識別介面實現服務端微信小程式
- Go語言高併發與微服務實戰專題精講——遠端過程呼叫 RPC——高效能的 gRPCGo微服務RPC
- 【GoLang 那點事】gRPC 之流式呼叫原理 http2 協議分析(四)GolangRPCHTTP協議
- java版gRPC實戰之四:客戶端流JavaRPC客戶端
- 本地服務呼叫K8S環境中的SpringCloud微服務實戰K8SSpringGCCloud微服務
- 分散式服務框架 gRPC分散式框架RPC
- 讓 gRPC 提供 REST 服務RPCREST
- SpringBoot 實戰 (十五) | 服務端引數校驗之一Spring Boot服務端
- Dubbo原始碼分析(五)Dubbo呼叫鏈-服務端原始碼服務端
- (2)什麼是服務拆分和遠端呼叫
- gRPC(八)生態 grpc-gateway 應用:同一個服務端支援Rpc和Restful ApiRPCGateway服務端RESTAPI