RPC

RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许调用不同进程空间的程序。RPC 的客户端和服务器可以在一台机器上,也可以在不同的机器上。程序员使用时,就像调用本地程序一样,无需关注内部的实现细节。

为什么需要rpc

两台机器上,两个应用程序之间需要通信。

  1. 首先需要确定采用的传输协议是什么?
    2. 两个应用程序位于不同的机器,那么一般会选择 TCP 协议或者 HTTP 协议;那如果两个应用程序位于相同的机器,也可以选择 Unix Socket 协议。

报文的编码格式?

比如采用最常用的 JSON 或者 XML,那如果报文比较大,还可能会选择 protobuf 等其他的编码方式。
1 首先要明确一点:RPC可以用HTTP协议实现,并且用HTTP是建立在 TCP 之上最广泛使用的 RPC。
2、现在业界提倡“微服务“的概念,而服务之间通信目前有两种方式,RPC就是其中一种。RPC可以保证不同服务之间的互相调用。即使是跨语言跨平台也不是问题,让构建分布式系统更加容易。
3、RPC框架都会有服务降级、流量控制的功能,保证服务的高可用。

rpc 实现

go中 RPC 可以有多种实现方式:但其大体思路就是相同的:

1 在 Server 端注册一个服务 eg:rpc.Register(new(Arith))

2 服务端监听相应的端口 lis, err := net.Listen(“tcp”, “127.0.0.1:8095”)

3 客户端请求对应地址加端口 conn, err := rpc.DialHTTP(“tcp”, “127.0.0.1:8095”)

4 客户端调用在服务端注册服务的方法,并返回返回值。

// 调用注册服务的方法, 服务名.方法名, 请求参数,返回参数
err = conn.Call(“Arith.Multiply”, req, &res)

在gRPC里,客户端可以像调用本地方法一样直接调用其他机器上的服务端应用程序的方法,帮助你更容易创建分布式应用程序和服务。与许多RPC系统一样,gRPC是基于定义一个服务,指定一个可以远程调用的带有参数和返回类型的的方法。在服务端程序中实现这个接口并且运行gRPC服务处理客户端调用。在客户端,有一个stub提供和服务端相同的方法。

grpc 系统结构 go grpc原理_tcp/ip

Go RPC可以利用tcp或http来传递数据,可以对要传递的数据使用多种类型的编解码方式。

golang官方的net/rpc库实现RPC方法,使用http作为RPC的载体,通过net/http包监听客户端连接请求。

示例:

服务器端

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.Register(new(Arith)) // 注册rpc服务
    rpc.HandleHTTP()         // 采用http协议作为rpc载体

    // 监听 127.0.0.1 的8095 端口
    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")

    http.Serve(lis, nil)
}

客户端

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() {
    // 请求 127.0.0.1 的8095 端口
    conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8095")
    if err != nil {
        log.Fatalln("dailing 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, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

注意方法名一定要大写,不然会显示找不到方法。