查看原文
其他

Go必知必会:Go RPC揭秘构建高效远程服务的指南

就业陪跑训练营 王中阳
2024-08-30

文末有面经共享群

本文来自极客学院专栏,欢迎订阅:Go入门进阶实战专栏:其实学Go很简单。

什么是 RPC

远程过程调用(Remote Procedure Call,RPC)是一种强大的通信机制,它允许程序像调用本地过程一样简单直接地请求远程节点上的服务。RPC的实现通常依赖于客户端与服务端之间建立的socket连接,这种连接方式相比HTTP REST等通信协议,在频繁的远程服务调用场景中,能够显著降低通信成本,提高效率。通过RPC,分布式系统中的各个组件可以无缝协作,就像它们运行在同一个地址空间中一样。

进一步地,理解RPC的优势,我们需要将其与本地过程调用相比较。本地过程调用是程序内部组件间的直接调用,而RPC则跨越了网络,允许不同地理位置的节点进行通信。RPC的高效性在于它减少了每次通信所需的开销,因为它避免了重复的连接建立和HTTP头部解析。这种优化使得RPC成为构建高性能分布式系统的理想选择,特别是在需要快速、可靠地进行远程服务调用的场合。

为什么用RPC

在分布式系统中,远程过程调用(RPC)是一种核心机制,允许应用程序像调用本地函数一样调用远程服务。HTTP协议因为无法在同一个进程内,或者无法在同一个服务器上通过本地调用的方式实现我们的需求,所以我们需要使用RPC。

RPC的优势及与HTTP对比

RPC(远程过程调用)技术以其高效性和易用性在分布式系统中得到广泛应用。相较于HTTP等其他通信协议,RPC具有以下优势。

  1. 性能优势:RPC通常使用二进制序列化和压缩技术,减少了数据传输的体积,同时减少了解析时间,提高了通信效率。

  2. 连接复用:RPC通过建立持久的连接,避免了HTTP协议中每次请求都需要建立新连接的开销,从而降低了连接建立和关闭的频率,提高了资源利用率。

  3. 实时性:由于RPC的连接复用和高效的序列化机制,它在实时性要求较高的场景中表现更为出色。

  4. 服务治理:RPC框架通常提供了服务发现、负载均衡、故障转移等高级功能,这些功能在HTTP协议中通常需要额外的组件来实现。

  5. 语言无关性:RPC框架支持多种编程语言,使得不同语言编写的服务能够无缝通信,而HTTP协议虽然也是语言无关的,但在服务间的直接调用上不如RPC直观和方便。

与RPC相比,HTTP协议在以下方面存在一些限制。

  1. 性能开销:HTTP协议的文本格式(如JSON、XML)在序列化和反序列化过程中相对RPC的二进制协议有更大的性能开销。

  2. 连接非持久性:HTTP/1.1虽然支持持久连接,但默认情况下每次请求仍然需要建立和关闭连接,这在高并发场景下可能导致性能瓶颈。

  3. 服务治理复杂性:HTTP协议本身不包含服务治理的功能,需要依赖额外的中间件或服务来实现。

  4. 语义表达限制: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进行乘法和除法运算。

基本构成

  1. RPC的基本构成:服务端、客户端

  2. 服务端基本构成:结构体、请求结构体、响应结构体

  3. 客户端基本构成:请求结构体、响应结构体

代码示例

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

jsonrpcnet/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, 03000);
        $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 更合适。

早日上岸!

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。

关注上方公众号,回复 AI ,免费领取AI提效工具。


点击下方文章,看看他们是怎么找到好工作的!

这些朋友赢麻了!

我们又出成绩啦!大厂Offer集锦!遥遥领先!

继续滑动看下一个
王中阳
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存