gRPC - 一元gRPC



現在我們將瞭解gRPC框架支援的各種通訊型別。我們將以書店為例,客戶可以在其中搜索書籍並下單送貨。

讓我們看看一元gRPC通訊,我們讓客戶端搜尋標題,並返回與查詢標題匹配的其中一本隨機書籍。

.proto 檔案

首先,讓我們在common_proto_files中定義bookstore.proto檔案:

syntax = "proto3";
option java_package = "com.tp.bookstore";

service BookStore {
   rpc first (BookSearch) returns (Book) {}
}
message BookSearch {
   string name = 1;
   string author = 2;
   string genre = 3;
}
message Book {
   string name = 1;
   string author = 2;
   int32 price = 3;
}

現在讓我們仔細看看上面程式碼塊中的每一行。

syntax = "proto3";

語法

這裡的“syntax”表示我們使用的Protobuf版本。我們使用最新的版本3,因此該模式可以使用所有對版本3有效的語法。

package tutorial;

這裡的package用於衝突解決,例如,如果我們有多個同名的類/成員。

option java_package = "com.tp.bookstore";

此引數特定於Java,即從.proto檔案自動生成程式碼的包。

service BookStore {
   rpc first (BookSearch) returns (Book) {}
}

這表示服務的名稱"BookStore"和可以呼叫的函式名"first""first"函式接收型別為"BookSearch"的輸入,並返回型別為"Book"的輸出。因此,實際上,我們讓客戶端搜尋標題,並返回與查詢標題匹配的其中一本書籍。

現在讓我們看看這些型別。

message BookSearch {
   string name = 1;
   string author = 2;
   string genre = 3;
}

在上面的程式碼塊中,我們定義了BookSearch,它包含名稱、作者型別等屬性。客戶端應該向伺服器傳送型別為“BookSearch”的物件。

message Book {
   string name = 1;
   string author = 2;
   int32 price = 3;
}

在這裡,我們還定義了,給定一個“BookSearch”,伺服器將返回包含書籍屬性以及書籍價格的“Book”。伺服器應該向客戶端傳送型別為“Book”的物件。

請注意,我們已經完成了Maven設定,用於自動生成我們的類檔案以及我們的RPC程式碼。因此,現在我們可以簡單地編譯我們的專案:

mvn clean install

這應該會自動生成我們使用gRPC所需的原始碼。原始碼將放在:

Protobuf class code: target/generated-sources/protobuf/java/com.tp.bookstore
Protobuf gRPC code: target/generated-sources/protobuf/grpc-java/com.tp.bookstore

設定gRPC伺服器

現在我們已經定義了包含函式定義的proto檔案,讓我們設定一個可以呼叫這些函式的伺服器。

讓我們編寫我們的伺服器程式碼來服務上述函式,並將其儲存在com.tp.bookstore.BookeStoreServerUnary.java中:

示例

package com.tp.bookstore;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import com.tp.bookstore.BookStoreOuterClass.Book;
import com.tp.bookstore.BookStoreOuterClass.BookSearch;
public class BookeStoreServerUnary {
   private static final Logger logger = Logger.getLogger(BookeStoreServerUnary.class.getName());
 
   static Map<String, Book> bookMap = new HashMap<>();
   static {
      bookMap.put("Great Gatsby", Book.newBuilder().setName("Great Gatsby")
         .setAuthor("Scott Fitzgerald")
         .setPrice(300).build());
      bookMap.put("To Kill MockingBird", Book.newBuilder().setName("To Kill MockingBird")
         .setAuthor("Harper Lee")
         .setPrice(400).build());
      bookMap.put("Passage to India", Book.newBuilder().setName("Passage to India")
         .setAuthor("E.M.Forster")
         .setPrice(500).build());
      bookMap.put("The Side of Paradise", Book.newBuilder().setName("The Side of Paradise")
         .setAuthor("Scott Fitzgerald")
         .setPrice(600).build());
      bookMap.put("Go Set a Watchman", Book.newBuilder().setName("Go Set a Watchman")
         .setAuthor("Harper Lee")
         .setPrice(700).build());
   }
   private Server server;
   private void start() throws IOException {
      int port = 50051;
      server = ServerBuilder.forPort(port)
         .addService(new BookStoreImpl()).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");
            try {
               server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
               e.printStackTrace(System.err);
            }
         }
      });
   }
   public static void main(String[] args) throws IOException, InterruptedException {
      final BookeStoreServerUnary greetServer = new BookeStoreServerUnary();
      greetServer.start();
      greetServer.server.awaitTermination();
   }
   static class BookStoreImpl extends BookStoreGrpc.BookStoreImplBase {
      @Override
      public void first(BookSearch searchQuery, StreamObserver<Book> responseObserver) {
         logger.info("Searching for book with title: " + searchQuery.getName());
         List<String> matchingBookTitles = bookMap.keySet().stream().filter(title ->
            title.startsWith(searchQuery.getName().trim())).collect(Collectors.toList());

         Book foundBook = null;
         if(matchingBookTitles.size() > 0) {
            foundBook = bookMap.get(matchingBookTitles.get(0));
         }
         responseObserver.onNext(foundBook);
         responseObserver.onCompleted();
      }
   }
}

