四.RPC

什么是RPC

RPC(Remote Procedure Call Protocal),远程过程调度协议,是一种进程间通信方式。它允许程序像调用本地接口一样调用另一个地址空间(通常是共享网络的另一台机器上,当然也可以是本机其他服务)的过程或函数,并得到返回的结果,而不需要关心底层通信细节和调用过程。

RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

为什么要使用RPC

良好的RPC调用时面向服务的封装,针对服务的可用性和效率都做了优化,相比较于单纯的http调用,多了更多面向服务的高级特效。

RPC应用场景

在一个由多个微服务构成的大系统中,某些关键服务间的调用应当在较短的时间内返回,而且各个微服务的专业化程度较高,同一个请求的关注者只有一个。这个时候就应该用RPC。

RPC调用方式

  1. 同步调用:客户端调用RPC接口后,必须等待服务端处理完毕并返回结果;
  2. 异步调用:客户端调用RPC接口后,不必等待执行结果返回,但依然可以通过回调通知方式获取返回结果。若客户端不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果

主流RPC框架

业界主流的RPC框架整体上分为三类:

  1. 支持多语言的RPC框架,比较成熟的有Google的grpc、Apache(Facebool)的Thrift;
  2. 只支持特定语言的RPC框架,例如新浪微博的Motan(基于java);
  3. 支持服务治理等服务化特性的分布式服务框架,其底层内核仍然是RPC框架,例如阿里的Dubbo。

RPC实现

  • RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。

  • 运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:

  1. 调用客户端句柄;执行传送参数;
  2. 调用本地系统内核发送网络消息;
  3. 消息传送到远程主机;
  4. 服务器句柄得到消息并取得参数;
  5. 执行远程过程;
  6. 执行的过程将结果返回服务器句柄;
  7. 服务器句柄返回结果,调用远程系统内核;
  8. 消息传回本地主机;
  9. 客户机句柄由内核接收消息;
  10. 客户接收句柄返回的消息

rpc实现

  • RPC包含传输协议和编码协议。在网络通信方面,RPC一般使用tcp作为传输协议,当然也可以使用http作为传输协议。从广义上来说,传统的http接口调用也可以称作是RPC的一种实现方式。而消息数据编码协议一般包括json(jsonrpc)、xml(xmlrpc)、protobuf(grpc)等序列化方法。

##. 代码实现

server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

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

type Rpc int
func (this *Rpc) TestRpc (argType string, replyType *string) error {
fmt.Println("客户端消息: ", argType)
*replyType = "rpc server received client message: " + argType
return nil
}

func main() {
listener, err := net.Listen("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("lister err: ", err)
}

demo := new(Rpc)
rpc.Register(demo)
rpc.HandleHTTP()

http.Serve(listener, nil)
}

client.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"net/rpc"
)

func main () {
cli, err := rpc.DialHTTP("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("dial rpc server failed, err: ", err)
}

var result string
err = cli.Call("Rpc.TestRpc", "hello, rpc", &result)
if err != nil {
fmt.Println("rpc call failed, err: ", err)
}

fmt.Println("rpc 调用结果为:", result)
}

运行结果

1
2
➜  rpc go run server.go
客户端消息: hello, rpc
1
2
➜  rpc go run client.go
rpc 调用结果为: rpc server received client message: hello, rpc