本文是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调用流程
简单图示:
详细介绍:
(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
", time.Now())
//等待客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("listener.Accept err: ", err)
continue
}
fmt.Printf("getted at %v
", 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
", 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
", args.A, args.B, quo.Quo, quo.Rem)
}