本文是RPC的简介与实践,首先介绍一下RPC的概念与原理,接着介绍一下Go语言对RPC的支持,最后提供一个Go语言原生RPC的简单demo。

 

一、RPC是什么

  RPC,全称:Remote Procedure Call,中文翻译:远程过程调用。

  RPC是一种技术思想,而非规范或协议,它指的是,本地计算机程序通过网络向远程计算机程序请求服务,隐藏底层网络技术,使其看起来就像调用本地函数一样简便,服务提供方与调用方都无需了解底层网络的传输细节。

 

二、RPC是怎么工作的

1. RPC框架的组成

(1)客户端(Client):服务调用者。

(2)服务端(Server):服务提供者。

(3)客户端存根(Client Stub):存放服务端的地址信息,负责将客户端的请求参数打包成网络消息,通过网络远程发送给服务方。

(4)服务端存根(Server Stub):接收调用方发送过来的消息,将消息解包,并调用本地的方法。

(5)底层网络传输:可以是TCP或HTTP。

2. 一次完整的RPC调用流程

简单图示:

rpc es rpce是什么组织_rpc es

详细介绍:

(1)客户端通过本地调用的方式调用服务。

(2)客户端存根接收到调用请求后负责将调用的方法、参数等信息序列化成网络消息体,找到远程服务的地址,将消息通过网络发送给服务方。

(3)服务端存根接收到消息后进行解码(反序列化),根据解码结果调用本地服务。

(4)服务端调用本地方法进行业务逻辑处理,然后将处理结果返回给服务端存根。

(5)服务端存根将调用结果序列化,然后通过网络发送给调用方。

(6)客户端存根收到消息,将消息进行解码(方序列化),得到调用结果,将结果发送给客户端。

(7)客户端最终得到服务调用结果。

 

三、Go语言对RPC的支持

1. Go内置RPC

  Go标准包“net/rpc”中已经提供了对RPC的支持,而且支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC只支持Go开发的服务器与客户端之间的交互,无法跨语言调用,因为在内部,它们采用了Gob编码。

2. 什么是Gob编码

  Gob是Go语言内置的编解码方式,可以支持变长类型的编解码(意味着通用)而且它的效率比json,xml更高。

3. Go的RPC函数怎么写

  Go RPC函数只有符合下面的条件才能被远程访问,否则会被忽略:

(1)函数必须是导出的(函数名称首字母大写)。

(2)必须有两个导出类型的参数,第一个是接收的参数,第二个是返回给客户端的参数,第二个参数必须是指针类型的。

(3)必须有一个error类型的返回值。

4. 一个简单的demo

  这里提供一个基于TCP的RPC简单demo,逻辑很简单,就是求两个整型数值的乘机与商余,代码如下。

(1)服务端:

/**
* rpc demo tcp server - 求两数的乘积与商余
* author: JetWu
* date: 2020.04.21
 */
package main

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

//接收参数
type Args struct {
	A, B int
}

//返回参数
type Quotient struct {
	Quo, Rem int
}

//RPC服务函数

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	return nil
}

func (t *Arith) Divide(args *Args, quo *Quotient) error {
	if args.B == 0 {
		return errors.New("divide by zero")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}

func main() {
	//RPC服务注册
	arith := new(Arith)
	rpc.Register(arith)

	tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
	if err != nil {
		log.Fatal("net.ResolveTCPAddr err: ", err)
	}
	//监听网络
	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		log.Fatal("net.ListenTCP err: ", err)
	}
	defer listener.Close()
	log.Println("rpc demo tcp server started ...")

	for {
		fmt.Printf("accepted at %v\n", time.Now())
		//等待客户端连接
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err: ", err)
			continue
		}
		fmt.Printf("getted at %v\n", time.Now())
		//为客户端提供服务
		go rpc.ServeConn(conn)
	}
}

 

(2)客户端:

/**
* rpc demo tcp client - 求两数的乘积与商余
* author: JetWu
* date: 2020.04.21
 */
package main

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

//接收参数
type Args struct {
	A, B int
}

//返回参数
type Quotient struct {
	Quo, Rem int
}

func main() {
	//连接服务端
	client, err := rpc.Dial("tcp", ":1234")
	if err != nil {
		log.Fatal("rpc.Dial error: ", err)
	}
	log.Println("successfully connected to the rpc demo tcp server ...")

	//RPC服务调用

	args := Args{17, 8}
	var reply int
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("Arith.Multiply error: ", err)
	}
	fmt.Printf("Arith.Multiply: %d * %d = %d\n", args.A, args.B, reply)

	var quo Quotient
	err = client.Call("Arith.Divide", args, &quo)
	if err != nil {
		log.Fatal("Arith.Divide error: ", err)
	}
	fmt.Printf("Arith.Divide: %d / %d = %d remainder %d\n", args.A, args.B, quo.Quo, quo.Rem)
}

  

rpc es rpce是什么组织_Go_02