上面的程式碼在指定的埠啟動一個gRPC伺服器,並服務於我們在proto檔案中編寫的函式和服務。讓我們一起瀏覽上面的程式碼:

  • main方法開始,我們在指定的埠建立一個gRPC伺服器。

  • 但在啟動伺服器之前,我們將伺服器分配給我們要執行的服務,即在我們的例子中是BookStore服務。

  • 為此,我們需要將服務例項傳遞給伺服器,因此我們繼續建立一個服務例項,即在我們的例子中是BookStoreImpl

  • 服務例項需要提供.proto檔案中存在的method/function的實現,即在我們的例子中是first方法。

  • 該方法期望一個在.proto檔案中定義的型別的物件,即對於我們來說是BookSearch

  • 該方法搜尋可用bookMap中的書籍,然後透過呼叫onNext()方法返回Book。完成後,伺服器透過呼叫onCompleted()來宣佈它已完成輸出。

  • 最後,我們還有一個關閉鉤子,以確保在完成程式碼執行時伺服器能夠乾淨地關閉。

設定gRPC客戶端

現在我們已經編寫了伺服器的程式碼,讓我們設定一個可以呼叫這些函式的客戶端。

讓我們編寫我們的客戶端程式碼來呼叫上述函式,並將其儲存在com.tp.bookstore.BookStoreClientUnaryBlocking.java中:

示例

package com.tp.bookstore;

import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.tp.bookstore.BookStoreOuterClass.Book;
import com.tp.bookstore.BookStoreOuterClass.BookSearch;
import com.tp.greeting.GreeterGrpc;
import com.tp.greeting.Greeting.ServerOutput;
import com.tp.greeting.Greeting.ClientInput;

public class BookStoreClientUnaryBlocking {
   private static final Logger logger = Logger.getLogger(BookStoreClientUnaryBlocking.class.getName());
   private final BookStoreGrpc.BookStoreBlockingStub blockingStub;
	
   public BookStoreClientUnaryBlocking(Channel channel) {
      blockingStub = BookStoreGrpc.newBlockingStub(channel);
   }
   public void getBook(String bookName) {
      logger.info("Querying for book with title: " + bookName);
      BookSearch request = BookSearch.newBuilder().setName(bookName).build();
 
   Book response; 
   try {
      response = blockingStub.first(request);
      } catch (StatusRuntimeException e) {
         logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
         return;
      }
      logger.info("Got following book from server: " + response);
   }
   public static void main(String[] args) throws Exception {
      String bookName = args[0];
      String serverAddress = "localhost:50051";
	  
      ManagedChannel channel = ManagedChannelBuilder.forTarget(serverAddress)
         .usePlaintext()
         .build();
 
      try {
         BookStoreClientUnaryBlocking client = new 
         BookStoreClientUnaryBlocking(channel);
         client.getBook(bookName);
      } finally {
         channel.shutdownNow().awaitTermination(5, 
         TimeUnit.SECONDS);
      }
   }
}

上面的程式碼在指定的埠啟動一個gRPC伺服器,並服務於我們在proto檔案中編寫的函式和服務。讓我們一起瀏覽上面的程式碼:

  • main方法開始,我們接受一個引數,即我們要搜尋的書籍的標題。

  • 我們為與伺服器的gRPC通訊設定一個Channel。

  • 然後,我們使用channel建立一個阻塞存根。在這裡,我們選擇要呼叫其函式的服務“BookStore”。“存根”只不過是一個包裝器,它隱藏了遠端呼叫對呼叫者的複雜性。

  • 然後,我們只需建立在.proto檔案中定義的預期輸入,即在我們的例子中是BookSearch,並新增我們想要伺服器搜尋的標題名稱。

  • 我們最終進行呼叫並等待來自伺服器的結果。

  • 最後,我們關閉channel以避免任何資源洩漏。

所以,這就是我們的客戶端程式碼。

客戶端伺服器呼叫

總而言之,我們要做的是:

  • 啟動gRPC伺服器。

  • 客戶端查詢伺服器以獲取具有給定名稱/標題的書籍。

  • 伺服器在其商店中搜索書籍。

  • 伺服器然後返回書籍及其其他屬性。

現在,我們已經定義了我們的proto檔案,編寫了我們的伺服器和客戶端程式碼,讓我們繼續執行這段程式碼,看看實際效果。

要執行程式碼,啟動兩個shell。透過執行以下命令在第一個shell中啟動伺服器:

java -cp .\target\grpc-point-1.0.jar 
com.tp.bookstore.BookeStoreServerUnary

我們將看到以下輸出:

輸出

Jul 03, 2021 7:21:58 PM 
com.tp.bookstore.BookeStoreServerUnary start
INFO: Server started, listening on 50051

上述輸出意味著伺服器已啟動。

現在,讓我們啟動客戶端。

java -cp .\target\grpc-point-1.0.jar 
com.tp.bookstore.BookStoreClientUnaryBlocking "To Kill"

我們將看到以下輸出:

輸出

Jul 03, 2021 7:22:03 PM 
com.tp.bookstore.BookStoreClientUnaryBlocking getBook
INFO: Querying for book with title: To Kill

Jul 03, 2021 7:22:04 PM 
com.tp.bookstore.BookStoreClientUnaryBlocking getBook
INFO: Got following book from server: name: "To Kill 

MockingBird"
author: "Harper Lee"
price: 400

因此,正如我們所看到的,客戶端能夠透過查詢伺服器來獲取書籍詳細資訊。

廣告