歡迎訪問我的GitHub
https://github.com/zq2599/blog_demos
內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;
《java版gRPC實戰》全系列連結
關於gRPC定義的四種型別
本文是《java版gRPC實戰》系列的第三篇,前文我們們實戰體驗了簡單的RPC請求和響應,那種簡單的請求響應方式其實只是gRPC定義的四種型別之一,這裡給出《gRPC 官方文件中文版》對這四種gRPC型別的描述:
- 簡單 RPC:客戶端使用存根(stub)傳送請求到伺服器並等待響應返回,就像平常的函式呼叫一樣;
- 伺服器端流式 RPC:客戶端傳送請求到伺服器,拿到一個流去讀取返回的訊息序列。 客戶端讀取返回的流,直到裡面沒有任何訊息;(即本篇內容)
- 客戶端流式 RPC:客戶端寫入一個訊息序列並將其傳送到伺服器,同樣也是使用流。一旦 客戶端完成寫入訊息,它等待伺服器完成讀取返回它的響應;
- 雙向流式 RPC:是雙方使用讀寫流去傳送一個訊息序列。兩個流獨立操作,因此客戶端和伺服器 可以以任意喜歡的順序讀寫:比如, 伺服器可以在寫入響應前等待接收所有的客戶端訊息,或者可以交替 的讀取和寫入訊息,或者其他讀寫的組合。 每個流中的訊息順序被預留;
本篇概覽
本篇是服務端流型別的gRPC服務實戰,包括以下內容:
- 開發一個gRPC服務,型別是服務端流;
- 開發一個客戶端,呼叫前面釋出的gRPC服務;
- 驗證;
- 不多說了,開始上程式碼;
原始碼下載
- 本篇實戰中的完整原始碼可在GitHub下載到,地址和連結資訊如下表所示(https://github.com/zq2599/blog_demos):
名稱 | 連結 | 備註 |
---|---|---|
專案主頁 | https://github.com/zq2599/blog_demos | 該專案在GitHub上的主頁 |
git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該專案原始碼的倉庫地址,https協議 |
git倉庫地址(ssh) | git@github.com:zq2599/blog_demos.git | 該專案原始碼的倉庫地址,ssh協議 |
- 這個git專案中有多個資料夾,《java版gRPC實戰》系列的原始碼在grpc-tutorials資料夾下,如下圖紅框所示:
- grpc-tutorials資料夾下有多個目錄,本篇文章對應的服務端程式碼在server-stream-server-side目錄下,客戶端程式碼在server-stream-client-side目錄下,如下圖:
開發一個gRPC服務,型別是服務端流
- 首先要開發的是gRPC服務端,一共要做下圖所示的七件事:
- 開啟grpc-lib模組,在src/main/proto目錄下新增檔案mall.proto,裡面定一個了一個gRPC方法ListOrders及其入參和返回物件,內容如下,要注意的是返回值要用關鍵字stream修飾,表示該介面型別是服務端流:
syntax = "proto3";
option java_multiple_files = true;
// 生成java程式碼的package
option java_package = "com.bolingcavalry.grpctutorials.lib";
// 類名
option java_outer_classname = "MallProto";
// gRPC服務,這是個線上商城的訂單查詢服務
service OrderQuery {
// 服務端流式:訂單列表介面,入參是買家資訊,返回訂單列表(用stream修飾返回值)
rpc ListOrders (Buyer) returns (stream Order) {}
}
// 買家ID
message Buyer {
int32 buyerId = 1;
}
// 返回結果的資料結構
message Order {
// 訂單ID
int32 orderId = 1;
// 商品ID
int32 productId = 2;
// 交易時間
int64 orderTime = 3;
// 買家備註
string buyerRemark = 4;
}
- 雙擊下圖紅框位置的generateProto,即可根據proto生成java程式碼:
- 新生成的java程式碼如下圖紅框:
- 在父工程grpc-turtorials下面新建名為server-stream-server-side的模組,其build.gradle內容如下:
// 使用springboot外掛
plugins {
id 'org.springframework.boot'
}
dependencies {
implementation 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter'
// 作為gRPC服務提供方,需要用到此庫
implementation 'net.devh:grpc-server-spring-boot-starter'
// 依賴自動生成原始碼的工程
implementation project(':grpc-lib')
}
- 新建配置檔案application.yml:
spring:
application:
name: server-stream-server-side
# gRPC有關的配置,這裡只需要配置服務埠號
grpc:
server:
port: 9899
- 啟動類:
package com.bolingcavalry.grpctutorials;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServerStreamServerSideApplication {
public static void main(String[] args) {
SpringApplication.run(ServerStreamServerSideApplication.class, args);
}
}
- 接下來是最關鍵的gRPC服務,程式碼如下,可見responseObserver.onNext方法被多次呼叫,用以向客戶端持續輸出資料,最後通過responseObserver.onCompleted結束輸出:
package com.bolingcavalry.grpctutorials;
import com.bolingcavalry.grpctutorials.lib.Buyer;
import com.bolingcavalry.grpctutorials.lib.Order;
import com.bolingcavalry.grpctutorials.lib.OrderQueryGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.util.ArrayList;
import java.util.List;
@GrpcService
public class GrpcServerService extends OrderQueryGrpc.OrderQueryImplBase {
/**
* mock一批資料
* @return
*/
private static List<Order> mockOrders(){
List<Order> list = new ArrayList<>();
Order.Builder builder = Order.newBuilder();
for (int i = 0; i < 10; i++) {
list.add(builder
.setOrderId(i)
.setProductId(1000+i)
.setOrderTime(System.currentTimeMillis()/1000)
.setBuyerRemark(("remark-" + i))
.build());
}
return list;
}
@Override
public void listOrders(Buyer request, StreamObserver<Order> responseObserver) {
// 持續輸出到client
for (Order order : mockOrders()) {
responseObserver.onNext(order);
}
// 結束輸出
responseObserver.onCompleted();
}
}
- 至此,服務端開發完成,我們們再開發一個springboot應用作為客戶端,看看如何遠端呼叫listOrders介面,得到responseObserver.onNext方法輸出的資料;
開發一個客戶端,呼叫前面釋出的gRPC服務
- 客戶端模組的基本功能是提供一個web介面,其內部會呼叫服務端的listOrders介面,將得到的資料返回給前端,如下圖:
- 在父工程grpc-turtorials下面新建名為server-stream-client-side的模組,其build.gradle內容如下:
plugins {
id 'org.springframework.boot'
}
dependencies {
implementation 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'net.devh:grpc-client-spring-boot-starter'
implementation project(':grpc-lib')
}
- 應用配置資訊application.yml內容如下,可見是埠和gRPC服務端地址的配置:
server:
port: 8081
spring:
application:
name: server-stream-client-side
grpc:
client:
# gRPC配置的名字,GrpcClient註解會用到
server-stream-server-side:
# gRPC服務端地址
address: 'static://127.0.0.1:9899'
enableKeepAlive: true
keepAliveWithoutCalls: true
negotiationType: plaintext
- 服務端的listOrders介面返回的Order物件裡面有很多gRPC相關的內容,不適合作為web介面的返回值,因此定義一個DispOrder類作為web介面返回值:
package com.bolingcavalry.grpctutorials;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@Data
@AllArgsConstructor
public class DispOrder {
private int orderId;
private int productId;
private String orderTime;
private String buyerRemark;
}
- 平淡無奇的啟動類:
package com.bolingcavalry.grpctutorials;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServerStreamClientSideApplication {
public static void main(String[] args) {
SpringApplication.run(ServerStreamClientSideApplication.class, args);
}
}
- 重點來了,GrpcClientService.java,裡面展示瞭如何遠端呼叫gRPC服務的listOrders介面,可見對於服務端流型別的介面,客戶端這邊通過stub呼叫會得到Iterator型別的返回值,接下來要做的就是遍歷Iterator:
package com.bolingcavalry.grpctutorials;
import com.bolingcavalry.grpctutorials.lib.Buyer;
import com.bolingcavalry.grpctutorials.lib.Order;
import com.bolingcavalry.grpctutorials.lib.OrderQueryGrpc;
import io.grpc.StatusRuntimeException;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@Service
@Slf4j
public class GrpcClientService {
@GrpcClient("server-stream-server-side")
private OrderQueryGrpc.OrderQueryBlockingStub orderQueryBlockingStub;
public List<DispOrder> listOrders(final String name) {
// gRPC的請求引數
Buyer buyer = Buyer.newBuilder().setBuyerId(101).build();
// gRPC的響應
Iterator<Order> orderIterator;
// 當前方法的返回值
List<DispOrder> orders = new ArrayList<>();
// 通過stub發起遠端gRPC請求
try {
orderIterator = orderQueryBlockingStub.listOrders(buyer);
} catch (final StatusRuntimeException e) {
log.error("error grpc invoke", e);
return new ArrayList<>();
}
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
log.info("start put order to list");
while (orderIterator.hasNext()) {
Order order = orderIterator.next();
orders.add(new DispOrder(order.getOrderId(),
order.getProductId(),
// 使用DateTimeFormatter將時間戳轉為字串
dtf.format(LocalDateTime.ofEpochSecond(order.getOrderTime(), 0, ZoneOffset.of("+8"))),
order.getBuyerRemark()));
log.info("");
}
log.info("end put order to list");
return orders;
}
}
- 最後做一個controller類,對外提供一個web介面,裡面會呼叫GrpcClientService的方法:
package com.bolingcavalry.grpctutorials;
import com.bolingcavalry.grpctutorials.lib.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class GrpcClientController {
@Autowired
private GrpcClientService grpcClientService;
@RequestMapping("/")
public List<DispOrder> printMessage(@RequestParam(defaultValue = "will") String name) {
return grpcClientService.listOrders(name);
}
}
- 至此,編碼完成,開始驗證
驗證
- 啟動server-stream-server-side,啟動成功後會監聽9989埠:
- 啟動server-stream-client-side,再在瀏覽器訪問:http://localhost:8081/?name=Tom ,得到結果如下(firefox自動格式化json資料),可見成功地獲取了gRPC的遠端資料:
至此,服務端流型別的gRPC介面的開發和使用實戰就完成了,接下來的章節還會繼續學習另外兩種型別;
你不孤單,欣宸原創一路相伴
歡迎關注公眾號:程式設計師欣宸
微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...
https://github.com/zq2599/blog_demos