一文掌握gRPC

文章目錄

    • 1. gRPC簡介
    • 2. Http2.0協議
    • 3. 序列化-Protobuf
    • 4. gRPC開發實戰環境搭建
    • 5. gRPC的四種通信方式(重點)
    • 6. gRPC的代理方式
    • 7. SprintBoot整合gRPC

1. gRPC簡介

gRPC是由google開源的高性能的RPC框架。它是由google的Stubby這樣一個內部的RPC框架演化出來,gRPC2015年開源,目前是在云原生時代的一個RPC的標準。

gRPC的核心設計思路:

  • 協議:使用Http2協議(傳輸數據使用二進制數據內容、支持雙向流[雙工]、連接的多路復用)
  • 序列化:基于二進制(protobuf- 谷歌開源的一種序列化方式)
  • 代理的創建 :讓調用者像調本地方法一樣去調用遠端的方法

Thrift和gRPC的區別:首先Thrift和gRPC這兩個RPC框架有一個共性,就是都支持異構語言的RPC

  • 網絡通信層面:Thrift自己定義了自己的協議,直接基于TCP協議,而gRPC的協議是HTTP2的協議
  • 性能角度:ThriftRPC的效率是高于gRPC
  • gRPC是大廠背書(google),云原生時代gRPC與其它組件合作的更加好,所以gRPC應用的更廣泛

gRPC的優點:

  • 能夠高效的進行進程間通信(協議+序列化)
  • 支持多種語言,對主流的語言提供了原生的支持(C、GO、Java)
  • 支持多平臺運行(Linux、Android、IOS、Mac1OS、Windows)
  • grpc序列化方式使用prorubuf、效率高
  • 使用Http2協議
  • google大廠背書

2. Http2.0協議

回顧HTTP1.x協議

  • Http1.0協議:基于請求響應的模式,并且是一個短連接(無狀態的協議),傳輸文本格式的數據,并且是單工的(只有客戶端找服務端,而無法實現服務端的主動推送)。

這里思考一個問題:Http底層使用TCP協議,TCP是一個長連接協議,為什么Http1.0是一個短連接協議?

因為HTTP1.0的短連接是自己的設計造成的,一次數據發送完畢就會主動斷開,導致了Http1.0是一個短連接協議。這種設計是因為當時服務器性能較差,無法支持維持大量的長連接。

  • Http1.1協議:基于請求響應模式,但它支持有限的長連接(保持一段時間,一段時間Keepalived字段后自動斷開連接),基于此出現了WebSocket技術。

總結:Http1.x協議它們傳輸數據都是文本格式(Http1.1請求體可以是二進制數據),可讀性好但是效率低。Http1.x協議無法實現雙工通信。Http1.x資源請求時,需要發送多個請求,建立多個連接(比如客戶端從服務端拿一個網頁,一次請求拿到了這個頁面,但是這個頁面如果里面有超鏈接、和CSS以及js資源,而這寫資源又要發送新的請求,所以需要建立多個連接)

Http2.0協議

  • Http2.0協議是一個二進制協議,傳輸效率高于Http1.x協議,但是二進制可讀性差
  • 可以實現雙工通信
  • 一個請求可以請求多次和多個數據(多路復用)

為什么Http2.0可以實現多路復用?

Http2.0抽取了3個重要的概念,分別是數據流(Stream)、消息(Message)和幀(Frame),這三個概念有機的整合在一起就可以實現多路復用。

在這里插入圖片描述

  1. Http2.0發送的一個數據時是以一個Stream為宏觀單位的,例如一個連接上有3個數據流(Stream),每個數據流代表我們請求的一個功能。假如在Http1.x協議中,我們請求一個頁面首先會請求到一個HTML頁面,然后就會異步的發送兩個請求來請求CSS資源和JS資源,所以需要三個連接。而在Http2.0中它會復用一個連接,創建3個Stream流,一個Stream流負責獲取HTML頁面,另一個Stream流負責獲取CSS資源而最后一個Stream流負責獲取JS資源,重要的是這三個Stream流會放在一個連接上(連接復用)。
  2. 而一個Stream中會有一個Message,這個Message里面會有兩個frame,一個frame放置請求頭,一個frame放置請求體
  3. 同時響應也可以有多個Stream

