RPC 简介
RPC(Remote Procedure Call):远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。
RPC 是一种技术思想而非一种规范或协议,常见 RPC 技术和框架有:
- 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
- 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
- 通信框架:MINA 和 Netty。
目前流行的开源 RPC 框架还是比较多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。
完整的 RPC 框架
在一个典型 RPC 的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中“RPC 协议”就指明了程序如何进行网络传输和序列化。
图 1:完整 RPC 架构图
如下是 Dubbo 的设计架构图,分层清晰,功能复杂:
图 2:Dubbo 架构图
RPC 核心功能
RPC 的核心功能是指实现一个 RPC 最重要的功能模块,就是上图中的”RPC 协议”部分:
图 3:RPC 核心功能
一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等。
图 4:RPC 核心功能图
下面分别介绍核心 RPC 框架的重要组成:
- 客户端(Client):服务调用方。
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
- 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
- 服务端(Server):服务的真正提供者。
- Network Service:底层传输,可以是 TCP 或 HTTP。
Golang RPC Demo
JSON-RPC
JSON-RPC是一个轻量级的远程调用协议,简单易用。本章节通过该协议进行演示。
请求数据体
1 { 2 "id": 1, //本次请求的标识码,远程返回时数据的标识码应与本次请求的标识码相同 3 "method": "getName", //远端的方法名 4 "params": ["1"] //远程方法接收的参数列表 5 }
返回数据体
1 { 2 "id": 1, //调用时所传来的id 3 "result": {"id": 1, "name": "name1"} //远程方法返回值 4 "error": null //错误信息 5 }
Go 的 RPC 包
net/rpc 包实现了最基本的rpc调用,它默认通过HTTP协议传输gob数据来实现远程调用。
服务端实现了一个HTTP server,接收客户端的请求,在收到调用请求后,会反序列化客户端传来的gob数据,获取要调用的方法名,并通过反射来调用我们自己实现的处理方法,这个处理方法传入固定的两个参数,并返回一个error对象,参数分别为客户端的请求内容以及要返回给客户端的数据体的指针。
net/rpc/jsonrpc 包实现了JSON-RPC协议,即实现了net/rpc包的ClientCodec接口与ServerCodec,增加了对json数据的序列化与反序列化。
Demo
代码目录结构
Demo代码
service.go
1 package service 2 3 import "errors" 4 5 // 需要传输的对象 6 type DemoService struct { 7 } 8 9 // 需要传输的参数 10 type Args struct { 11 A, B int 12 } 13 14 // 普通的数学除法服务 如果被除数是0 则返回错误 15 func (s DemoService) Div(args Args, result *float64) error { 16 if args.B == 0 { 17 return errors.New("division by zero") 18 } 19 *result = float64(args.A) / float64(args.B) 20 return nil 21 }
server.go
1 package main 2 3 import ( 4 "RPC/service" 5 "log" 6 "net" 7 "net/rpc" 8 "net/rpc/jsonrpc" 9 ) 10 11 func main() { 12 // 注册一个服务 13 err := rpc.Register(service.DemoService{}) 14 if err != nil { 15 panic(err) 16 } 17 18 // 开始监听 使用 1234 端口 19 listener, err := net.Listen("tcp", ":1234") 20 if err != nil { 21 panic(err) 22 } 23 24 // 等待并处理连接 25 for { 26 conn, err := listener.Accept() 27 if err != nil { 28 log.Printf("accept error: %v", err) 29 continue 30 } 31 // 使用协程 goroutine 异步处理连接请求 32 go jsonrpc.ServeConn(conn) 33 } 34 }
client.go
1 package main 2 3 import ( 4 "RPC/service" 5 "fmt" 6 "net" 7 "net/rpc" 8 "net/rpc/jsonrpc" 9 ) 10 11 func main() { 12 // 创建连接 13 conn, err := net.Dial("tcp", ":1234") 14 if err != nil { 15 panic(err) 16 } 17 18 // 建立 json-rpc 协议 客户端 通信管道 19 client := jsonrpc.NewClient(conn) 20 21 // 发送请求数据 22 SendData(client, service.Args{ 23 A: 10, 24 B: 3, 25 }) 26 SendData(client, service.Args{ 27 A: 10, 28 B: 0, 29 }) 30 } 31 32 func SendData(client *rpc.Client, args service.Args) { 33 var result float64 34 err := client.Call("DemoService.Div", args, &result) 35 if err != nil { 36 fmt.Println(err) 37 } else { 38 fmt.Println(result) 39 } 40 }
流程复现
- 运行 server.go 启动 RPC 服务器
- 通过 window 自带的 telnet 服务 手工发送请求测试
如果连接成功 会出现一片空白
此时键入 Ctrl + ] 进入 Microsoft Telnet 命令行模式 输入
1 send {"id":1,"method":"DemoService.Div","params":[{"A":10,"B":3}]}
发送数据后 再按一下回车键 退出 Microsoft Telnet 命令行模式
由结果可以知道 调用除法服务成功 结果:10 / 3 = 3.3333333333333335
再发送一组错误参数
1 send {"id":1,"method":"DemoService.Div","params":[{"A":10,"B":0}]}
得到正确的错误返回 说明我们的Demo服务正确执行了
- 运行 client.go 通过客户端模式 发送请求
得到的结果跟预期一样 Demo演示结束