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