《Go語言高級編程》RPC 入門
一、什么是 RPC?
RPC(Remote Procedure Call,遠程過程調用)是分布式系統中不同節點間的通信方式,允許程序像調用本地函數一樣調用遠程服務的方法。
Go 語言的標準庫 net/rpc
提供了基礎的 RPC 實現,基于網絡(如 TCP)通信,支持服務注冊、方法調用和數據編碼。
二、RPC 版 “Hello, World” 示例
1. 服務端實現
// 定義服務類型及方法(需滿足 RPC 規則:公開方法、兩參數、第二參數為指針、返回 error)
type HelloService struct {}func (p *HelloService) Hello(request string, reply *string) error {*reply = "hello:" + requestreturn nil
}func main() {// 注冊服務,方法會被放在 "HelloService" 命名空間下rpc.RegisterName("HelloService", new(HelloService))// 監聽 TCP 連接listener, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal("ListenTCP error:", err)}// 接受單個連接并提供服務conn, err := listener.Accept()if err != nil {log.Fatal("Accept error:", err)}rpc.ServeConn(conn)
}
2. 客戶端實現
func main() {// 撥號連接服務端client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {log.Fatal("dialing:", err)}// 調用服務方法(參數:服務名.方法名、請求參數、響應指針)var reply stringerr = client.Call("HelloService.Hello", "hello", &reply)if err != nil {log.Fatal(err)}fmt.Println(reply) // 輸出:hello:hello
}
關鍵點說明:
- RPC 方法規則:必須為公開方法(首字母大寫),有兩個可序列化參數,第二參數為指針,返回
error
。 - 服務注冊:
rpc.RegisterName
將方法注冊到指定命名空間,便于客戶端調用。 - 通信流程:服務端監聽連接,客戶端撥號后通過
Call
方法遠程調用。
三、更安全的 RPC 接口設計
1. 接口規范定義
// 定義服務名、接口和注冊函數,解耦服務實現與調用
const HelloServiceName = "path/to/pkg.HelloService"type HelloServiceInterface interface {Hello(request string, reply *string) error
}func RegisterHelloService(svc HelloServiceInterface) error {return rpc.RegisterName(HelloServiceName, svc)
}
2. 客戶端封裝
// 封裝客戶端接口,簡化調用并提供類型安全保障
type HelloServiceClient struct {*rpc.Client
}// 確保 HelloServiceClient 實現接口
var _ HelloServiceInterface = (*HelloServiceClient)(nil)func DialHelloService(network, address string) (*HelloServiceClient, error) {c, err := rpc.Dial(network, address)if err != nil {return nil, err}return &HelloServiceClient{Client: c}, nil
}func (p *HelloServiceClient) Hello(request string, reply *string) error {return p.Client.Call(HelloServiceName+".Hello", request, reply)
}
3. 客戶端調用(簡化版)
func main() {client, err := DialHelloService("tcp", "localhost:1234")if err != nil {log.Fatal("dialing:", err)}var reply stringerr = client.Hello("hello", &reply)if err != nil {log.Fatal(err)}fmt.Println(reply)
}
優勢:
- 接口隔離:服務端和客戶端通過接口規范解耦,便于團隊分工。
- 類型安全:編譯器確保服務實現和客戶端調用符合接口定義,減少錯誤。
- 可維護性:服務名和方法名通過常量管理,避免硬編碼。
四、跨語言 RPC(基于 JSON 編碼)
Go 標準庫默認使用 gob 編碼(Go 特有),若需跨語言調用,可改用 JSON 編碼:
1. 服務端(JSON 版本)
func main() {rpc.RegisterName("HelloService", new(HelloService))listener, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal("ListenTCP error:", err)}// 支持多個連接,每個連接使用 JSON 編解碼器for {conn, err := listener.Accept()if err != nil {log.Fatal("Accept error:", err)}go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))}
}
2. 客戶端(JSON 版本)
func main() {// 手動建立 TCP 連接conn, err := net.Dial("tcp", "localhost:1234")if err != nil {log.Fatal("net.Dial:", err)}// 使用 JSON 編解碼器創建客戶端client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))var reply stringerr = client.Call("HelloService.Hello", "hello", &reply)if err != nil {log.Fatal(err)}fmt.Println(reply)
}
3. 數據格式(JSON)
- 請求格式:
{"method":"服務名.方法名","params":["參數"],"id":調用編號}
示例:{"method":"HelloService.Hello","params":["hello"],"id":0}
- 響應格式:
{"id":編號,"result":"結果","error":錯誤信息}
示例:{"id":0,"result":"hello:hello","error":null}
跨語言原理:
通過標準 JSON 格式交換數據,任何支持 JSON 解析的語言(如 Python、Java)都可按此格式調用 Go 的 RPC 服務。
五、基于 HTTP 的 RPC
將 RPC 服務架設在 HTTP 協議上,便于與 Web 系統集成:
服務端實現
func main() {rpc.RegisterName("HelloService", new(HelloService))// 在 HTTP 路徑 /jsonrpc 處理 RPC 請求http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {// 構造 IO 讀寫器,適配 HTTP 請求和響應var conn io.ReadWriteCloser = struct {io.Writerio.ReadCloser}{ReadCloser: r.Body,Writer: w,}rpc.ServeRequest(jsonrpc.NewServerCodec(conn))})http.ListenAndServe(":1234", nil)
}
客戶端調用(通過 curl)
curl localhost:1234/jsonrpc -X POST \--data '{"method":"HelloService.Hello","params":["hello"],"id":0}'
優勢:
- 兼容性強:HTTP 是互聯網標準協議,支持瀏覽器、API 網關等多種客戶端。
- 易于調試:可直接通過瀏覽器或 curl 工具測試 RPC 接口。
六、核心概念總結
- RPC 本質:封裝網絡通信,使遠程調用像本地函數調用一樣簡單。
- Go RPC 關鍵組件:
- 服務注冊:
rpc.RegisterName
綁定服務名與方法。 - 編解碼器:默認 gob,跨語言可用
jsonrpc
。 - 連接處理:
ServeConn
(單連接)、ServeCodec
(多連接)。
- 服務注冊:
- 設計原則:
- 接口與實現分離,通過規范解耦服務端與客戶端。
- 跨語言場景優先使用標準格式(如 JSON)編碼數據。