RPC技术内部原理是通过两种技术的组合来实现的:本地方法调用 和 网络通信技术。
在上述本地过程调用的例子中,我们是在一台计算机上执行了计算机上的程序,完成调用。随着计算机技术的发展和需求场景的变化,有时就需要从一台计算机上执行另外一台计算机上的程序的需求,因此后来又发展出来了RPC技术。特别是目前随着互联网技术的快速迭代和发展,用户和需求几乎都是以指数式的方式在高速增长,这个时候绝大多数情况下程序都是部署在多台机器上,就需要在调用其他物理机器上的程序的情况。
RPC是Remote Procedure Call Protocol三个单词首字母的缩写,简称为:RPC,翻译成中文叫远程过程调用协议。所谓远程过程调用,通俗的理解就是可以在本地程序中调用运行在另外一台服务器上的程序的功能方法。这种调用的过程跨越了物理服务器的限制,是在网络中完成的,在调用远端服务器上程序的过程中,本地程序等待返回调用结果,直到远端程序执行完毕,将结果进行返回到本地,最终完成一次完整的调用。
需要强调的是:远程过程调用指的是调用远端服务器上的程序的方法整个过程。
RPC技术在架构设计上有四部分组成,分别是:客户端、客户端存根、服务端、服务端存根。
这里提到了客户端和服务端的概念,其属于程序设计架构的一种方式,在现代的计算机软件程序架构设计上,大方向上分为两种方向,分别是:B/S架构、C/S架构。B/S架构指的是浏览器到服务器交互的架构方式,另外一种是在计算机上安装一个单独的应用,称之为客户端,与服务器交互的模式。
由于在服务的调用过程中,有一方是发起调用方,另一方是提供服务方。因此,我们把服务发起方称之为客户端,把服务提供方称之为服务端。以下是对RPC的四种角色的解释和说明:
了解完了RPC技术的组成结构我们来看一下具体是如何实现客户端到服务端的调用的。实际上,如果我们想要在网络中的任意两台计算机上实现远程调用过程,要解决很多问题,比如:
让我们来看看RPC具体是如何解决这些问题的,RPC具体的调用步骤图如下:
在上述图中,通过1-10的步骤图解的形式,说明了RPC每一步的调用过程。具体描述为:
通过上文一系列的文字描述和讲解,我们已经了解了RPC的由来和RPC整个调用过程。我们可以看到RPC是一系列操作的集合,其中涉及到很多对数据的操作,以及网络通信。因此,对RPC中涉及到的技术做一个总结和分析:
1、动态代理技术: 上文中我们提到的Client Stub和Sever Stub程序,在具体的编码和开发实践过程中,都是使用动态代理技术自动生成的一段程序。
2、序列化和反序列化: 在RPC调用的过程中,我们可以看到数据需要在一台机器上传输到另外一台机器上。在互联网上,所有的数据都是以字节的形式进行传输的。而我们在编程的过程中,往往都是使用数据对象,因此想要在网络上将数据对象和相关变量进行传输,就需要对数据对象做序列化和反序列化的操作。
我们常见的Json,XML等相关框架都可以对数据做序列化和反序列化编解码操作。同时,在之前的《Go语言微服务理论与实践》课程中,我们已经学习过Protobuf协议,这也是一种数据编解码的协议,在RPC框架中使用的更广泛。
在服务端流模式的RPC实现中,服务端得到客户端请求后,处理结束返回一个数据应答流。在发送完所有的客户端请求的应答数据后,服务端的状态详情和可选的跟踪元数据发送给客户端。服务端流RPC实现案例如下:
在.proto文件中定义服务接口,使用服务端流模式定义服务接口,如下所示:
...
//订单服务service定义
service OrderService {
rpc GetOrderInfos (OrderRequest) returns (stream OrderInfo) {}; //服务端流模式
}
我们可以看到与之前简单模式下的数据作为服务接口的参数和返回值不同的是,此处服务接口的返回值使用了stream进行修饰。通过stream修饰的方式表示该接口调用时,服务端会以数据流的形式将数据返回给客户端。
使用gRPC插件编译命令编译.proto文件,编译命令如下:
protoc --go_out=plugins=grpc:. message.proto
与数据结构体发送携带数据实现不同的时,流模式下的数据发送和接收使用新的功能方法完成。在自动生成的go代码程序当中,每一个流模式对应的服务接口,都会自动生成对应的单独的client和server程序,以及对应的结构体实现。具体编程如下图所示:
type OrderService_GetOrderInfosServer interface {
Send(*OrderInfo) error
grpc.ServerStream
}
type orderServiceGetOrderInfosServer struct {
grpc.ServerStream
}
func (x *orderServiceGetOrderInfosServer) Send(m *OrderInfo) error {
return x.ServerStream.SendMsg(m)
}
流模式下,服务接口的服务端提供Send方法,将数据以流的形式进行发送
type OrderService_GetOrderInfosClient interface {
Recv() (*OrderInfo, error)
grpc.ClientStream
}
type orderServiceGetOrderInfosClient struct {
grpc.ClientStream
}
func (x *orderServiceGetOrderInfosClient) Recv() (*OrderInfo, error) {
m := new(OrderInfo)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
流模式下,服务接口的客户端提供Recv()方法接收服务端发送的流数据。
定义好服务接口并编译生成代码文件后,即可根据规则对定义的服务进行编码实现。具体的服务编码实现如下所示:
//订单服务实现
type OrderServiceImpl struct {
}
//获取订单信息s
func (os *OrderServiceImpl) GetOrderInfos(request *message.OrderRequest, stream message.OrderService_GetOrderInfosServer) error {
fmt.Println(" 服务端流 RPC 模式")
orderMap := map[string]message.OrderInfo{
"201907300001": message.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"},
"201907310001": message.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"},
"201907310002": message.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"},
}
for id, info := range orderMap {
if (time.Now().Unix() >= request.TimeStamp) {
fmt.Println("订单序列号ID:", id)
fmt.Println("订单详情:", info)
//通过流模式发送给客户端
stream.Send(&info)
}
}
return nil
}
GetOrderInfos方法就是服务接口的具体实现,因为是流模式开发,服务端将数据以流的形式进行发送,因此,该方法的第二个参数类型为OrderService_GetOrderInfosServer,该参数类型是一个接口,其中包含Send方法,允许发送流数据。Send方法的具体实现在编译好的pb.go文件中,进一步调用grpc.SeverStream.SendMsg方法。
服务的监听与处理与前文所学内容没有区别,依然是相同的步骤:
func main() {
server := grpc.NewServer()
//注册
message.RegisterOrderServiceServer(server, new(OrderServiceImpl))
lis, err := net.Listen("tcp", ":8090")
if err != nil {
panic(err.Error())
}
server.Serve(lis)
}
服务端使用Send方法将数据以流的形式进行发送,客户端可以使用Recv()方法接收流数据,因为数据流失源源不断的,因此使用for无限循环实现数据流的读取,当读取到io.EOF时,表示流数据结束。客户端数据读取实现如下:
...
for {
orderInfo, err := orderInfoClient.Recv()
if err == io.EOF {
fmt.Println("读取结束")
return
}
if err != nil {
panic(err.Error())
}
fmt.Println("读取到的信息:", orderInfo)
}
...
按照先后顺序,依次运行server.go文件和client.go文件,可以得到运行结果。
服务端流 RPC 模式
订单序列号ID: 201907300001
订单详情: {201907300001 衣服 已付款 {} [] 0}
订单序列号ID: 201907310001
订单详情: {201907310001 零食 已付款 {} [] 0}
订单序列号ID: 201907310002
订单详情: {201907310002 食品 未付款 {} [] 0}
客户端请求RPC调用:服务端流模式
读取到的信息: OrderId:"201907310001" OrderName:"\351\233\266\351\243\237" OrderStatus:"\345\267\262\344\273\230\346\254\276"
读取到的信息: OrderId:"201907310002" OrderName:"\351\243\237\345\223\201" OrderStatus:"\346\234\252\344\273\230\346\254\276"
读取到的信息: OrderId:"201907300001" OrderName:"\350\241\243\346\234\215" OrderStatus:"\345\267\262\344\273\230\346\254\276"
读取结束
上文演示的是服务端以数据流的形式返回数据的形式。对应的,也存在客户端以流的形式发送请求数据的形式。
与服务端同理,客户端流模式的RPC服务声明格式,就是使用stream修饰服务接口的接收参数,具体如下所示:
...
//订单服务service定义
service OrderService {
rpc AddOrderList (stream OrderRequest) returns (OrderInfo) {}; //客户端流模式
}
使用编译命令编译.protow文件。客户端流模式中也会自动生成服务接口的接口。
type OrderService_AddOrderListServer interface {
SendAndClose(*OrderInfo) error
Recv() (*OrderRequest, error)
grpc.ServerStream
}
type orderServiceAddOrderListServer struct {
grpc.ServerStream
}
func (x *orderServiceAddOrderListServer) SendAndClose(m *OrderInfo) error {
return x.ServerStream.SendMsg(m)
}
func (x *orderServiceAddOrderListServer) Recv() (*OrderRequest, error) {
m := new(OrderRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
SendAndClose和Recv方法是客户端流模式下的服务端对象所拥有的方法。
type OrderService_AddOrderListClient interface {
Send(*OrderRequest) error
CloseAndRecv() (*OrderInfo, error)
grpc.ClientStream
}
type orderServiceAddOrderListClient struct {
grpc.ClientStream
}
func (x *orderServiceAddOrderListClient) Send(m *OrderRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *orderServiceAddOrderListClient) CloseAndRecv() (*OrderInfo, error) {
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
m := new(OrderInfo)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
Send和CloseAndRecv是客户端流模式下的客户端对象所拥有的方法。
客户端流模式的服务接口具体实现如下:
//订单服务实现
type OrderServiceImpl struct {
}
//添加订单信息服务实现
func (os *OrderServiceImpl) AddOrderList(stream message.OrderService_AddOrderListServer) error {
fmt.Println(" 客户端流 RPC 模式")
for {
//从流中读取数据信息
orderRequest, err := stream.Recv()
if err == io.EOF {
fmt.Println(" 读取数据结束 ")
result := message.OrderInfo{OrderStatus: " 读取数据结束 "}
return stream.SendAndClose(&result)
}
if err != nil {
fmt.Println(err.Error())
return err
}
//打印接收到的数据
fmt.Println(orderRequest)
}
}
依然是采用相同的服务注册和监听处理方式对服务进行注册和监听处理。
func main() {
server := grpc.NewServer()
//注册
message.RegisterOrderServiceServer(server, new(OrderServiceImpl))
lis, err := net.Listen("tcp", ":8090")
if err != nil {
panic(err.Error())
}
server.Serve(lis)
}
客户端调用send方法流数据到服务端,具体实现如下:
...
//调用服务方法
addOrderListClient, err := orderServiceClient.AddOrderList(context.Background())
if err != nil {
panic(err.Error())
}
//调用方法发送流数据
for _, info := range orderMap {
err = addOrderListClient.Send(&info)
if err != nil {
panic(err.Error())
}
}
for {
orderInfo, err := addOrderListClient.CloseAndRecv()
if err == io.EOF {
fmt.Println(" 读取数据结束了 ")
return
}
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(orderInfo.GetOrderStatus())
}
运行案例,程序输出如下:
客户端流 RPC 模式
201907300001 衣服 已付款
201907310001 零食 已付款
201907310002 食品 未付款
读取数据结束
客户端流 RPC 模式
201907300001 衣服 已付款
201907310001 零食 已付款
201907310002 食品 未付款
读取数据结束
客户端运行程序输出如下:
客户端请求RPC调用:客户端流模式
读取数据结束
读取数据结束了
上文已经讲过了服务端流模式和客户端流模式。如果将客户端和服务端两种流模式结合起来,就是第三种模式,双向流模式。即客户端发送数据的时候以流数据发送,服务端返回数据也以流的形式进行发送,因此称之为双向流模式。
//订单服务service定义
service OrderService {
rpc GetOrderInfos (stream OrderRequest) returns (stream OrderInfo) {}; //双向流模式
}
type OrderService_GetOrderInfosServer interface {
Send(*OrderInfo) error
Recv() (*OrderRequest, error)
grpc.ServerStream
}
type orderServiceGetOrderInfosServer struct {
grpc.ServerStream
}
func (x *orderServiceGetOrderInfosServer) Send(m *OrderInfo) error {
return x.ServerStream.SendMsg(m)
}
func (x *orderServiceGetOrderInfosServer) Recv() (*OrderRequest, error) {
m := new(OrderRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
type OrderService_GetOrderInfosClient interface {
Send(*OrderRequest) error
Recv() (*OrderInfo, error)
grpc.ClientStream
}
type orderServiceGetOrderInfosClient struct {
grpc.ClientStream
}
func (x *orderServiceGetOrderInfosClient) Send(m *OrderRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *orderServiceGetOrderInfosClient) Recv() (*OrderInfo, error) {
m := new(OrderInfo)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}