RPC
什么是RPC
RPC(Remote Promote Call) 一 远程过程调用,即一种进程间的通信方式。通过网络通信调用不同的服务。允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
RPC采用客户机/服务器模式,如下图:
用通俗易懂的语言描述就是:RPC允许跨机器、跨语言调用计算机程序方法。可以理解为:
- 客户端 A 服务器上,调用 B 服务器上应用提供的函数/方法
- RPC 由客户端发起,调用服务端的方法进行通信
- 服务端处理后,通过同样的方式,再把结果返回给客户端
如下图
客户端发起: 1 2 3 4 5
服务端发起:6 7 8 9 10
在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。
由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。
RPC 的核心
RPC 的核心点有两个:
- 通信协议
- 序列化
序列化和反序列化是一种把传输数据进行编码和解码的方式,常见的编解码方式有 JSON、Protobuf 等。
核心流程解析
- 客户端调用:服务调用方在调用服务时,一般进行相关初始化,通过配置文件/配置中心 获取服务端地址用户调用。
- 动态代理:服务调用者用的服务实际是远程服务的本地代理。
- 网络传输:将方法参数进行处理后,就要使用发起网络请求,使用tcp传输的就利用socket通信进行传输。其中请求的方式有同步阻塞/非阻塞。
- 序列化:毕竟是远程通信,需要将对象转化成二进制流进行传输。
服务端返回:服务端获取客户端请求的数据后,将返回值其序列化封装,通过netty进行数据返回,客户端在接受数据并解析,这就完成了一次rpc请求调用的全过程。
常见RPC技术和框架应用级的服务框架:Dubbo/Dubbox、ZeroICE、GRpc、Spring Boot/Spring Cloud。
- 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
- 通信框架:MINA和Netty。
Go 语言 RPC
Go SDK中,内置了 net/rpc 包来实现 RPC。使用 encoding/gob 进行编解码,支持tcp或http数据传输方式。由于其他语言不支持gob编解码方式,所以使用 net/rpc 库实现的RPC方法没办法进行跨语言调用。
golang官方还提供了 net/rpc/jsonrpc 库实现RPC方法,JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。
但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。
除了golang官方提供的rpc库,还有许多第三方库为在golang中实现RPC提供支持,大部分第三方rpc库的实现都是使用protobuf进行数据编解码,根据protobuf声明文件自动生成rpc方法定义与服务注册代码,在golang中可以很方便的进行rpc服务调用。
net/rpc库
下面的例子演示一下如何使用golang官方的net/rpc库实现RPC方法,使用http作为RPC的载体,通过net/http包监听客户端连接请求。
服务端: rpc_server.go
package main
import (
"fmt"
"log"
"net"
"net/http"
"net/rpc"
"os"
)
// 矩形结构体
type Rect struct {}
type Params struct {
Width int
Height int
}
// 面积
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
// 周长
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Width + p.Height) * 2
return nil
}
func main() {
// 注册RPC服务
rpc.Register(new(Rect))
// 采用http协议作为rpc载体
rpc.HandleHTTP()
lis, err := net.Listen("tcp", "127.0.0.1:8083")
if err != nil {
log.Fatalln("fatal error:", err)
}
fmt.Println(os.Stdout, "%s", "start connect\n")
http.Serve(lis, nil)
}
客户端: rpc_client.go
package main
import (
"fmt"
"log"
"net/rpc"
)
type Params struct {
Width int
Height int
}
func main() {
// 连接远程RPC服务
conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8083")
if err != nil {
log.Fatal(err)
}
ret := 0
err = conn.Call("Rect.Area", Params{50, 100}, &ret)
if err != nil {
log.Fatalln("error:", err)
}
fmt.Println("面积:", ret)
err = conn.Call("Rect.Perimeter", Params{50, 100}, &ret)
if err != nil {
log.Fatalln("error:", err)
}
fmt.Println("周长:", ret)
/*
打印结果
面积: 5000
周长: 300
*/
}
说明
# 启动服务端,运行后将会监听本地的8083端口
go run rpc_server.go
# 启动客户端,连接服务端并实现RPC方法调用
go run rpc_client.go
net/rpc/jsonrpc库
// server.go
package main
import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"os"
)
// 矩形结构体
type Rect struct {}
type Params struct {
Width int
Height int
}
// 面积
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
// 周长
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Width + p.Height) * 2
return nil
}
func main() {
// 注册RPC服务
rpc.Register(new(Rect))
// 采用http协议作为rpc载体
rpc.HandleHTTP()
lis, err := net.Listen("tcp", "127.0.0.1:8083")
if err != nil {
log.Fatalln("fatal error:", err)
}
fmt.Println(os.Stdout, "%s", "start connect\n")
for {
conn, err := lis.Accept() // 接收客户端连接请求
if err != nil {
continue
}
go func(conn net.Conn) { // 并发处理客户端请求
fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")
jsonrpc.ServeConn(conn)
}(conn)
}
}
// client.go
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
type Params struct {
Width int
Height int
}
func main() {
// 连接远程RPC服务
conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8083")
if err != nil {
log.Fatal(err)
}
ret := 0
err = conn.Call("Rect.Area", Params{50, 100}, &ret)
if err != nil {
log.Fatalln("error:", err)
}
fmt.Println("面积:", ret)
err = conn.Call("Rect.Perimeter", Params{50, 100}, &ret)
if err != nil {
log.Fatalln("error:", err)
}
fmt.Println("周长:", ret)
/*
打印结果
面积: 5000
周长: 300
*/
}
说明
代码基本相似
# 启动服务端,运行后将会监听本地的8083端口,
# 当客户端启动成功连接后,服务端会打印 new client in coming
go run server.go
# 启动客户端,连接服务端并实现RPC方法调用
go run client.go
总结
- 远程过程调用(Remote Procedure Call,缩写为 RPC)
- 可以将一些比较通用的场景抽象成微服务,然后供其他系统远程调用
- RPC 可以基于HTTP协议 也可以基于TCP协议,基于HTTP协议的RPC像是我们访问网页一样(GET/POST/PUT/DELETE/UPDATE),大部分的RPC都是基于TPC协议的(因为基于传输层,效率稍高一些)
- 基于TCP 的 RPC 工作过程
- 客户端对请求的对象序列化
- 客户端连接服务端,并将序列化的对象通过socket 传输给服务端,并等待接收服务端的响应
- 服务端收到请求对象后将其反序列化还原客户端的对象
- 服务端从请求对象中获取到请求的参数,然后执行对应的方法,得到返回结果
- 服务端将其结果序列化并传给客户端,客户端得到响应结果对象后将其反序列化,得到响应结果
参考:
- https://www.bilibili.com/video/BV1c54y1R7oB?from=search&seid=16932845423006243758&spm_id_from=333.337.0.0
- https://www.cnblogs.com/mindwind/p/5518145.html
- https://www.jianshu.com/p/7d6853140e13
- https://zhuanlan.zhihu.com/p/80149999
- https://studygolang.com/articles/14336
- https://www.cnblogs.com/zhuchenglin/p/13172984.html
- https://segmentfault.com/a/1190000040422920
- https://blog.csdn.net/qq_38093301/article/details/104210592