【摘要】 Rpc基本概念RPC(Remote Procedure Call)遠(yuǎn)程過程調(diào)用,是一種通過網(wǎng)絡(luò)從遠(yuǎn)程計(jì)算機(jī)程序上請求服務(wù),而不需要了解底層網(wǎng)絡(luò)技術(shù)的協(xié)議,簡單的理解是一個(gè)節(jié)點(diǎn)請求另一個(gè)節(jié)點(diǎn)提供的服務(wù)。RPC只是一套協(xié)議,基于這套協(xié)議規(guī)范來實(shí)現(xiàn)的框架都可以稱為 RPC 框架,比較典型的有 有阿里巴巴的 Dubbo、Google 的 gRPC、Facebook 的 Thrift 和 Twitt...
Rpc基本概念
RPC(Remote Procedure Call)遠(yuǎn)程過程調(diào)用,是一種通過網(wǎng)絡(luò)從遠(yuǎn)程計(jì)算機(jī)程序上請求服務(wù),而不需要了解底層網(wǎng)絡(luò)技術(shù)的協(xié)議,簡單的理解是一個(gè)節(jié)點(diǎn)請求另一個(gè)節(jié)點(diǎn)提供的服務(wù)。RPC只是一套協(xié)議,基于這套協(xié)議規(guī)范來實(shí)現(xiàn)的框架都可以稱為 RPC 框架,比較典型的有 有阿里巴巴的 Dubbo、Google 的 gRPC、Facebook 的 Thrift 和 Twitter 的 Finagle 等。
RPC 機(jī)制和實(shí)現(xiàn)過程
RPC 是遠(yuǎn)程過程調(diào)用的方式之一,涉及調(diào)用方和被調(diào)用方兩個(gè)進(jìn)程的交互。因?yàn)?RPC 提供類似于本地方法調(diào)用的形式,所以對于調(diào)用方來說,調(diào)用 RPC 方法和調(diào)用本地方法并沒有明顯區(qū)別。
RPC的機(jī)制的誕生和基礎(chǔ)概念
1984 年,Birrell 和 Nelson 在 ACM Transactions on Computer Systems 期刊上發(fā)表了名為“Implementing remote procedure calls”的論文,該文對 RPC 的機(jī)制做了經(jīng)典的詮釋:
RPC 遠(yuǎn)程過程調(diào)用是指計(jì)算機(jī) A 上的進(jìn)程,調(diào)用另外一臺計(jì)算機(jī) B 上的進(jìn)程的方法。其中A 上面的調(diào)用進(jìn)程被掛起,而 B 上面的被調(diào)用進(jìn)程開始執(zhí)行對應(yīng)方法,并將結(jié)果返回給 A,計(jì)算機(jī) A 接收到返回值后,調(diào)用進(jìn)程繼續(xù)執(zhí)行。
發(fā)起 RPC 的進(jìn)程通過參數(shù)等方式將信息傳送給被調(diào)用方,然后被調(diào)用方處理結(jié)束后,再通過返回值將信息傳遞給調(diào)用方。這一過程對于開發(fā)人員來說是透明的,開發(fā)人員一般也無須知道雙方底層是如何進(jìn)行消息通信和信息傳遞的,這樣可以讓業(yè)務(wù)開發(fā)人員更專注于業(yè)務(wù)開發(fā),而非底層細(xì)節(jié)。
RPC 讓程序之間的遠(yuǎn)程過程調(diào)用具有與本地調(diào)用類似的形式。比如說某個(gè)程序需要讀取某個(gè)文件的數(shù)據(jù),開發(fā)人員會(huì)在代碼中執(zhí)行 read 系統(tǒng)調(diào)用來獲取數(shù)據(jù)。
當(dāng) read 實(shí)際是本地調(diào)用時(shí),read 函數(shù)由鏈接器從依賴庫中提取出來,接著鏈接器會(huì)將它鏈接到該程序中。雖然 read 中執(zhí)行了特殊的系統(tǒng)調(diào)用,但它本身依然是通過將參數(shù)壓入堆棧的常規(guī)方式調(diào)用的,調(diào)用方并不知道 read 函數(shù)的具體實(shí)現(xiàn)和行為。
當(dāng) read 實(shí)際是一個(gè)遠(yuǎn)程過程時(shí)(比如調(diào)用遠(yuǎn)程文件服務(wù)器提供的方法),調(diào)用方程序中需要引入 read 的接口定義,稱為客戶端存根(client-stub)。遠(yuǎn)程過程 read 的客戶端存根與本地方法的 read 函數(shù)類似,都執(zhí)行了本地函數(shù)調(diào)用。不同的是它底層實(shí)現(xiàn)上不是進(jìn)行操作系統(tǒng)調(diào)用讀取本地文件來提供數(shù)據(jù),而是將參數(shù)打包成網(wǎng)絡(luò)消息,并將此網(wǎng)絡(luò)消息發(fā)送到遠(yuǎn)程服務(wù)器,交由遠(yuǎn)程服務(wù)執(zhí)行對應(yīng)的方法,在發(fā)送完調(diào)用請求后,客戶端存根隨即阻塞,直到收到服務(wù)器發(fā)回的響應(yīng)消息為止。
下圖展示了遠(yuǎn)程方法調(diào)用過程中的客戶端和服務(wù)端各個(gè)階段的操作。
總結(jié)下RPC執(zhí)行步驟:
調(diào)用客戶端句柄,執(zhí)行傳遞參數(shù)。
調(diào)用本地系統(tǒng)內(nèi)核發(fā)送網(wǎng)絡(luò)消息。
消息傳遞到遠(yuǎn)程主機(jī),就是被調(diào)用的服務(wù)端。
服務(wù)端句柄得到消息并解析消息。
服務(wù)端執(zhí)行被調(diào)用方法,并將執(zhí)行完畢的結(jié)果返回給服務(wù)器句柄。
服務(wù)器句柄返回結(jié)果,并調(diào)用遠(yuǎn)程系統(tǒng)內(nèi)核。
消息經(jīng)過網(wǎng)絡(luò)傳遞給客戶端。
客戶端接受數(shù)據(jù)。
安裝gRPC和Protobuf
gRPC由google開發(fā),是一款語言中立、平臺中立、開源的遠(yuǎn)程過程調(diào)用系統(tǒng)
gRPC客戶端和服務(wù)端可以在多種環(huán)境中運(yùn)行和交互,例如用java寫一個(gè)服務(wù)端,可以用go語言寫客戶端調(diào)用
在gRPC中,我們可以一次性的在一個(gè) proto文件中定義服務(wù)并使用任意的支持gRPC的語言去實(shí)現(xiàn)客戶端和服務(wù)端,整個(gè)過程操作變得簡單,就像調(diào)用本地函數(shù)一樣。
通過 proto生成服務(wù)端代碼,也就是服務(wù)端的骨架,提供低層通信抽象
通過 proto生成客戶端代碼,也就是客戶端的存根,隱藏了不同語言的差異,提供抽象的通信方式,就像調(diào)用本地函數(shù)一樣。
安裝
go getgithub.com/golang/protobuf/proto
go getgoogle.golang.org/grpc(無法使用,用如下命令代替)
git clonehttps://github.com/grpc/grpc-go.git$GOPATH/src/google.golang.org/grpc
git clonehttps://github.com/golang/net.git$GOPATH/src/golang.org/x/net
git clonehttps://github.com/golang/text.git$GOPATH/src/golang.org/x/text
go get -ugithub.com/golang/protobuf/{proto,protoc-gen-go}
git clonehttps://github.com/google/go-genproto.git$GOPATH/src/google.golang.org/genproto
cd $GOPATH/src/
go installgoogle.golang.org/grpc
go getgithub.com/golang/protobuf/protoc-gen-go
上面安裝好后,會(huì)在GOPATH/bin下生成protoc-gen-go.exe
但還需要一個(gè)protoc.exe,windows平臺編譯受限,很難自己手動(dòng)編譯,直接去網(wǎng)站下載一個(gè),地址:https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.0,同樣放在GOPATH/bin下
proto 服務(wù)定義
gRPC 使用protocol buffer 來定義服務(wù)接口,protocol buffer和 XML、JSON一樣是一種結(jié)構(gòu)化數(shù)據(jù)序列化的可擴(kuò)展存儲(chǔ)結(jié)構(gòu),protocol buffer是一種語言中立,結(jié)構(gòu)簡單高效,比XML更小更簡單,可以通過特殊的插件自動(dòng)生成代碼來讀寫操作這個(gè)數(shù)據(jù)結(jié)構(gòu)。
import "myproject/other_protos.proto";// 導(dǎo)入其他 proto文件 syntax = "proto3"; // 指定proto版本 package hello; // 指定默認(rèn)包名 // 指定golang包名 option go_package = "hello"; message SearchRequest { required string query = 1;// 必須賦值字段 optional int32 page_number = 2 [default = 10];// 可選字段 repeated int32 result_per_page = 3;// 可重復(fù)字段 } message SearchResponse { message Result // 嵌套定義 { required string url = 1; optional string title = 2; repeated string snippets = 3; } repeated Result result = 1; } message SomeOtherMessage { optional SearchResponse.Result result = 1;// 使用其他消息的定義 } service List{// 定義gRPC服務(wù)接口 rpc getList(SearchRequest) returns (SearchResponse); }
// 插件自動(dòng)生成gRPC骨架和存根 protoc --go_out=plugins=grpc: ./ *.proto
后面需要實(shí)現(xiàn)服務(wù)端具體的邏輯就行,然后注冊到gRPC服務(wù)器
客戶端在調(diào)用遠(yuǎn)程方法時(shí)會(huì)使用阻塞式存根,所以gRPC主要使用同步的方式通信,在建立連接后,可以使用流的方式操作。
客戶端編排為protocol buffer的格式,服務(wù)端再解排執(zhí)行,以HTTP2 傳輸
gRPC 優(yōu)勢
更高效的進(jìn)程通信:使用基于protocol buffer在Http2 中以二進(jìn)制協(xié)議通信,而不是JSON、XML文本格式
簡單定義的服務(wù)接口、易擴(kuò)展
強(qiáng)類型、跨語言
一元RPC、服務(wù)端流、客戶端流、雙工流
gRPC入門
簡單使用
protocol buffer
syntax = "proto3"; package hello; // 第一個(gè)分割參數(shù),輸出路徑;第二個(gè)設(shè)置生成類的包路徑 option go_package = "./proto/hello"; // 設(shè)置服務(wù)名稱 service Greeter { // 設(shè)置方法 rpc SayHello (HelloRequest) returns (HelloReply) {} } // 請求信息用戶名. message HelloRequest { string name = 1; } // 響應(yīng)信息 message HelloReply { string message = 1; }
服務(wù)端
package main import ( "context" "flag" "fmt" "log" "net" "google.golang.org/grpc" pb "mygrpc/proto/hello" ) var ( port = flag.Int("port", 50051, "The server port") ) type server struct { pb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } func main() { flag.Parse() lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } // 開啟rpc s := grpc.NewServer() // 注冊服務(wù) pb.RegisterGreeterServer(s, &server{}) log.Printf("server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
客戶端
package main import ( "context" "flag" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "mygrpc/proto/hello" // 引入編譯生成的包 ) const ( defaultName = "world" ) var ( addr = flag.String("addr", "localhost:50051", "the address to connect to") name = flag.String("name", defaultName, "Name to greet") ) func main() { flag.Parse() // 與服務(wù)建立連接. conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() // 創(chuàng)建指定服務(wù)的客戶端 c := pb.NewGreeterClient(conn) // 連接服務(wù)器并打印出其響應(yīng)。 ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // 調(diào)用指定方法 r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name}) if err != nil { log.Fatalf("could not greet: %v", err) } log.Printf("Greeting: %s", r.GetMessage()) }
客戶端連接gRPC服務(wù)器以后,就可以像調(diào)用本地函數(shù)一樣操作遠(yuǎn)程服務(wù)器。
審核編輯:黃飛
-
計(jì)算機(jī)
+關(guān)注
關(guān)注
19文章
7522瀏覽量
88296 -
服務(wù)器
+關(guān)注
關(guān)注
12文章
9248瀏覽量
85737 -
JAVA
+關(guān)注
關(guān)注
19文章
2973瀏覽量
104898 -
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11541
原文標(biāo)題:RPC簡介和grpc的使用
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論