Go RPC

RPC是什么

RPC(Remote Procedure Call)是远程过程调用协议,一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。简言之,RPC使得程序能够像访问本地系统资源一样,去访问远端系统资源。

在OSI网络通信模型(应用层、表达层、会话层、传输层、网络层、数据链路层、物理层)中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

为何要有RPC

在我们业务不大的时候,大多数使用是 HTTP 协议,该协议简单,开发方便。随着系统业务增多,访问量的增大,服务的拆分和分布式部署,需要更加高效通信方式。HTTP 协议进行通信,需要每次三次握手建立连接,且不能不能建立长链接(HTTP 2 已支持),而 RPC 是长连接,没有 HTTP 协议的三次握手,减少网络开销。其次,RPC框架提供的是面向服务的封装,它针对服务的性能效率、可用性等都做了优化(比如提供了:注册中心、服务治理、负载均衡、二进制传输、熔断、服务降级等功能),是一套完整的解决方案;而HTTP调用缺少这些高级特性,它只是简单的数据通信,另外HTTP API受限于HTTP协议(要带HTTP请求头),传输效率及安全性不如RPC。

RPC 4个核心组件

  1. 客户端(client):服务调用者

  1. 客户端存根(client stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端

  1. 服务端存根(server stub):接收客户端请求的消息并进行解包,然后再调用本地服务进行处理

  1. 服务端(server):服务提供者

RPC 协议

协议是 rpc 重要一环,它主要解决客户端和服务端通信的问题,rpc协议示意图如下:

序列化协议

网络数据传输必须是二进制,执行过程是变成语言的对象方法,那么就涉及到如何将对象序列化成可传输消息(二进制),并可反序列化还原。常见的通用型协议如 XML、 JSON、Protobuf、Thrift 等,也有语言绑定的如Golang 原生支持的 Gob 协议等。

消息编码协议

它是一种客户端和服务端的调用约定,比如请求和参数如何组织,Header 放置什么内容。这部分每个框架设计均不同,有时也称这一层为狭义的 RPC 协议层。

网络传输协议

基于 TCP、UDP 还是 HTTP,UDP 要自己解决可靠性传输问题,而 HTTP 又太重,包含很多没必要的头信息,所以一般 RPC 框架会优先选择 TCP 协议(gRPC 基于 HTTP2)。

RPC 调用过程

rpc实现流程如下:

  1. 客户端(client)发起一个远程过程调用。

  1. 客户端存根(client stub)程序接收到客户端(client)调用请求,负责将调用方法名、参数等信息打包成可通过网路传输给数据包。

  1. 客户端存根(client stub)查找到远程服务器程序IP,将打包好的数据通过网络发送给服务端(Server)。

  1. 服务端存根(server stub)接收到客户端(client)的数据包信息,将数据信息解包。

  1. 服务端存根(server stub)根据解包结果调用本地服务进行相关处理。

  1. 服务端(server)执行结果返回给服务端存根(server stub)。

  1. 服务端存根(server stub)将执行结果打包成网络数据包,通过网络发送至客户端(Client)。

  1. 客户端存根(client stub)接收到消息,并进行解包。

  1. 客户端存根(client stub)将解包结果返回到客户端(client),得到最终执行结果,整个RPC调用过程结束。

Go RPC

Go标准包中已经提供了对RPC的支持,而且支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,它和传统的RPC系统不同,它只支持Go开发的服务器与客户端之间的交互,因为在内部,它们采用了Gob来编码。

Go RPC的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:

  • 函数必须是导出的(首字母大写)

  • 必须有两个导出类型的参数,

  • 第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的

  • 函数还要有一个返回值error

举个例子,正确的RPC函数格式如下:

func (t *T) MethodName(argType T1, replyType *T2) error

T、T1和T2类型必须能被encoding/gob包编解码。

HTTP RPC

服务端:

package main

import (
    "log"
    "net/http"
    "net/rpc"
)

type Args struct {
    A, B int
}

type Arith int

// Multiply RPC服务端方法,计算乘法
func (a *Arith) Multiply(args *Args, replay *int) error {
    *replay = args.A * args.B
    return nil
}

func main() {
    // 1.注册服务
    err := rpc.Register(new(Arith))
    if err != nil {
        return
    }
    // 2.服务处理绑定到http协议上
    rpc.HandleHTTP()
    // 3.监听服务
    err = http.ListenAndServe(":9000", nil)
    if err != nil {
        log.Fatal(err)
    }
}

客户端:

package main

import (
    "fmt"
    "log"
    "net/rpc"
)

// Args 传的参数
type Args struct {
    A, B int
}

func main() {
    // 1.连接远程rpc服务
    conn, err := rpc.DialHTTP("tcp", ":9000")
    if err != nil {
        log.Fatal(err)
    }
    // 2.调用方法
    var area int
    err = conn.Call("Arith.Multiply", Args{10, 5}, &area)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("面积:", area)
}

TCP RPC

服务端:

package main

import (
    "log"
    "net"
    "net/http"
    "net/rpc"
)

type Args struct {
    A, B int
}

type Reply struct {
    C int
}

type Arith int

// Multiply RPC服务端方法,计算乘法
func (t *Arith) Multiply(args Args, reply *Reply) error {
    reply.C = args.A * args.B
    return nil
}

func main() {
    tcpAddr, err := net.ResolveTCPAddr("tcp", ":9000")
    if err != nil {
        log.Fatal(err)
    }

    listener, err := net.ListenTCP("tcp", tcpAddr)
    if err != nil {
        log.Fatal(err)
    }

    err = rpc.Register(new(Arith))
    if err != nil {
        return
    }

    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        rpc.ServeConn(conn)
    }
}