在這里插入圖片描述

其它概念:

  1. 數據流的優先級:各個Stream有優先級,通過給不同的Stream設置權重,來限制不同流的傳輸順序
  2. 可以做流控,如果客戶端的流的發送速度大于服務端處理數據,導致服務端處理不過來,此時服務端可以通知客戶端暫時停止發送流

3. 序列化-Protobuf

Protobuf是一種與編程語言無關(它自己定義的IDL語言),與平臺無關(操作系統)。它定義了中間語言,可以方便在客戶端和服務端中進行RPC傳輸。Protobuf有兩種協議版本,一個是Protobuf2一個是Protobuf3,目前主流使用的是Protobuf3。在使用Protobuf的過程中我們需要按照Protobuf的編譯器,這個編譯器的作用就是可以把Protobuf的IDL的語言轉換為具體某一種開發語言。

  • Protobuf編譯器的安裝

安裝網址:https://github.com/protocolbuffers/protobuf/releases

在這里插入圖片描述
在這里插入圖片描述

idea同時安裝Protobuf插件,方便開發。

注意2021.2版本后的idea是內置了Protobuf插件的,老版本可以選裝ProtoBuf版本,但是二者是不能共存的

在這里插入圖片描述

  • Protobuf基本使用(Protobuf3)

文件格式:Protobuf所有的內容都需要寫在一個.proto文件中

版本設定:.proto文件的第一行,syntax="proto3";

注釋:

  • 單行注釋://
  • 多行注釋:/**/

與java語言相關的語法:

//后續protobuf生產的java代碼是一個源文件還是多個源文件
option java_multiple_files = true/false;
//指定protobuf生產的java源文件放置的位置
option java_package="com.jackiechai";
//指定protobuf生成的java類封裝的大類的名稱(因為protobuf生成的所有java類都會成為一個大類的內部類)
option java_outer_classname="UserService";

邏輯包:package xxx;用于protobuf對于文件內容的管理

導入:在實際的開發中我們可能有多個.proto,每個.proto管理自己的內容,現在可能出現一個問題,其中一個.proto依賴另一個.proto的內容,這里就需要使用導入語法:import "xxx/Userservice.proto"

在這里插入圖片描述

  • ProtoBuf基本類型

官網可以查看ProtoBuf所支持的所有基本類型:https://protobuf.dev/programming-guides/proto3/

  • 枚舉類型
enum SEASON{SPRING = 0;//0和1代表字段的編號SUMMER = 1;
}

注意枚舉的編號是從0開始的

  • 消息Message
message LoginRequest{string username = 1;//1對應這個內容在消息中的編號string password = 2;int32 age = 3;
}

客戶端就是通過message來進行信息交流。

編號:編號是1開始到 2 2 9 ? 1 2^29-1 229?1結束,注意其中19000-19999不能用,這個區間內的編號是protobuf自己保留的

關鍵字:消息中可以加入兩個關鍵字,singular,這個是修飾消息字段的,可寫也可以不寫,表示這個字段的值只能是0個或者1個(默認關鍵字)。

message LoginRequest{singular string username = 1;//1對應這個內容在消息中的編號singular string password = 2;singular int32 age = 3;
}

repeated修飾的字段,表示這個字段的返回值是多個,等價于java中的list:

message LoginRequest{string content=1;repeated string stutas=2;
}

上面content是singular類型,stutas是repeated類型。

在protobuf中消息是可以嵌套的:

message searchResponse{message Result{string url=1;string title=2;}string xxx=1;int32 yyy=2;Result ppp=3;
}message AAA{string xxx=1;searchResponse.Result yyy=2;
}

oneof關鍵字,如下我們在使用test_oneof對象時,我們只能代表它的值之一,例如test_oneof可以代表name或者代表age。

message simpleMessage{oneof test_oneof{string name=1;int32 age=2;}test_oneof xxxx=1;
}
  • 服務的定義
