Go中RPC遠(yuǎn)程過(guò)程調(diào)用的實(shí)現(xiàn)
一、RPC簡(jiǎn)介及原理介紹
1.1、背景
在前面的課程《Go語(yǔ)言微服務(wù)理論與實(shí)踐課程》課程中,我們已經(jīng)學(xué)習(xí)了微服務(wù)的理論知識(shí),了解了微服務(wù)實(shí)踐中需要解決哪些問(wèn)題。
從本篇技術(shù)文檔開(kāi)始,我們進(jìn)入新的微服務(wù)內(nèi)容的學(xué)習(xí)。在本系列課程中,我們會(huì)著重講框架的內(nèi)容,主要包括兩個(gè):gRPC框架、go-micro框架。
首先來(lái)學(xué)習(xí)gRPC框架相關(guān)的內(nèi)容。
1.2、本地過(guò)程調(diào)用
讓我們先來(lái)看看正常情況下程序的執(zhí)行和調(diào)用情況。例如有如下go語(yǔ)言代碼:
func main() { var a, b int a = 1 b = 2 c := Add(a, b) fmt.Println("計(jì)算結(jié)果:", c) } func Add(a int, b int) int { return a + b }
在上述的Go語(yǔ)言代碼中,我們定義了一個(gè)Add方法用于實(shí)現(xiàn)兩個(gè)數(shù)相加的功能,在main方法中通過(guò)調(diào)用Add方法實(shí)現(xiàn)了計(jì)算兩個(gè)變量之和的操作。整個(gè)過(guò)程涉及到變量值入棧,出棧,賦值等操作,最后將出棧的計(jì)算結(jié)果返回并賦值給c變量。
總結(jié)說(shuō)來(lái),本地程序調(diào)用的過(guò)程大致可以分為幾個(gè)步驟和階段:
- 開(kāi)發(fā)者開(kāi)發(fā)好的程序,并進(jìn)行編譯,編譯成機(jī)器認(rèn)可的可執(zhí)行文件。
- 運(yùn)行可執(zhí)行文件,調(diào)用對(duì)應(yīng)的功能方法,期間會(huì)讀取可執(zhí)行文件中國(guó)的機(jī)器指令,進(jìn)行入棧,出棧賦值等操作。此時(shí),計(jì)算機(jī)由可執(zhí)行程序所在的進(jìn)程控制。
- 調(diào)用結(jié)束,所有的內(nèi)存數(shù)據(jù)出棧,程序執(zhí)行結(jié)束。計(jì)算機(jī)繼續(xù)由操作系統(tǒng)進(jìn)行控制。
1.3、問(wèn)題及解決方法
上文我們已經(jīng)說(shuō)過(guò),遠(yuǎn)程過(guò)程調(diào)用是在兩臺(tái)或者多臺(tái)不同的物理機(jī)器上實(shí)現(xiàn)的調(diào)用,其間要跨越網(wǎng)絡(luò)進(jìn)行調(diào)用。因此,我們?cè)傧胪ㄟ^(guò)前文本地方法調(diào)用的形式完成功能調(diào)用,就無(wú)法實(shí)現(xiàn)了,因?yàn)榫幾g器無(wú)法通過(guò)編譯的可執(zhí)行文件來(lái)調(diào)用遠(yuǎn)程機(jī)器上的程序方法。因此需要采用RPC的方式來(lái)實(shí)現(xiàn)遠(yuǎn)端服務(wù)器上的程序方法的調(diào)用。
RPC技術(shù)內(nèi)部原理是通過(guò)兩種技術(shù)的組合來(lái)實(shí)現(xiàn)的:本地方法調(diào)用 和 網(wǎng)絡(luò)通信技術(shù)。
1.4、RPC簡(jiǎn)介
在上述本地過(guò)程調(diào)用的例子中,我們是在一臺(tái)計(jì)算機(jī)上執(zhí)行了計(jì)算機(jī)上的程序,完成調(diào)用。隨著計(jì)算機(jī)技術(shù)的發(fā)展和需求場(chǎng)景的變化,有時(shí)就需要從一臺(tái)計(jì)算機(jī)上執(zhí)行另外一臺(tái)計(jì)算機(jī)上的程序的需求,因此后來(lái)又發(fā)展出來(lái)了RPC技術(shù)。特別是目前隨著互聯(lián)網(wǎng)技術(shù)的快速迭代和發(fā)展,用戶(hù)和需求幾乎都是以指數(shù)式的方式在高速增長(zhǎng),這個(gè)時(shí)候絕大多數(shù)情況下程序都是部署在多臺(tái)機(jī)器上,就需要在調(diào)用其他物理機(jī)器上的程序的情況。
RPC是Remote Procedure Call Protocol單詞首字母的縮寫(xiě),簡(jiǎn)稱(chēng)為:RPC,翻譯成中文叫遠(yuǎn)程過(guò)程調(diào)用協(xié)議。所謂遠(yuǎn)程過(guò)程調(diào)用,通俗的理解就是可以在本地程序中調(diào)用運(yùn)行在另外一臺(tái)服務(wù)器上的程序的功能方法。這種調(diào)用的過(guò)程跨越了物理服務(wù)器的限制,是在網(wǎng)絡(luò)中完成的,在調(diào)用遠(yuǎn)端服務(wù)器上程序的過(guò)程中,本地程序等待返回調(diào)用結(jié)果,直到遠(yuǎn)端程序執(zhí)行完畢,將結(jié)果進(jìn)行返回到本地,最終完成一次完整的調(diào)用。
需要強(qiáng)調(diào)的是:遠(yuǎn)程過(guò)程調(diào)用指的是調(diào)用遠(yuǎn)端服務(wù)器上的程序的方法整個(gè)過(guò)程。
1.5、RPC設(shè)計(jì)組成
RPC技術(shù)在架構(gòu)設(shè)計(jì)上有四部分組成,分別是:客戶(hù)端、客戶(hù)端存根、服務(wù)端、服務(wù)端存根。
這里提到了客戶(hù)端和服務(wù)端的概念,其屬于程序設(shè)計(jì)架構(gòu)的一種方式,在現(xiàn)代的計(jì)算機(jī)軟件程序架構(gòu)設(shè)計(jì)上,大方向上分為兩種方向,分別是:B/S架構(gòu)、C/S架構(gòu)。B/S架構(gòu)指的是瀏覽器到服務(wù)器交互的架構(gòu)方式,另外一種是在計(jì)算機(jī)上安裝一個(gè)單獨(dú)的應(yīng)用,稱(chēng)之為客戶(hù)端,與服務(wù)器交互的模式。
由于在服務(wù)的調(diào)用過(guò)程中,有一方是發(fā)起調(diào)用方,另一方是提供服務(wù)方。因此,我們把服務(wù)發(fā)起方稱(chēng)之為客戶(hù)端,把服務(wù)提供方稱(chēng)之為服務(wù)端。以下是對(duì)RPC的四種角色的解釋和說(shuō)明:
- **客戶(hù)端(Client):**服務(wù)調(diào)用發(fā)起方,也稱(chēng)為服務(wù)消費(fèi)者。
- **客戶(hù)端存根(Client Stub):**該程序運(yùn)行在客戶(hù)端所在的計(jì)算機(jī)機(jī)器上,主要用來(lái)存儲(chǔ)要調(diào)用的服務(wù)器的地址,另外,該程序還負(fù)責(zé)將客戶(hù)端請(qǐng)求遠(yuǎn)端服務(wù)器程序的數(shù)據(jù)信息打包成數(shù)據(jù)包,通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)端Stub程序;其次,還要接收服務(wù)端Stub程序發(fā)送的調(diào)用結(jié)果數(shù)據(jù)包,并解析返回給客戶(hù)端。
- **服務(wù)端(Server):**遠(yuǎn)端的計(jì)算機(jī)機(jī)器上運(yùn)行的程序,其中有客戶(hù)端要調(diào)用的方法。
- **服務(wù)端存根(Server Stub):**接收客戶(hù)Stub程序通過(guò)網(wǎng)絡(luò)發(fā)送的請(qǐng)求消息數(shù)據(jù)包,并調(diào)用服務(wù)端中真正的程序功能方法,完成功能調(diào)用;其次,將服務(wù)端執(zhí)行調(diào)用的結(jié)果進(jìn)行數(shù)據(jù)處理打包發(fā)送給客戶(hù)端Stub程序。
1.6、RPC原理及調(diào)用步驟
了解完了RPC技術(shù)的組成結(jié)構(gòu)我們來(lái)看一下具體是如何實(shí)現(xiàn)客戶(hù)端到服務(wù)端的調(diào)用的。實(shí)際上,如果我們想要在網(wǎng)絡(luò)中的任意兩臺(tái)計(jì)算機(jī)上實(shí)現(xiàn)遠(yuǎn)程調(diào)用過(guò)程,要解決很多問(wèn)題,比如:
- 兩臺(tái)物理機(jī)器在網(wǎng)絡(luò)中要建立穩(wěn)定可靠的通信連接。
- 兩臺(tái)服務(wù)器的通信協(xié)議的定義問(wèn)題,即兩臺(tái)服務(wù)器上的程序如何識(shí)別對(duì)方的請(qǐng)求和返回結(jié)果。也就是說(shuō)兩臺(tái)計(jì)算機(jī)必須都能夠識(shí)別對(duì)方發(fā)來(lái)的信息,并且能夠識(shí)別出其中的請(qǐng)求含義和返回含義,然后才能進(jìn)行處理。這其實(shí)就是通信協(xié)議所要完成的工作。
讓我們來(lái)看看RPC具體是如何解決這些問(wèn)題的,RPC每一步的調(diào)用過(guò)程。具體描述為:
1、客戶(hù)端想要發(fā)起一個(gè)遠(yuǎn)程過(guò)程調(diào)用,首先通過(guò)調(diào)用本地客戶(hù)端Stub程序的方式調(diào)用想要使用的功能方法名;
2、客戶(hù)端Stub程序接收到了客戶(hù)端的功能調(diào)用請(qǐng)求,將客戶(hù)端請(qǐng)求調(diào)用的方法名,攜帶的參數(shù)等信息做序列化操作,并打包成數(shù)據(jù)包。
3、客戶(hù)端Stub查找到遠(yuǎn)程服務(wù)器程序的IP地址,調(diào)用Socket通信協(xié)議,通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)端。
4、服務(wù)端Stub程序接收到客戶(hù)端發(fā)送的數(shù)據(jù)包信息,并通過(guò)約定好的協(xié)議將數(shù)據(jù)進(jìn)行反序列化,得到請(qǐng)求的方法名和請(qǐng)求參數(shù)等信息。
5、服務(wù)端Stub程序準(zhǔn)備相關(guān)數(shù)據(jù),調(diào)用本地Server對(duì)應(yīng)的功能方法進(jìn)行,并傳入相應(yīng)的參數(shù),進(jìn)行業(yè)務(wù)處理。
6、服務(wù)端程序根據(jù)已有業(yè)務(wù)邏輯執(zhí)行調(diào)用過(guò)程,待業(yè)務(wù)執(zhí)行結(jié)束,將執(zhí)行結(jié)果返回給服務(wù)端Stub程序。
7、服務(wù)端Stub程序**將程序調(diào)用結(jié)果按照約定的協(xié)議進(jìn)行序列化,**并通過(guò)網(wǎng)絡(luò)發(fā)送回客戶(hù)端Stub程序。
8、客戶(hù)端Stub程序接收到服務(wù)端Stub發(fā)送的返回?cái)?shù)據(jù),**對(duì)數(shù)據(jù)進(jìn)行反序列化操作,**并將調(diào)用返回的數(shù)據(jù)傳遞給客戶(hù)端請(qǐng)求發(fā)起者。
9、客戶(hù)端請(qǐng)求發(fā)起者得到調(diào)用結(jié)果,整個(gè)RPC調(diào)用過(guò)程結(jié)束。
1.7、RPC涉及到的相關(guān)技術(shù)
通過(guò)上文一系列的文字描述和講解,我們已經(jīng)了解了RPC的由來(lái)和RPC整個(gè)調(diào)用過(guò)程。我們可以看到RPC是一系列操作的集合,其中涉及到很多對(duì)數(shù)據(jù)的操作,以及網(wǎng)絡(luò)通信。因此,我們對(duì)RPC中涉及到的技術(shù)做一個(gè)總結(jié)和分析:
- 1、動(dòng)態(tài)代理技術(shù): 上文中我們提到的Client Stub和Sever Stub程序,在具體的編碼和開(kāi)發(fā)實(shí)踐過(guò)程中,都是使用動(dòng)態(tài)代理技術(shù)自動(dòng)生成的一段程序。
- 2、序列化和反序列化: 在RPC調(diào)用的過(guò)程中,我們可以看到數(shù)據(jù)需要在一臺(tái)機(jī)器上傳輸?shù)搅硗庖慌_(tái)機(jī)器上。在互聯(lián)網(wǎng)上,所有的數(shù)據(jù)都是以字節(jié)的形式進(jìn)行傳輸?shù)?。而我們?cè)诰幊痰倪^(guò)程中,往往都是使用數(shù)據(jù)對(duì)象,因此想要在網(wǎng)絡(luò)上將數(shù)據(jù)對(duì)象和相關(guān)變量進(jìn)行傳輸,就需要對(duì)數(shù)據(jù)對(duì)象做序列化和反序列化的操作。
**序列化:**把對(duì)象轉(zhuǎn)換為字節(jié)序列的過(guò)程稱(chēng)為對(duì)象的序列化,也就是編碼的過(guò)程。
**反序列化:**把字節(jié)序列恢復(fù)為對(duì)象的過(guò)程稱(chēng)為對(duì)象的反序列化,也就是解碼的過(guò)程。
我們常見(jiàn)的Json,XML等相關(guān)框架都可以對(duì)數(shù)據(jù)做序列化和反序列化編解碼操作。同時(shí),在之前的《Go語(yǔ)言微服務(wù)理論與實(shí)踐》課程中,我們已經(jīng)學(xué)習(xí)過(guò)Protobuf協(xié)議,這也是一種數(shù)據(jù)編解碼的協(xié)議,在RPC框架中使用的更廣泛。
二、Go語(yǔ)言實(shí)現(xiàn)RPC編程
上節(jié)課我們對(duì)RPC知識(shí)做了介紹,講解了RPC的原理,通過(guò)圖示方式講解了RPC的內(nèi)部執(zhí)行過(guò)程。本節(jié)課,我們繼續(xù)來(lái)學(xué)習(xí)RPC相關(guān)的內(nèi)容。
2.1、RPC官方庫(kù)
在Go語(yǔ)言官方網(wǎng)站的pkg說(shuō)明中,提供了官方支持的rpc包,具體鏈接如下:https://golang.org/pkg/net/rpc/。官方提供的rpc包完整的包名是:net/rpc。根據(jù)官方的解釋?zhuān)瑀pc包主要是提供通過(guò)網(wǎng)絡(luò)訪問(wèn)一個(gè)對(duì)象方法的功能。
本節(jié)課,我們就來(lái)學(xué)習(xí)如何使用go語(yǔ)言官方提供的RPC包實(shí)現(xiàn)RPC調(diào)用編碼。
2.2、net/rpc庫(kù)實(shí)現(xiàn)RPC調(diào)用編程
前文我們已經(jīng)講過(guò)rpc調(diào)用有兩個(gè)參與者,分別是:客戶(hù)端(client)和服務(wù)器(server)。
首先是提供方法暴露的一方–服務(wù)器。
2.2.1、服務(wù)定義及暴露
在編程實(shí)現(xiàn)過(guò)程中,服務(wù)器端需要注冊(cè)結(jié)構(gòu)體對(duì)象,然后通過(guò)對(duì)象所屬的方法暴露給調(diào)用者,從而提供服務(wù),該方法稱(chēng)之為輸出方法,此輸出方法可以被遠(yuǎn)程調(diào)用。當(dāng)然,在定義輸出方法時(shí),能夠被遠(yuǎn)程調(diào)用的方法需要遵循一定的規(guī)則。我們通過(guò)代碼進(jìn)行講解:
func (t *T) MethodName(request T1,response *T2) error
上述代碼是go語(yǔ)言官方給出的對(duì)外暴露的服務(wù)方法的定義標(biāo)準(zhǔn),其中包含了主要的幾條規(guī)則,分別是:
1、對(duì)外暴露的方法有且只能有兩個(gè)參數(shù),這個(gè)兩個(gè)參數(shù)只能是輸出類(lèi)型或內(nèi)建類(lèi)型,兩種類(lèi)型中的一種。
2、方法的第二個(gè)參數(shù)必須是指針類(lèi)型。
3、方法的返回類(lèi)型為error。
4、方法的類(lèi)型是可輸出的。
5、方法本身也是可輸出的。
我們舉例說(shuō)明:假設(shè)目前我們有一個(gè)需求,給出一個(gè)float類(lèi)型變量,作為圓形的半徑,要求通過(guò)RPC調(diào)用,返回對(duì)應(yīng)的圓形面積。具體的編程實(shí)現(xiàn)思路如下:
type MathUtil struct{ } //該方法向外暴露:提供計(jì)算圓形面積的服務(wù) func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error { *resp = math.Pi * req * req //圓形的面積 s = π * r * r return nil //返回類(lèi)型 }
在上述的案例中,我們可以看到:
1、Calculate方法是服務(wù)對(duì)象MathUtil向外提供的服務(wù)方法,該方法用于接收傳入的圓形半徑數(shù)據(jù),計(jì)算圓形面積并返回。
2、第一個(gè)參數(shù)req代表的是調(diào)用者(client)傳遞提供的參數(shù)。
3、第二個(gè)參數(shù)resp代表要返回給調(diào)用者的計(jì)算結(jié)果,必須是指針類(lèi)型。
4、正常情況下,方法的返回值為是error,為nil。如果遇到異?;蛱厥馇闆r,則error將作為一個(gè)字符串返回給調(diào)用者,此時(shí),resp參數(shù)就不會(huì)再返回給調(diào)用者。
至此為止,已經(jīng)實(shí)現(xiàn)了服務(wù)端的功能定義,接下來(lái)就是讓服務(wù)代碼生效,需要將服務(wù)進(jìn)行注冊(cè),并啟動(dòng)請(qǐng)求處理。
2.2.2、注冊(cè)服務(wù)及監(jiān)聽(tīng)請(qǐng)求
net/rpc包為我們提供了注冊(cè)服務(wù)和處理請(qǐng)求的一系列方法,結(jié)合本案例實(shí)現(xiàn)注冊(cè)及處理邏輯,如下所示:
//1、初始化指針數(shù)據(jù)類(lèi)型 mathUtil := new(MathUtil) //初始化指針數(shù)據(jù)類(lèi)型 //2、調(diào)用net/rpc包的功能將服務(wù)對(duì)象進(jìn)行注冊(cè) err := rpc.Register(mathUtil) if err != nil { ?? ?panic(err.Error()) } //3、通過(guò)該函數(shù)把mathUtil中提供的服務(wù)注冊(cè)到HTTP協(xié)議上,方便調(diào)用者可以利用http的方式進(jìn)行數(shù)據(jù)傳遞 rpc.HandleHTTP() //4、在特定的端口進(jìn)行監(jiān)聽(tīng) listen, err := net.Listen("tcp", ":8081") if err != nil { ?? ?panic(err.Error()) } go http.Serve(listen, nil)
經(jīng)過(guò)服務(wù)注冊(cè)和監(jiān)聽(tīng)處理,RPC調(diào)用過(guò)程中的服務(wù)端實(shí)現(xiàn)就已經(jīng)完成了。接下來(lái)需要實(shí)現(xiàn)的是客戶(hù)端請(qǐng)求代碼的實(shí)現(xiàn)。
2.2.3、客戶(hù)端調(diào)用
在服務(wù)端是通過(guò)Http的端口監(jiān)聽(tīng)方式等待連接的,因此在客戶(hù)端就需要通過(guò)http連接,首先與服務(wù)端實(shí)現(xiàn)連接。
客戶(hù)端連接服務(wù)端
client, err := rpc.DialHTTP("tcp", "localhost:8081") if err != nil { panic(err.Error()) }
遠(yuǎn)端方法調(diào)用
客戶(hù)端成功連接服務(wù)端以后,就可以通過(guò)方法調(diào)用調(diào)用服務(wù)端的方法,具體調(diào)用方法如下:
var req float32 //請(qǐng)求值 req = 3 var resp *float32 //返回值 err = client.Call("MathUtil.CalculateCircleArea", req, &resp) if err != nil { ?? ?panic(err.Error()) } fmt.Println(*resp)
上述的調(diào)用方法核心在于client.Call方法的調(diào)用,該方法有三個(gè)參數(shù),第一個(gè)參數(shù)表示要調(diào)用的遠(yuǎn)端服務(wù)的方法名,第二個(gè)參數(shù)是調(diào)用時(shí)要傳入的參數(shù),第三個(gè)參數(shù)是調(diào)用要接收的返回值。
上述的Call方法調(diào)用實(shí)現(xiàn)的方式是同步的調(diào)用,除此之外,還有一種異步的方式可以實(shí)現(xiàn)調(diào)用。異步調(diào)用代碼實(shí)現(xiàn)如下:
var respSync *float32 //異步的調(diào)用方式 syncCall := client.Go("MathUtil.CalculateCircleArea", req, &respSync, nil) replayDone := <-syncCall.Done fmt.Println(replayDone) fmt.Println(*respSync)
2.2.4、多參數(shù)的請(qǐng)求調(diào)用參數(shù)傳遞
上述內(nèi)容演示了單個(gè)參數(shù)下的RPC調(diào)用,對(duì)于多參數(shù)下的請(qǐng)求該如何實(shí)現(xiàn)。我們通過(guò)另外一個(gè)案例進(jìn)行演示。
假設(shè)現(xiàn)在需要實(shí)現(xiàn)另外一個(gè)需求:通過(guò)RPC調(diào)用實(shí)現(xiàn)計(jì)算兩個(gè)數(shù)字相加功能并返回計(jì)算結(jié)果。此時(shí),就需要傳遞兩個(gè)參數(shù),具體實(shí)現(xiàn)如下:
將參數(shù)定義在一個(gè)新的結(jié)構(gòu)體中,存放在param包中:
type AddParma struct { Args1 float32 //第一個(gè)參數(shù) Args2 float32 //第二個(gè)參數(shù) }
在server.go文件中,實(shí)現(xiàn)兩數(shù)相加的功能,并實(shí)現(xiàn)服務(wù)注冊(cè)的邏輯:
func (mu *MathUtil) Add(param param.AddParma, resp *float32) error { ?? ?*resp = param.Args1 + param.Args2 //實(shí)現(xiàn)兩數(shù)相加的功能 ?? ?return nil } mathUtil := new(MathUtil) ?? ?err := rpc.RegisterName("MathUtil", mathUtil) ?? ?if err != nil { ?? ??? ?panic(err.Error()) ?? ?} ?? ?rpc.HandleHTTP() ?? ?listen, err := net.Listen("tcp", ":8082") ?? ?http.Serve(listen, nil)
在本案例中,我們通過(guò)新的注冊(cè)方法rpc.RegisterName實(shí)現(xiàn)了服務(wù)的注冊(cè)和調(diào)用。
至此,我們已經(jīng)完成了net/rpc包的最基礎(chǔ)的使用。
三、RPC與Protobuf結(jié)合使用
上節(jié)課我們使用Golang提供的核心net/rpc庫(kù)實(shí)現(xiàn)了RPC調(diào)用編程。本節(jié)課繼續(xù)來(lái)看一下RPC和之前所學(xué)的Protobuf在編程中的結(jié)合實(shí)現(xiàn)。
需求:假設(shè)在一個(gè)系統(tǒng)中,有訂單模塊(Order),其他模塊想要實(shí)現(xiàn)RPC的遠(yuǎn)程工程調(diào)用,根據(jù)訂單ID和時(shí)間戳可以獲取訂單信息。如果獲取成功就返回相應(yīng)的訂單信息;如果查詢(xún)不到返回失敗信息?,F(xiàn)在,我們來(lái)進(jìn)行需求的編程實(shí)現(xiàn)。
3.1、傳輸數(shù)據(jù)格式定義
在《Go語(yǔ)言微服務(wù)理論實(shí)踐課程》中,學(xué)習(xí)過(guò)關(guān)于Protobuf的相關(guān)知識(shí)??梢岳肞rotobuf相關(guān)規(guī)則定義相應(yīng)的數(shù)據(jù)格式,文件擴(kuò)展名是.proto。
數(shù)據(jù)定義
根據(jù)需求,定義message.proto文件,詳細(xì)定義如下:
syntax = "proto3"; package message; //訂單請(qǐng)求參數(shù) message OrderRequest { ? ? string orderId = 1; ? ? int64 timeStamp = 2; } //訂單信息 message OrderInfo { ? ? string OrderId = 1; ? ? string OrderName = 2; ? ? string OrderStatus = 3; }
在上述文件中,定義了客戶(hù)端發(fā)起RPC調(diào)用時(shí)的請(qǐng)求數(shù)據(jù)結(jié)構(gòu)OrderRequest和服務(wù)端查詢(xún)后返回的數(shù)據(jù)結(jié)構(gòu)OrderInfo。數(shù)據(jù)定義采用proto3語(yǔ)法實(shí)現(xiàn),整個(gè)數(shù)據(jù)定義被定義在message包下。
編譯proto文件
通過(guò)proto編譯命令對(duì).proto文件進(jìn)行編譯,自動(dòng)生成對(duì)應(yīng)結(jié)構(gòu)體的Go語(yǔ)言文件。編譯命令如下:
protoc ./message.proto --go_out=./
執(zhí)行上述命令是在message包下。編譯命令結(jié)束后,會(huì)在message包下生成message.pb.go文件,其中自動(dòng)生成了OrderRequest和OrderInfo在Go語(yǔ)言中結(jié)構(gòu)體的定義和相關(guān)的方法。
3.2、Protobuf格式數(shù)據(jù)與RPC結(jié)合
服務(wù)的定義
進(jìn)行RPC遠(yuǎn)程過(guò)程調(diào)用,實(shí)現(xiàn)調(diào)用遠(yuǎn)程服務(wù)器的方法,首先要有服務(wù)。在本案例中,定義提供訂單查詢(xún)功能的服務(wù),取名為OrderService,同時(shí)提供訂單信息查詢(xún)方法供遠(yuǎn)程調(diào)用。詳細(xì)的服務(wù)和方法定義如下:
//訂單服務(wù) type OrderService struct { } func (os *OrderService) GetOrderInfo(request message.OrderRequest, response *message.OrderInfo) error { ?? ?//201907310003 ?? ?orderMap := map[string]message.OrderInfo{ ?? ??? ?"201907300001": message.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"}, ?? ??? ?"201907310001": message.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"}, ?? ??? ?"201907310002": message.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"}, ?? ?} ? ?current := time.Now().Unix() ? ?if (request.TimeStamp > current) { ?? ? ?*response = message.OrderInfo{OrderId: "0", OrderName: "", OrderStatus: "訂單信息異常"} ? ?} else { ?? ? ?result := orderMap[request.OrderId]//201907310003 ?? ? ?if result.OrderId != "" { ?? ??? ? *response = orderMap[request.OrderId] ?? ? ?} else { ?? ??? ? return errors.New("server error") ?? ? ?} ? ?} ? ?return nil }
在服務(wù)的方法定義中,使用orderMap模擬初始訂單數(shù)據(jù)庫(kù),方便案例查詢(xún)展示。GetOrderInfo方法有兩個(gè)參數(shù),第一個(gè)是message.OrderRequest,作為調(diào)用者傳遞的參數(shù),第二個(gè)是message.OrderInfo,作為調(diào)用返回的參數(shù),通過(guò)此處的兩個(gè)參數(shù),將上文通過(guò).proto定義并自動(dòng)生成的Go語(yǔ)言結(jié)構(gòu)體數(shù)據(jù)結(jié)合起來(lái)。
服務(wù)的注冊(cè)和處理
服務(wù)定義好以后,需要將服務(wù)注冊(cè)到RPC框架,并開(kāi)啟http請(qǐng)求監(jiān)聽(tīng)處理。這部分代碼與之前的RPC服務(wù)端實(shí)現(xiàn)邏輯一致,具體實(shí)現(xiàn)如下:
func main() { ?? ?orderService := new(OrderService) ?? ?rpc.Register(orderService) ?? ?rpc.HandleHTTP() ?? ?listen, err := net.Listen("tcp", ":8081") ?? ?if err != nil { ?? ??? ?panic(err.Error()) ?? ?} ?? ?http.Serve(listen, nil) }
RPC客戶(hù)端調(diào)用實(shí)現(xiàn)
在客戶(hù)端,除了客戶(hù)端正常訪問(wèn)遠(yuǎn)程服務(wù)器的邏輯外,還需要準(zhǔn)備客戶(hù)端需要傳遞的請(qǐng)求數(shù)據(jù)message.OrderInfo。具體實(shí)現(xiàn)如下:
client, err := rpc.DialHTTP("tcp", "localhost:8081") ?? ?if err != nil { ?? ??? ?panic(err.Error()) ?? ?} ?? ?timeStamp := time.Now().Unix() ?? ?request := message.OrderRequest{OrderId: "201907310001", TimeStamp: timeStamp} ?? ?var response *message.OrderInfo ?? ?err = client.Call("OrderService.GetOrderInfo", request, &response) ?? ?if err != nil { ?? ??? ?panic(err.Error()) ?? ?} ?? ?fmt.Println(*response)
3.3、運(yùn)行結(jié)果
分別依次運(yùn)行server.go和client.go程序。
到此這篇關(guān)于Go中RPC遠(yuǎn)程過(guò)程調(diào)用的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go中RPC遠(yuǎn)程調(diào)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
GO語(yǔ)言字符串處理Strings包的函數(shù)使用示例講解
這篇文章主要為大家介紹了GO語(yǔ)言字符串處理Strings包的函數(shù)使用示例講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04Go語(yǔ)言轉(zhuǎn)化php數(shù)組的示例代碼
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言如何實(shí)現(xiàn)轉(zhuǎn)化php數(shù)組的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),對(duì)我們深入學(xué)習(xí)GO語(yǔ)言有一定的幫助,需要的可以參考下2023-11-11詳解Golang?ProtoBuf的基本語(yǔ)法總結(jié)
最近項(xiàng)目是采用微服務(wù)架構(gòu)開(kāi)發(fā)的,各服務(wù)之間通過(guò)gPRC調(diào)用,基于ProtoBuf序列化協(xié)議進(jìn)行數(shù)據(jù)通信,因此接觸學(xué)習(xí)了Protobuf,本文會(huì)對(duì)Protobuf的語(yǔ)法做下總結(jié),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助2022-10-10golang通過(guò)node_exporter監(jiān)控GPU及cpu頻率、溫度的代碼
node_exporter這個(gè)開(kāi)源組件是配合prometheus收集主機(jī)操作系統(tǒng)層的metrics的常用組件,但是官方?jīng)]有提供GPU卡的metrics的采集,今天通過(guò)本文給大家介紹golang通過(guò)node_exporter監(jiān)控GPU及cpu頻率、溫度的相關(guān)知識(shí),感興趣的朋友一起看看吧2022-05-05go語(yǔ)言對(duì)文件按照指定塊大小進(jìn)行分割的方法
這篇文章主要介紹了go語(yǔ)言對(duì)文件按照指定塊大小進(jìn)行分割的方法,實(shí)例分析了Go語(yǔ)言文件操作的技巧,需要的朋友可以參考下2015-03-03詳解Golang中結(jié)構(gòu)體方法的高級(jí)應(yīng)用
本文旨在深度剖析Go中結(jié)構(gòu)體方法的高級(jí)應(yīng)用。我們不僅會(huì)回顧結(jié)構(gòu)體方法的基本概念和用法,還將探討如何通過(guò)高級(jí)技巧和最佳實(shí)踐,希望對(duì)大家有所幫助2024-01-01