关于 RPC :
RPC(Remote Procedure Call Protocol)——远程过程调用协议,
是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
用于用于服务间调用的协议 。(一种供大家遵循的规范, 与restful一样没有实际的东西, 只是一种约定或者说规范)
设计的目的 :
实现函数调用模式的网络化。让客户端就像调用本地函数一样调用远程机器上的函数,然后客户端把这些参数
打包之后通过网络传递到服务端,服务端解包到处理过程中执行,然后执行的结果反馈给客户端。用以减轻客户端的压力。
RPC工作流程:
运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据
net/rpc 包解析:
软件包 rpc 通过网络或其他 I/O 连接提供对对象的导出方法的访问。
服务器注册一个对象,使其作为一个服务显示为对象类型的名称。
注册后,对象的导出方法将可以远程访问。服务器可以注册多个不同类型的对象(服务),
但注册多个相同类型的对象是错误的。
只有符合这些标准的方法才能用于远程访问; 其他方法将被忽略:
- the method's type is exported.
- the method is exported.
- the method has two arguments, both exported (or builtin) types.
- the method's second argument is a pointer.
- the method has return type error.
实际上,该方法必须看起来像
func (t *T) MethodName(argType T1, replyType *T2) error
T1 和 T2 可以通过 encoding/gob 进行编组。即使使用不同的编解码器,这些要求也适用。(将来,这些要求可能会因为自定义编解码器而变差。)
该方法的第一个参数表示由调用者提供的参数; 第二个参数表示要返回给调用者的结果参数。
如果非零,方法的返回值作为客户端看到的字符串传回,就像由 errors.New 创建的那样。
如果返回错误,答复参数将不会被发送回客户端。
demo : ------ server.go
package main
import (
"errors"
"log"
"net/http"
"net/rpc"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int // 1、 基于 int 定义一个新的类型(作为我们服务的提供者)
func (t *Arith) Multiply(args *Args, reply *int) error { // 2、 给服务者添加 Multiply 方法
*reply = args.A * args.B
return nil
}
func (t *Arith) Divide(args *Args, quo *Quotient) error { // 3、 给服务者添加 Divide 方法
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() {
arith := new(Arith) // 4、 创建一个 服务者 (用 new 方法返回服务者的地址)
err := rpc.Register(arith) // 5、 调用rpc.register方法注册暴露的服务对象
if err != nil {
log.Fatal("Register error:", err)
}
rpc.HandleHTTP() // 6、 将Rpc绑定到HTTP协议上。
err = http.ListenAndServe(":1234", nil) // 7、 启动 http 服务
if err != nil {
log.Fatal("ListenAndServe error:", err)
}
}
demo: --------------- client.go
package main
import (
"fmt"
"log"
"net/rpc"
"os"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "server")
os.Exit(1)
}
serverAddress := os.Args[1] // 接受参数作为服务提供方的 ip 地址
client, err := rpc.DialHTTP("tcp", serverAddress+":1234") // 拨号链接服务
if err != nil {
log.Fatal("dialing:", err)
}
args := Args{17, 8}
var reply int
// 进行远程调用:
err = client.Call("Arith.Multiply", args, &reply) // Call 调用指定的函数,【等待它完成】,并返回其错误状态。
if err != nil {
log.Fatal("arith error:", err)
}
var quot Quotient
err = client.Call("Arith.Divide", args, ") // Call 调用指定的函数,【等待它完成】,并返回其错误状态。
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("args = %v\n", args)
fmt.Printf("reply = %v\n", reply)
fmt.Printf("quot = %v\n", quot)
}
远程调用的两个方法 :
同步方法 :
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error
Call 调用指定的函数,等待它完成,并返回其错误状态。
err := client.Call("服务名.方法名", 方法接收参数, 一个指针代表的方法返回参数)
异步方法 :
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call
Go 异步调用该函数。它返回表示调用的 Call 结构。完成通道将通过返回相同的呼叫对象在呼叫完成时发出信号。
如果完成,则 Go 将分配一个新频道。如果非零,则必须进行缓冲或 Go 会故意崩溃
package main
import (
"fmt"
"log"
"net/rpc"
"os"
)
type Args struct {
A, B int
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "server")
os.Exit(1)
}
serverAddress := os.Args[1]
client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
if err != nil {
log.Fatal("dialing:", err)
}
args := Args{17, 8}
var reply int
done := make(chan *rpc.Call, 1)
call := client.Go("Arith.Multiply", args, &reply, done) // 异步调用
if call.Error != nil {
log.Fatal(err)
}
if resultCall := <-done; resultCall != nil { //如果不<-done,reply将不会有结果,reply将为0
fmt.Printf("Arith: %d*%d = %d \n", args.A, args.B, reply)
fmt.Printf("done : %#v\n", resultCall)
fmt.Printf("Multiply result : %#v\n", *(resultCall.Reply.(*int))) //根据Call的Reply得到返回的值
}
}
/*
输出 :
Arith: 17*8 = 136
done : &rpc.Call{ServiceMethod:"Arith.Multiply", Args:main.Args{A:17, B:8}, Reply:(*int)(0xc000058418), Error:error(nil), Done:(chan *rpc.Call)(0xc0000d63c0)}
Multiply result : 136
*/
rpc 总结 :
1、 rpc 是一种规范或约定, 而不是具体的技术, 跟 restful 性质类似。
2、 rpc 用于解决服务间远程调用 (比如原来我需要调用的函数与我运行的程序在一台服务器一个程序里。
我直接通过函数名字调用就行了。 现在我系统性能遇到瓶颈, 需要做系统拆分。
把这个函数放到另外一台服务器去, 我要去调用这个程序,就需要进行跨机器的进程间通信。 这个就是 rpc 出现的原因)。
3、使用 rpc 的前提是需要知道服务方的 地址、 服务的名字,服务方有哪些方法。
通过 【 地址 -> 服务名 -> 方法名 】 这三个要素来定位一个服务。
4、服务方提供的方法 接收 和 返回参数的格式是固定的,这是约定的一部分。
5、rpc的实现方式多种多样(比如http、tcp、websocket、消息队列等),我们只需要记住他要解决的问题就行了(跨机器的进程间通信),
具体的实现方式可以根据实际情况进行选择、切换。
参考 : https://cloud.tencent.com/developer/section/1143675