Go必知必會:Go RPC揭秘構建高效遠程服務的指南
什么是 RPC
遠程過程調用(Remote Procedure Call,RPC)是一種強大的通信機制,它允許程序像調用本地過程一樣簡單直接地請求遠程節點上的服務。RPC的實現通常依賴于客戶端與服務端之間建立的socket連接,這種連接方式相比HTTP REST等通信協議,在頻繁的遠程服務調用場景中,能夠顯著降低通信成本,提高效率。通過RPC,分布式系統中的各個組件可以無縫協作,就像它們運行在同一個地址空間中一樣。
進一步地,理解RPC的優勢,我們需要將其與本地過程調用相比較。本地過程調用是程序內部組件間的直接調用,而RPC則跨越了網絡,允許不同地理位置的節點進行通信。RPC的高效性在于它減少了每次通信所需的開銷,因為它避免了重復的連接建立和HTTP頭部解析。這種優化使得RPC成為構建高性能分布式系統的理想選擇,特別是在需要快速、可靠地進行遠程服務調用的場合。
為什么用RPC
在分布式系統中,遠程過程調用(RPC)是一種核心機制,允許應用程序像調用本地函數一樣調用遠程服務。HTTP協議因為無法在同一個進程內,或者無法在同一個服務器上通過本地調用的方式實現我們的需求,所以我們需要使用RPC。
RPC的優勢及與HTTP對比
RPC(遠程過程調用)技術以其高效性和易用性在分布式系統中得到廣泛應用。相較于HTTP等其他通信協議,RPC具有以下優勢。
- 性能優勢:RPC通常使用二進制序列化和壓縮技術,減少了數據傳輸的體積,同時減少了解析時間,提高了通信效率。
- 連接復用:RPC通過建立持久的連接,避免了HTTP協議中每次請求都需要建立新連接的開銷,從而降低了連接建立和關閉的頻率,提高了資源利用率。
- 實時性:由于RPC的連接復用和高效的序列化機制,它在實時性要求較高的場景中表現更為出色。
- 服務治理:RPC框架通常提供了服務發現、負載均衡、故障轉移等高級功能,這些功能在HTTP協議中通常需要額外的組件來實現。
- 語言無關性:RPC框架支持多種編程語言,使得不同語言編寫的服務能夠無縫通信,而HTTP協議雖然也是語言無關的,但在服務間的直接調用上不如RPC直觀和方便。
與RPC相比,HTTP協議在以下方面存在一些限制。
- 性能開銷:HTTP協議的文本格式(如JSON、XML)在序列化和反序列化過程中相對RPC的二進制協議有更大的性能開銷。
- 連接非持久性:HTTP/1.1雖然支持持久連接,但默認情況下每次請求仍然需要建立和關閉連接,這在高并發場景下可能導致性能瓶頸。
- 服務治理復雜性:HTTP協議本身不包含服務治理的功能,需要依賴額外的中間件或服務來實現。
- 語義表達限制:HTTP協議的GET、POST等方法在表達復雜操作時可能不夠直觀,而RPC可以定義任意的遠程調用方法。
綜上所述,RPC在分布式系統中提供了一種更為高效、靈活的通信方式,尤其是在需要頻繁遠程服務調用的場景中;然而,HTTP協議由于其廣泛的支持和簡單的語義,仍然在許多場景下被廣泛使用,尤其是在Web服務和互聯網通信中。選擇使用RPC還是HTTP,需要根據具體的應用場景和性能需求來決定。
RPC的使用邊界
RPC(遠程過程調用)是一種允許程序像調用本地函數一樣簡單直接地請求遠程計算機程序上服務的技術。它通過封裝遠程調用的細節,為分布式系統中的不同組件提供了一種透明化的通信方式。RPC的優勢在于其性能效率、跨語言調用能力及提高系統可擴展性的特點。相較于HTTP REST,RPC通常依賴于客戶端與服務端之間建立的socket連接,這減少了通信的開銷,尤其是在頻繁的遠程服務調用場景中,RPC的性能優勢更加明顯。
RPC的使用邊界主要體現在它適用于內部服務調用,特別是在公司內部的微服務架構中,RPC能夠實現高效的服務治理和負載均衡。然而,對于對外的異構環境,如瀏覽器接口調用、APP接口調用或第三方接口調用等,RPC可能不如HTTP REST適用。HTTP協議由于其廣泛的支持和簡單的語義,更適合于跨不同平臺和語言的網絡通信。
在技術選型時,應根據具體的應用場景和性能需求來決定使用RPC還是HTTP。例如,在需要高性能、低延遲的內部服務調用時,RPC可能是更好的選擇;而在需要與外部系統或不同語言編寫的服務進行通信時,HTTP REST可能更加合適。此外,RPC框架如Apache Thrift、gRPC、Dubbo等提供了豐富的功能,包括服務發現、負載均衡、故障轉移等,以支持大型分布式系統的構建和維護。
RPC入門實踐1:net/rpc
Go的net/rpc包提供了一個基本的RPC框架,支持自定義編碼和解碼。以下是一個簡單的服務端和客戶端示例,演示了如何使用net/rpc進行乘法和除法運算。
基本構成
- RPC的基本構成:服務端、客戶端
- 服務端基本構成:結構體、請求結構體、響應結構體
- 客戶端基本構成:請求結構體、響應結構體
代碼示例
rpc_service.go
package main
import (
"errors"
"fmt"
"log"
"net"
"net/http"
"net/rpc"
"os"
)
type Arith struct {
}
//請求結構體
type ArithRequest struct {
A int
B int
}
//響應結構體
type ArithResponse struct {
Pro int //乘積
Quo int //商
Rem int //余數
}
//乘積方法
func (this *Arith) Multiply(req ArithRequest,res *ArithResponse) error{
res.Pro = req.A * req.B
return nil
}
//除法運算方法
func (this *Arith) Divide(req ArithRequest,res *ArithResponse) error{
if req.B ==0 {
return errors.New("divide by zero")
}
res.Quo = req.A / req.B
res.Rem = req.A % req.B
return nil
}
func main() {
//注冊rpc服務
rpc.Register(new(Arith))
//采用http協議作為rpc載體
rpc.HandleHTTP()
lis,err := net.Listen("tcp","127.0.0.1:8095")
if err!=nil {
log.Fatalln("fatal error:",err)
}
fmt.Fprintf(os.Stdout,"%s","start connection\n")
//常規啟動http服務
http.Serve(lis,nil)
}
rpc_client.go
package main
import (
"fmt"
"log"
"net/rpc"
)
//算數運算請求結構體
type ArithRequest struct {
A int
B int
}
//響應結構體
type ArithResponse struct {
Pro int //乘
Quo int //商
Rem int //余數
}
func main() {
conn,err := rpc.DialHTTP("tcp","127.0.0.1:8095")
if err!=nil {
log.Fatalln("dialing error:",err)
}
req := ArithRequest{10,20}
var res ArithResponse
err = conn.Call("Arith.Multiply",req,&res) //乘法運算
if err!=nil {
log.Fatalln("arith error:",err)
}
fmt.Printf("%d * %d = %d\n",req.A,req.B,res.Pro)
//除法運算
err = conn.Call("Arith.Divide",req,&res)
if err!=nil {
log.Fatalln("arith error:",err)
}
fmt.Printf("%d / %d = %d 余數是:%d",req.A,req.B,res.Quo,res.Rem)
}
運行結果
先啟動服務端,再啟動客戶端連接服務端:
//服務端console
start connection
//客戶端console
10 * 20 = 200
10 / 20 = 0 余數是:10
RPC入門實踐2:net/rpc/jsonrpc
jsonrpc是net/rpc的子集包,使用JSON作為編碼格式,這使得它成為跨語言調用的理想選擇。
實現跨語言調用
jsonrpc_server.go
package main
import (
"errors"
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"os"
)
type Arith struct {
}
//請求結構體
type ArithRequest struct {
A int
B int
}
//響應結構體
type ArithResponse struct {
Pro int //乘積
Quo int //商
Rem int //余數
}
//乘積方法
func (this *Arith) Multiply(req ArithRequest,res *ArithResponse) error{
res.Pro = req.A * req.B
return nil
}
//除法運算方法
func (this *Arith) Divide(req ArithRequest,res *ArithResponse) error{
if req.B ==0 {
return errors.New("divide by zero")
}
res.Quo = req.A / req.B
res.Rem = req.A % req.B
return nil
}
func main() {
//注冊rpc服務
rpc.Register(new(Arith))
//采用http協議作為rpc載體
rpc.HandleHTTP()
lis,err := net.Listen("tcp","127.0.0.1:8096")
if err!=nil {
log.Fatalln("fatal error:",err)
}
fmt.Fprintf(os.Stdout,"%s","start connection\n")
//接收客戶端請求 并發處理 jsonrpc
for {
conn,err :=lis.Accept() //接收客戶端連接請求
if err!=nil {
continue
}
//并發處理客戶端請求
go func(conn net.Conn) {
fmt.Fprintf(os.Stdout,"%s","new client in coming\n")
jsonrpc.ServeConn(conn)
}(conn)
}
//常規啟動http服務
//http.Serve(lis,nil)
}
jsonrpc_client.go
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
//算數運算請求結構體
type ArithRequest struct {
A int
B int
}
//響應結構體
type ArithResponse struct {
Pro int //乘
Quo int //商
Rem int //余數
}
func main() {
// 只有這里不一樣
conn,err := jsonrpc.Dial("tcp","127.0.0.1:8096")
if err!=nil {
log.Fatalln("dialing error:",err)
}
req := ArithRequest{9,2}
var res ArithResponse
err = conn.Call("Arith.Multiply",req,&res) //乘法運算
if err!=nil {
log.Fatalln("arith error:",err)
}
fmt.Printf("%d * %d = %d\n",req.A,req.B,res.Pro)
//除法運算
err = conn.Call("Arith.Divide",req,&res)
if err!=nil {
log.Fatalln("arith error:",err)
}
fmt.Printf("%d / %d = %d 余數是:%d",req.A,req.B,res.Quo,res.Rem)
}
運行結果
先啟動服務端,再啟動客戶端連接服務端:
//服務端console
start connection
//客戶端console
9 * 2 = 18
9 / 2 = 4 余數是:1
//服務端console
new client in coming
RPC入門實踐3:go php跨語言調用
通過jsonrpc,Go可以輕松與其他支持JSON-RPC協議的語言(如PHP)進行通信。
Go作為服務端,PHP作為客戶端
jsonrpc_server.go:和入門2服務端的代碼一樣。
jsonrpc_client.php
<?php
class JsonRPC
{
private $conn;
function __construct($host, $port)
{
$this->conn = fsockopen($host, $port, $errno, $errstr, 3);
if (!$this->conn) {
return false;
}
}
public function Call($method, $params)
{
if (!$this->conn) {
return false;
}
$err = fwrite($this->conn, json_encode(array(
'method' => $method,
'params' => array($params),
'id' => 0,
)) . "\n");
if ($err === false) {
return false;
}
stream_set_timeout($this->conn, 0, 3000);
$line = fgets($this->conn);
if ($line === false) {
return NULL;
}
return json_decode($line, true);
}
}
$client = new JsonRPC("127.0.0.1", 8096);
$args = array('A' => 9, 'B' => 2);
$r = $client->Call("Arith.Multiply", $args);
printf("%d * %d = %d\n", $args['A'], $args['B'], $r['result']['Pro']);
$r = $client->Call("Arith.Divide", array('A' => 9, 'B' => 2));
printf("%d / %d, Quo is %d, Rem is %d\n", $args['A'], $args['B'], $r['result']['Quo'], $r['result']['Rem']);
運行結果
//本地啟動PHP服務:http://127.0.0.1/jsonrpc_client.php,運行結果如下:
9 * 2 = 18 9 / 2, Quo is 4, Rem is 1
總結
遠程過程調用(RPC)是一種強大通信機制,允許程序像調用本地過程一樣請求遠程服務。它相比 HTTP REST 等協議,在頻繁遠程調用場景中能降低成本、提高效率。
RPC 的優勢包括性能高、連接復用、實時性好、服務治理方便、語言無關等。與 HTTP 對比,HTTP 在性能開銷、連接持久性等方面有限制。
使用時應根據應用場景和性能需求選擇 RPC 或 HTTP,例如高性能內部調用選 RPC,與外部通信可能 HTTP 更合適。
本文轉載自微信公眾號「王中陽」,作者「王中陽」,可以通過以下二維碼關注。
轉載本文請聯系「王中陽」公眾號。