RPC 簡介
RPC(Remote Procedure Call):遠程過程調用,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的思想。
RPC 是一種技術思想而非一種規范或協議,常見 RPC 技術和框架有:
- 應用級的服務框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
- 遠程通信協議:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
- 通信框架:MINA 和 Netty。
目前流行的開源 RPC 框架還是比較多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。
完整的 RPC 框架
在一個典型 RPC 的使用場景中,包含了服務發現、負載、容錯、網絡傳輸、序列化等組件,其中“RPC 協議”就指明了程序如何進行網絡傳輸和序列化。
圖 1:完整 RPC 架構圖
如下是 Dubbo 的設計架構圖,分層清晰,功能復雜:
圖 2:Dubbo 架構圖
RPC 核心功能
RPC 的核心功能是指實現一個 RPC 最重要的功能模塊,就是上圖中的”RPC 協議”部分:
圖 3:RPC 核心功能
一個 RPC 的核心功能主要有 5 個部分組成,分別是:客戶端、客戶端 Stub、網絡傳輸模塊、服務端 Stub、服務端等。
圖 4:RPC 核心功能圖
下面分別介紹核心 RPC 框架的重要組成:
- 客戶端(Client):服務調用方。
- 客戶端存根(Client Stub):存放服務端地址信息,將客戶端的請求參數數據信息打包成網絡消息,再通過網絡傳輸發送給服務端。
- 服務端存根(Server Stub):接收客戶端發送過來的請求消息並進行解包,然后再調用本地服務進行處理。
- 服務端(Server):服務的真正提供者。
- Network Service:底層傳輸,可以是 TCP 或 HTTP。
Golang RPC Demo
JSON-RPC
JSON-RPC是一個輕量級的遠程調用協議,簡單易用。本章節通過該協議進行演示。
請求數據體
1 { 2 "id": 1, //本次請求的標識碼,遠程返回時數據的標識碼應與本次請求的標識碼相同 3 "method": "getName", //遠端的方法名 4 "params": ["1"] //遠程方法接收的參數列表 5 }
返回數據體
1 { 2 "id": 1, //調用時所傳來的id 3 "result": {"id": 1, "name": "name1"} //遠程方法返回值 4 "error": null //錯誤信息 5 }
Go 的 RPC 包
net/rpc 包實現了最基本的rpc調用,它默認通過HTTP協議傳輸gob數據來實現遠程調用。
服務端實現了一個HTTP server,接收客戶端的請求,在收到調用請求后,會反序列化客戶端傳來的gob數據,獲取要調用的方法名,並通過反射來調用我們自己實現的處理方法,這個處理方法傳入固定的兩個參數,並返回一個error對象,參數分別為客戶端的請求內容以及要返回給客戶端的數據體的指針。
net/rpc/jsonrpc 包實現了JSON-RPC協議,即實現了net/rpc包的ClientCodec接口與ServerCodec,增加了對json數據的序列化與反序列化。
Demo
代碼目錄結構
Demo代碼
service.go
1 package service 2 3 import "errors" 4 5 // 需要傳輸的對象 6 type DemoService struct { 7 } 8 9 // 需要傳輸的參數 10 type Args struct { 11 A, B int 12 } 13 14 // 普通的數學除法服務 如果被除數是0 則返回錯誤 15 func (s DemoService) Div(args Args, result *float64) error { 16 if args.B == 0 { 17 return errors.New("division by zero") 18 } 19 *result = float64(args.A) / float64(args.B) 20 return nil 21 }
server.go
1 package main 2 3 import ( 4 "RPC/service" 5 "log" 6 "net" 7 "net/rpc" 8 "net/rpc/jsonrpc" 9 ) 10 11 func main() { 12 // 注冊一個服務 13 err := rpc.Register(service.DemoService{}) 14 if err != nil { 15 panic(err) 16 } 17 18 // 開始監聽 使用 1234 端口 19 listener, err := net.Listen("tcp", ":1234") 20 if err != nil { 21 panic(err) 22 } 23 24 // 等待並處理連接 25 for { 26 conn, err := listener.Accept() 27 if err != nil { 28 log.Printf("accept error: %v", err) 29 continue 30 } 31 // 使用協程 goroutine 異步處理連接請求 32 go jsonrpc.ServeConn(conn) 33 } 34 }
client.go
1 package main 2 3 import ( 4 "RPC/service" 5 "fmt" 6 "net" 7 "net/rpc" 8 "net/rpc/jsonrpc" 9 ) 10 11 func main() { 12 // 創建連接 13 conn, err := net.Dial("tcp", ":1234") 14 if err != nil { 15 panic(err) 16 } 17 18 // 建立 json-rpc 協議 客戶端 通信管道 19 client := jsonrpc.NewClient(conn) 20 21 // 發送請求數據 22 SendData(client, service.Args{ 23 A: 10, 24 B: 3, 25 }) 26 SendData(client, service.Args{ 27 A: 10, 28 B: 0, 29 }) 30 } 31 32 func SendData(client *rpc.Client, args service.Args) { 33 var result float64 34 err := client.Call("DemoService.Div", args, &result) 35 if err != nil { 36 fmt.Println(err) 37 } else { 38 fmt.Println(result) 39 } 40 }
流程復現
- 運行 server.go 啟動 RPC 服務器
- 通過 window 自帶的 telnet 服務 手工發送請求測試
如果連接成功 會出現一片空白
此時鍵入 Ctrl + ] 進入 Microsoft Telnet 命令行模式 輸入
1 send {"id":1,"method":"DemoService.Div","params":[{"A":10,"B":3}]}
發送數據后 再按一下回車鍵 退出 Microsoft Telnet 命令行模式
由結果可以知道 調用除法服務成功 結果:10 / 3 = 3.3333333333333335
再發送一組錯誤參數
1 send {"id":1,"method":"DemoService.Div","params":[{"A":10,"B":0}]}
得到正確的錯誤返回 說明我們的Demo服務正確執行了
- 運行 client.go 通過客戶端模式 發送請求
得到的結果跟預期一樣 Demo演示結束