客户端:

package main

import (
    "fmt"
    "log"
    "net/rpc"
)

// Args 传的参数
type Args struct {
    A, B int
}

type Reply struct {
    C int
}

func main() {
    // 1.连接远程rpc服务
    client, err := rpc.Dial("tcp", ":9000")
    if err != nil {
        log.Fatal("dialing", err)
    }
    defer client.Close()

    // 2.调用方法
    args := &Args{7, 8}
    reply := new(Reply)
    err = client.Call("Arith.Multiply", args, &reply)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply.C)
}

另外,net/rpc/jsonrpc库通过json格式编解码,支持跨语言调用

JSON RPC

服务端:

package main

import (
    "log"
    "net"
    "net/http"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type Args struct {
    A, B int
}

type Reply struct {
    C int
}

type Arith int

// Multiply RPC服务端方法,计算乘法
func (t *Arith) Multiply(args Args, reply *Reply) error {
    reply.C = args.A * args.B
    return nil
}

func main() {
    // 1.实例化服务
    listener, err := net.Listen("tcp", ":9000")
    if err != nil {
        log.Fatal(err)
    }
    // 2.注册服务
    err = rpc.Register(new(Arith))
    if err != nil {
        return
    }
    // 3.启动服务
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        jsonrpc.ServeConn(conn)
    }
}

客户端:

package main

import (
    "fmt"
    "log"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

// Args 传的参数
type Args struct {
    A, B int
}

type Reply struct {
    C int
}

func main() {
    // 1.连接远程rpc服务
    conn, err := net.Dial("tcp", ":9000")
    if err != nil {
        log.Panicln(err)
    }
    // 2.调用方法
    args := &Args{7, 8}
    reply := new(Reply)
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    err = client.Call("Arith.Multiply", args, &reply)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply.C)
}

常见的 RPC 框架

  • gRPC:gRPC是谷歌开源的一款高性能的rpc框架 (https://grpc.io),是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。

  • Thrift:thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。

猜你喜欢

转载自blog.csdn.net/qq_34272964/article/details/129251449