service HelloService{//LoginRequest和HelloResponse就是前面的Messagerpc hello(LoginRequest) returns(HelloResponse){//hello就相當于一個服務接口,具體的服務邏輯是服務提供方來實現的} 
}

一個服務,例如上面HelloService,里面是可以定義多個服務方法的,而且根據我們的需要我們是可以定義多個服務的。對于gRPC來說它的服務方式是分成4種情況的,這里先不詳細分析。

4. gRPC開發實戰環境搭建

  • 項目結構
  1. xxxx-api模塊:定義protobuf的idl語言,并且通過命令來創建具體的代碼,后序服務端和客戶端引入使用(客戶端和服務端公共模塊),包括定義message和service
  2. xxxx-server模塊:服務提供方模塊。實現API模塊中定義的接口,需要和具體的業務相結合。然后發布整個gRPC服務(創建服務端程序)
  3. xxxx-client模塊:創建服務端的代理,基于這個代理來進行RPC調用
  • API模塊開發
  1. 創建API模塊

在這里插入圖片描述
2. 編寫proto文件

syntax = "proto3";option java_multiple_files = false;
option java_package = "com.jackie";
option java_outer_classname = "HelloProto";/**發布RPC服務*/
//定義請求消息
message HelloRequest{string name = 1;
}//定義響應消息
message HelloResponse{string result = 1;
}/**開發service*/
service HelloService{rpc hello(HelloRequest) returns (HelloResponse){}
}

在這里插入圖片描述
3. 將ProtoBuf的IDL內容轉換為具體使用的語言的代碼(protoc這個編譯器完成的,它可以將這個IDL語言轉換為我們使用的目標語言),所以我們要做的就是將這個proto文件進行編譯:

protoc --java_out=/***/***  /***/***/***.proto
#--java_out=:表示生成java代碼
# /***/***:生成的代碼文件放的位置
#/***/***/***.proto:定義好的proto文件

上面是編譯命令,但這種方式需要頻繁的使用終端所以不適合開發。下面看看我們怎么在開發中怎么使用:

其實我們可以使用Maven插件來進行protobuf IDL文件的編譯操作。首先我們需要引入相關的依賴:

<dependencies>
<!--       grpc-netty-shaded說明grpc底層通信使用的是netty框架--><dependency><groupId>io.grpc</groupId><artifactId>grpc-netty-shaded</artifactId><version>1.63.0</version><scope>runtime</scope></dependency>
<!--        protobuf使用所需的相關依賴--><dependency><groupId>io.grpc</groupId><artifactId>grpc-protobuf</artifactId><version>1.63.0</version></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-stub</artifactId><version>1.63.0</version></dependency><dependency> <!-- necessary for Java 9+ --><groupId>org.apache.tomcat</groupId><artifactId>annotations-api</artifactId><version>6.0.53</version><scope>provided</scope></dependency></dependencies>

引入相關的maven插件:

<build><extensions><extension><groupId>kr.motd.maven</groupId><artifactId>os-maven-plugin</artifactId><version>1.7.1</version></extension></extensions><plugins><plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.6.1</version><configuration><protocArtifact>com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}</protocArtifact><pluginId>grpc-java</pluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:1.63.0:exe:${os.detected.classifier}</pluginArtifact><outputBaseDirectory>${basedir}/src/main/java</outputBaseDirectory><clearOutputDirectory>false</clearOutputDirectory></configuration><executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions></plugin></plugins></build>

使用插件編譯proto文件:
在這里插入圖片描述
這樣就生成java文件了:
在這里插入圖片描述
我們仔細分析一下上面java文件的組成:

在這里插入圖片描述
首先第一個類名是HelloProto這個類名就是我們在 proto文件中定義的外部類的名稱:

option java_outer_classname = "HelloProto";

HelloRequest和HelloResponse都是在proto文件中定義過的:
在這里插入圖片描述
它們都是HelloProto的內部類。HelloProto主要就是用來處理協議消息的。HelloServiceGrpc主要就是用來處理Grpc了。我們來看以下它的內部類,首先是HelloServiceImplBase它就代表我們的業務接口,后面服務方暴露業務接口給外界時就需要實現這個接口。以stub結尾的這些類對應的就是客戶端的代理對象,這些stub都是客戶端的代理,區別就是網絡通信方式不同(阻塞、異步等)。

  • Server模塊開發

主要包括兩個過程,首先需要實現業務接口,添加具體的功能,然后創建服務端(Netty)。

  1. 實現業務接口并添加具體的功能

創建Service模塊
在這里插入圖片描述
創建ServiceImpl類

public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {/*** 1. 接收客戶端提交的參數* 2. 業務處理* 3. 將處理結果返回給調用者** @param request          客戶端請求的message* @param responseObserver 返回給客戶端的結果*/public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {//接收客戶端的請求參數String name = request.getName();//業務處理System.out.println("name parameter" + name);//封裝響應// 1.創建響應對象的構造者HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();// 2.填充數據builder.setResult("hello method invoke");// 3.封裝響應HelloProto.HelloResponse helloResponse = builder.build();responseObserver.onNext(helloResponse);responseObserver.onCompleted();}
}
  1. 創建服務端
public class GrpcServer1 {public static void main(String[] args) throws IOException, InterruptedException {//綁定端口ServerBuilder serverBuilder = ServerBuilder.forPort(9000);//有一個服務就調用一個serverBuilder.addService(new HelloServiceImpl());//創建服務對象Server server = serverBuilder.build();server.start();server.awaitTermination();}
}

服務端本質是用的netty

  • Client模塊開發

客戶端就是通過代理對象完成遠端對象的調用。

  1. 創建客戶端模型,并導入依賴

2
2. 創建客戶端

public static void main(String[] args) {//創建通信的管道(netty)ManagedChannel managedChannel= ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();try{//獲得代理對象(阻塞模式)HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub= HelloServiceGrpc.newBlockingStub(managedChannel);//完成rpc調用//1. 準備參數HelloProto.HelloRequest.Builder builder=HelloProto.HelloRequest.newBuilder();builder.setName("jackie");HelloProto.HelloRequest build = builder.build();HelloProto.HelloResponse hello = helloServiceBlockingStub.hello(build);System.out.println(hello);}catch (Exception e){throw new RuntimeException(e);}finally {managedChannel.shutdown();}
}
  • 測試結果

在這里插入圖片描述

在這里插入圖片描述
我們回顧一下前面的服務端代碼:
在這里插入圖片描述
request對象里面就封裝了客戶端傳過來的消息,responseObserver就是需要返回給客戶端的消息會封裝到這個對象中。調用responseObserveronNext方法就可以把處理后的響應消息通過網絡回傳給客戶端,onCompleted這個方法的作用就是用于通知客戶端響應已經結束了。

5. gRPC的四種通信方式(重點)

  • gRPC的四種通信方式如下:
  1. 簡單rpc(我們上面的應用程序就是簡單rpc),或者是一元rpc(Unary RPC)
  2. 服務端流式rpc(Server Stream RPC)
  3. 客戶端流式rpc(Client Stream RPC)
  4. 雙向流RPC(Bi-directional Stream RPC)
  • 簡單rpc

一元rpc的特點如下:

  1. 當客戶端發起調用后,會提交數據,并且等待服務端的響應
  2. 開發過程中主要采用的就是一元rpc這種通信方式
    在這里插入圖片描述
    一元rpc的語法就是:
service HelloService{rpc hello(HelloRequest) returns (HelloResponse){}
}
  • 服務端流式rpc:客戶端一個請求對象,服務端可以回傳多個

服務端流式rpc的特點如下:

在這里插入圖片描述
服務端流式rpc的使用場景有:

例如股票系統中,客戶端發送一個股票的編號到服務端(一個數據),服務端就會返回一系列數據,這些數據就是這個股票在某一時刻的行情。

服務端流式rpc的語法變動如下:

service HelloService{rpc hello(HelloRequest) returns (stream HelloResponse){}
}

在返回值前面加stream關鍵字。然后客戶端接收到的消息類型是Iterator類型,是一個迭代器,其它內容沒有什么變動。

上面的代碼中客戶端是阻塞的,所以在接收消息時一直會處于阻塞狀態,現在我們重寫客戶端代碼,讓其采用異步的方式來實現流式rpc的開發。

   public static void main(String[] args) {//創建通信的管道(netty)ManagedChannel managedChannel= ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();try{//獲得代理對象(異步模式)HelloServiceGrpc.HelloServiceStub helloServiceBlockingStub= HelloServiceGrpc.newStub(managedChannel);//完成rpc調用//1. 準備參數HelloProto.HelloRequest.Builder builder=HelloProto.HelloRequest.newBuilder();builder.setName("jackie");HelloProto.HelloRequest build = builder.build();HelloProto.HelloResponse hello = helloServiceBlockingStub.hello(build, new StreamObserver<HelloProto.HelloResponse>() {//監聽服務端的onNext方法(這個方法處理數據)public void onNext(HelloProto.HelloResponse helloResponse) {}//這個方法處理異常public void onError(Throwable throwable) {}//這個處理服務端響應結束public void onCompleted() {}});//一定要加這句代碼,因為上面代碼是沒有阻塞的,所以客戶端的回調方法還沒來得及調,客戶端就結束了managedChannel.awaitTermination(12,TimeUnit.SECONDS);}catch (Exception e){throw new RuntimeException(e);}finally {managedChannel.shutdown();}}
  • 客戶端流式rpc

所謂的客戶端流式rpc,指的是客戶端發送多個請求對象,服務端只返回一個結果。
在這里插入圖片描述
客戶端流式rpc的語法變動如下:

service HelloService{rpc hello(stream HelloRequest) returns (HelloResponse){}
}
  • 雙向流式rpc

意思就是客戶端可以發送多個請求消息,服務端同時也可以發送多條響應消息(例如聊天室)。
在這里插入圖片描述
雙向流式rpc的語法變動如下:

service HelloService{rpc hello(stream HelloRequest) returns (stream HelloResponse){}
}

6. gRPC的代理方式

gRPC的代理方式有下面幾種:

  • BlockingStub:阻塞通信方式
  • Stub:異步通過監聽處理
  • FutureStub:異步方式(類似于netty中的future),同步異步都支持

前兩種都分析了,現在來分析FutureStub,這種代理方式只能用于一元RPC

public static void main(String[] args){ManagedChannel managedChannel=ManagedChannelBuilder.forAddress("localhost",9000).userPlaintext().build();
try{TestServiceGrpc.TestServiceFuture testServiceFutureStub=TestServiceGrpc.newFutureStub(managedChannel);ListenableFuture<TestProto.TestResponse> responseListenableFuture=testServiceFutureStub.testSuns(HelloProto.TestRequest.newBuilder().setName("x").build());//獲取響應內容(同步)TestProto.TestResponse testResponse=responseListenableFuture.get();System.out.println(restResponse.getResult());//異步方式/**Futuress.addCallback(responseListenableFuture,new FutureCallback<TestProto.TestResponse>(){@Overridepublic void onSuccess(TestProto.TestResponse result){}@Overridepublic void onFaulure(Throwable t){}
},Executors.newCachedThreadPool());**/managedChannel.awaitTermination(12,TimeUnit.SECONDS);
}catch(Exception e){e.printStackTrace();
}finally{managedChannel.shutdown();
}
}

7. SprintBoot整合gRPC

  • SpringBoot和gRPC整合的思想

gRPC與SpringBoot整合主要是兩種思想:首先是服務端如何整合,另外就是客戶端如何整合。

  • SpringBoot與gRPC整合過程中對于服務端做了什么封裝

原生的gprc在創建服務端的時候就做了兩件事,首先是實現服務接口,然后創建服務端發布gRPC功能。

public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {}

上面的HelloServiceImplBase就是通過protoBuf的IDL文件生成的一個服務接口,而SpringBoot無法提供實現服務接口這個工作進行封裝的(這部分代碼是不固定的)。而服務端的啟動代碼如下:

   public static void main(String[] args) throws IOException, InterruptedException {//綁定端口ServerBuilder serverBuilder = ServerBuilder.forPort(9000);//有一個服務就調用一個serverBuilder.addService(new HelloServiceImpl());//創建服務對象Server server = serverBuilder.build();server.start();server.awaitTermination();}

上面代碼就是構建服務端的過程,這個過程SpringBoot是可以封裝的(相對固定,變動的地方是服務端端口和服務發布的過程)。

  • 下面開始創建服務端:
  1. 搭建開發環境
  • 搭建SpringBoot的開發環境
  • 引入與Grpc相關的內容

創建服務端模塊:
在這里插入圖片描述
創建api模塊:

syntax = "proto3";option java_multiple_files = false;
option java_package = "com.jackie";
option java_outer_classname = "HelloProto";/**發布RPC服務*/
//定義請求消息
message HelloRequest{string name = 1;
}//定義響應消息
message HelloResponse{string result = 1;
}/**開發service*/
service HelloService{rpc hello(HelloRequest) returns (HelloResponse){}rpc c2ss(HelloRequest) returns(stream HelloResponse){}rpc cs2s(stream HelloRequest) returns(HelloResponse){}rpc cs2ss(stream HelloRequest) returns(stream HelloResponse){}
}

在這里插入圖片描述
在服務端模塊中引入api模塊的依賴:

 <dependency><groupId>org.example</groupId><artifactId>rpc-grpc-myapi</artifactId><version>1.0-SNAPSHOT</version></dependency>

在server模塊中引入gRPC依賴:

 <dependency><groupId>net.devh</groupId><artifactId>grpc-server-spring-boot-starter</artifactId><version>2.14.0.RELEASE</version></dependency>

這個依賴里面就幫我們做了gRPC服務端的封裝(不是官方的支持)。

  • 創建gRPC服務端
@GrpcService
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {@Overridepublic void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {//拿到請求參數String name=request.getName();System.out.println("name is "+name);responseObserver.onNext(HelloProto.HelloResponse.newBuilder().setResult("this is result").build());responseObserver.onCompleted();}@Overridepublic void c2ss(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {super.c2ss(request, responseObserver);}@Overridepublic StreamObserver<HelloProto.HelloRequest> cs2s(StreamObserver<HelloProto.HelloResponse> responseObserver) {return super.cs2s(responseObserver);}@Overridepublic StreamObserver<HelloProto.HelloRequest> cs2ss(StreamObserver<HelloProto.HelloResponse> responseObserver) {return super.cs2ss(responseObserver);}
}

在這里插入圖片描述

  • 配置gRPC服務的端口號

在這里插入圖片描述

  • 啟動服務類

我們看控制臺的數據,我們可以發現一個問題,就是啟動過程中Tomcat也啟動了,實際上我們服務端是用的grpc服務端所以我們不需要Tomacat,所以在pom中排除Tomcat。

在這里插入圖片描述

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency>

在這里插入圖片描述
到此服務端開發就完畢了。

  • 下面開始創建客戶端

傳統的gPPC的客戶端的開發流程為:首先通過管道設置服務端的ip和端口,然后獲取stub(遠端功能的代理)。

  1. 環境搭建

流程和server創建的過程一樣。
在這里插入圖片描述
然后對一些重要參數進行配置:

spring:application:name: grpc-clientgrpc:client:grpc-server:address: 'static://127.0.0.1:9000'negotiation-type: plaintext
  1. grpc客戶端開發

創建controller

@RestController
public class TestController {//注入代理對象@GrpcClient("grpc-server")private HelloServiceGrpc.HelloServiceBlockingStub stub;@RequestMapping("/test")public String test1(String name) {System.out.println("name=" + name);HelloProto.HelloResponse helloResponse = stub.hello(HelloProto.HelloRequest.newBuilder().setName(name).build());return helloResponse.getResult();}
}

這里就完了了客戶端的創建,上面其實就是SpringBoot+gRPC實現了一元RPC。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/11240.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/11240.shtml
英文地址,請注明出處:http://en.pswp.cn/web/11240.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

reactJs動態執行js代碼

參考了這篇文章 js——new Function 一個可以隨時動態執行字符串js代碼的神器 因為一些原因&#xff0c;想要js代碼塊配置在數據庫中返回&#xff0c;例如時間&#xff0c;我需要用到第三方庫 moment。然后動態的得到startDate 和 endDate 配置在數據庫中的startDate值是$mom…

Java日志總結

開發中&#xff0c;日志記錄是不可或缺的一部分&#xff0c;應用日志的記錄主要用于&#xff1a;記錄操作軌跡數據、監控系統運行情況、系統故障定位問題&#xff0c;日志的重要性不言而喻&#xff0c;想要快速定位問題&#xff0c;日志分析是個重要的手段&#xff0c;Java也提…

JAVA 集合(單列集合)

集合框架 1.集合的特點 a.只能存儲引用數據類型的數據 b.長度可變 c.集合中有大量的方法,方便我們操作 2.分類: a.單列集合:一個元素就一個組成部分: list.add(“張三”) b.雙列集合:一個元素有兩部分構成: key 和 value map.put(“濤哥”,“金蓮”) -> key,value叫做鍵值…

Docker各版本的新特性

Docker 作為流行的容器化平臺&#xff0c;會定期發布新版本以引入新特性、改進和修復。根據提供的搜索結果&#xff0c;以下是一些 Docker 版本及其新特性的概覽&#xff1a; Docker Desktop v4.12 Containerd 的集成&#xff1a;更深入集成 containerd 以管理容器生命周期&a…

鎖和MVCC如何實現mysql的隔離級別

概述 MVCC解決讀的隔離性&#xff0c;加鎖解決寫的隔離性。 讀未提交 讀未提交&#xff0c;更新數據大概率使用的是獨享鎖吧。 讀已提交 在 Read Committed&#xff08;讀已提交&#xff09;隔離級別下&#xff0c;每次執行讀操作時都會生成一個新的 read view。這是因為在讀…

英譯漢早操練-(二十)

hello大家好&#xff0c;這篇跟隨十九&#xff0c;繼續真題學習。如果想看全部請返回到第十九篇。 英譯漢早操練-&#xff08;十九&#xff09;-CSDN博客 The political upheaval in Libya and elsewhere in North Africa has opened the way for thousands of new migrants to…

【C++學習第15天】STL

一、種類 vector&#xff1a;變長數組&#xff0c;倍增的思想。給數組申請空間所耗費的時間取決于申請次數&#xff0c;而不是申請空間的大小&#xff0c;即a[1]和a[10000]兩個數組的申請時間是基本一致的。pair<int, string>&#xff1a;存儲一個二元組&#xff0c;前后…

AI 圖像生成-環境配置

一、python環境安裝 Windows安裝Python&#xff08;圖解&#xff09; 二、CUDA安裝 CUDA安裝教程&#xff08;超詳細&#xff09;-CSDN博客 三、Git安裝 git安裝教程&#xff08;詳細版本&#xff09;-CSDN博客 四、啟動器安裝 這里安裝的是秋葉aaaki的安裝包 【AI繪畫…

【GlobalMapper精品教程】081:WGS84/CGCS2000轉Lambert投影

參考閱讀:ArcGIS實驗教程——實驗十:矢量數據投影變換 文章目錄 一、加載實驗數據二、設置輸出坐標系三、數據導出一、加載實驗數據 打開配套案例數據包中的data081.rar中的矢量數據,如下所示: 查看源坐標系:雙擊圖層的,圖層投影選項卡,數據的已有坐標系為WGS84地理坐標…

MySQL創建存儲過程函數

DDL CREATE TABLE student (id int(11) NOT NULL AUTO_INCREMENT COMMENT 學號,createDate datetime DEFAULT NULL,userName varchar(20) DEFAULT NULL,pwd varchar(36) DEFAULT NULL,phone varchar(11) DEFAULT NULL,age tinyint(3) unsigned DEFAULT NULL,sex char(2) DEFAU…

[初學rust] 05_ rust struct

rust struct 其實這也算復合類型&#xff0c;但是其他語言都有&#xff0c;所以抽取出來單獨看的時候會很簡單&#xff0c;更容易學 1. 定義結構體 結構體的定義和其他語言沒啥區別。 struct User {name: String,age: i32, }2. 創建實例 創建實例的過程就跟js類似 let us…

【3dmax筆記】021:對齊工具(快速對齊、法線對齊、對齊攝影機)

文章目錄 一、對齊二、快速對齊三、法線對齊四、對齊攝影機五、注意事項3dmax提供了對齊、快速對齊、法線對齊和對齊攝像機等對齊工具: 對齊工具選項: 下面進行一一講解。 一、對齊 快捷鍵為Alt+A,將當前選擇對象與目標對象進行對齊。 最大對最大:

【小筆記】neo4j用load csv指令導入數據

【小筆記】neo4j用load csv指令導入數據 背景 很久沒有用load CSV的方式導入過數據了因為它每次導入有數量限制&#xff08;印象中是1K還是1W&#xff09;&#xff0c;在企業中構建的圖譜往往都是大規模的&#xff0c;此時通常采用的是Neo4j-admin import方式。最近遇到了一些…

振弦式表面應變計怎么安裝

振弦式表面應變計是一種用于測量結構表面應變的高精度傳感器&#xff0c;廣泛應用于工程和科研領域。正確安裝振弦式表面應變計對于確保測量結果的準確性至關重要。以下是安裝振弦式表面應變計的步驟和注意事項&#xff1a; 1. 準備工作 在開始安裝前&#xff0c;需要準備以下工…

whisper之初步使用記錄

文章目錄 前言 一、whisper是什么&#xff1f; 二、使用步驟 1.安裝 2.python調用 3.識別效果評估 4.一點封裝 5.參考鏈接 總結 前言 隨著AI大模型的不斷發展&#xff0c;語音識別等周邊內容也再次引發關注&#xff0c;通過語音轉文字再與大模型交互&#xff0c;從而…

【Gitlab遠程訪問本地倉庫】Gitlab如何安裝配置并結合內網穿透實現遠程訪問本地倉庫進行管理

文章目錄 前言1. 下載Gitlab2. 安裝Gitlab3. 啟動Gitlab4. 安裝cpolar5. 創建隧道配置訪問地址6. 固定GitLab訪問地址6.1 保留二級子域名6.2 配置二級子域名 7. 測試訪問二級子域名 前言 GitLab 是一個用于倉庫管理系統的開源項目&#xff0c;使用Git作為代碼管理工具&#xf…

為什么質量工程師必學六西格瑪?突破職業發展的瓶頸?

在質量管理領域工作多年&#xff0c;你是否曾感受到事業發展的停滯不前&#xff1f;3年、5年的職業生涯&#xff0c;薪水依舊停留在每月5000-7000&#xff0c;而同行業的其他人卻能月入2-3萬&#xff0c;這種差距讓人不禁陷入深思。 問題究竟出在哪里&#xff1f;為什么我們的…

編譯OpenResty遇到找不到OpenSSL的解決辦法

以OpenResty-1.19.9.1為例 編輯openresty-1.19.9.1/build/nginx-1.19.9/auto/lib/openssl/conf CORE_INCS"$CORE_INCS $OPENSSL/.openssl/include" CORE_DEPS"$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h" CORE_LIBS"$CORE_LIBS $OPENSSL/.…

揭秘圖形編程 動靜接口如何助力 AGV 集成

在公司軟件開發團隊的辦公室里&#xff0c;陽光透過窗戶灑在排列整齊的辦公桌上。臥龍坐在辦公桌前&#xff0c;面前攤開一份內測報告&#xff0c;他的手指時不時地敲擊著桌面&#xff0c;流露出內心的煩躁。他抬起頭&#xff0c;眼神中透露出一絲困惑&#xff0c;看向正在文件…

調用函數實現數組的輸入排序插入與輸出

輸入一串數字&#xff0c;輸出排序插入后輸出最后的數字序列&#xff1b; 把主要步驟交給主函數&#xff0c;通過調用函數來實現整體的功能&#xff1a; 寫出主函數&#xff08;這里使用冒泡排序&#xff09;&#xff1a; int main(){int n;int a[10];cin>>n;inputa(a…