rpc简介及原理

1.RPC简介及原理介绍

RPC技术内部原理是通过两种技术的组合来实现的:本地方法调用 和 网络通信技术。

1.1 RPC简介

在上述本地过程调用的例子中,我们是在一台计算机上执行了计算机上的程序,完成调用。随着计算机技术的发展和需求场景的变化,有时就需要从一台计算机上执行另外一台计算机上的程序的需求,因此后来又发展出来了RPC技术。特别是目前随着互联网技术的快速迭代和发展,用户和需求几乎都是以指数式的方式在高速增长,这个时候绝大多数情况下程序都是部署在多台机器上,就需要在调用其他物理机器上的程序的情况。

RPC是Remote Procedure Call Protocol三个单词首字母的缩写,简称为:RPC,翻译成中文叫远程过程调用协议。所谓远程过程调用,通俗的理解就是可以在本地程序中调用运行在另外一台服务器上的程序的功能方法。这种调用的过程跨越了物理服务器的限制,是在网络中完成的,在调用远端服务器上程序的过程中,本地程序等待返回调用结果,直到远端程序执行完毕,将结果进行返回到本地,最终完成一次完整的调用。

需要强调的是:远程过程调用指的是调用远端服务器上的程序的方法整个过程。

1.2 RPC设计组成

RPC技术在架构设计上有四部分组成,分别是:客户端、客户端存根、服务端、服务端存根。

这里提到了客户端服务端的概念,其属于程序设计架构的一种方式,在现代的计算机软件程序架构设计上,大方向上分为两种方向,分别是:B/S架构C/S架构。B/S架构指的是浏览器到服务器交互的架构方式,另外一种是在计算机上安装一个单独的应用,称之为客户端,与服务器交互的模式。

由于在服务的调用过程中,有一方是发起调用方,另一方是提供服务方。因此,我们把服务发起方称之为客户端,把服务提供方称之为服务端。以下是对RPC的四种角色的解释和说明:

  • **客户端(Client):**服务调用发起方,也称为服务消费者。
  • **客户端存根(Client Stub):**该程序运行在客户端所在的计算机机器上,主要用来存储要调用的服务器的地址,另外,该程序还负责将客户端请求远端服务器程序的数据信息打包成数据包,通过网络发送给服务端Stub程序;其次,还要接收服务端Stub程序发送的调用结果数据包,并解析返回给客户端。
  • **服务端(Server):**远端的计算机机器上运行的程序,其中有客户端要调用的方法。
  • **服务端存根(Server Stub):**接收客户Stub程序通过网络发送的请求消息数据包,并调用服务端中真正的程序功能方法,完成功能调用;其次,将服务端执行调用的结果进行数据处理打包发送给客户端Stub程序。

RPC原理及调用步骤

了解完了RPC技术的组成结构我们来看一下具体是如何实现客户端到服务端的调用的。实际上,如果我们想要在网络中的任意两台计算机上实现远程调用过程,要解决很多问题,比如:

  • 两台物理机器在网络中要建立稳定可靠的通信连接。
  • 两台服务器的通信协议的定义问题,即两台服务器上的程序如何识别对方的请求和返回结果。也就是说两台计算机必须都能够识别对方发来的信息,并且能够识别出其中的请求含义和返回含义,然后才能进行处理。这其实就是通信协议所要完成的工作。

让我们来看看RPC具体是如何解决这些问题的,RPC具体的调用步骤图如下:

img

在上述图中,通过1-10的步骤图解的形式,说明了RPC每一步的调用过程。具体描述为:

  • 1、客户端想要发起一个远程过程调用,首先通过调用本地客户端Stub程序的方式调用想要使用的功能方法名;
  • 2、客户端Stub程序接收到了客户端的功能调用请求,将客户端请求调用的方法名,携带的参数等信息做序列化操作,并打包成数据包。
  • 3、客户端Stub查找到远程服务器程序的IP地址,调用Socket通信协议,通过网络发送给服务端。
  • 4、服务端Stub程序接收到客户端发送的数据包信息,并通过约定好的协议将数据进行反序列化,得到请求的方法名和请求参数等信息。
  • 5、服务端Stub程序准备相关数据,调用本地Server对应的功能方法进行,并传入相应的参数,进行业务处理。
  • 6、服务端程序根据已有业务逻辑执行调用过程,待业务执行结束,将执行结果返回给服务端Stub程序。
  • 7、服务端Stub程序**将程序调用结果按照约定的协议进行序列化,**并通过网络发送回客户端Stub程序。
  • 8、客户端Stub程序接收到服务端Stub发送的返回数据,**对数据进行反序列化操作,**并将调用返回的数据传递给客户端请求发起者。
  • 9、客户端请求发起者得到调用结果,整个RPC调用过程结束。

RPC涉及到的相关技术

通过上文一系列的文字描述和讲解,我们已经了解了RPC的由来和RPC整个调用过程。我们可以看到RPC是一系列操作的集合,其中涉及到很多对数据的操作,以及网络通信。因此,对RPC中涉及到的技术做一个总结和分析:

  • 1、动态代理技术: 上文中我们提到的Client Stub和Sever Stub程序,在具体的编码和开发实践过程中,都是使用动态代理技术自动生成的一段程序。

  • 2、序列化和反序列化: 在RPC调用的过程中,我们可以看到数据需要在一台机器上传输到另外一台机器上。在互联网上,所有的数据都是以字节的形式进行传输的。而我们在编程的过程中,往往都是使用数据对象,因此想要在网络上将数据对象和相关变量进行传输,就需要对数据对象做序列化和反序列化的操作。

    • **序列化:**把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
    • **反序列化:**把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。

我们常见的Json,XML等相关框架都可以对数据做序列化和反序列化编解码操作。同时,在之前的《Go语言微服务理论与实践》课程中,我们已经学习过Protobuf协议,这也是一种数据编解码的协议,在RPC框架中使用的更广泛。

2. grpc 流模式调用

一 服务端流 RPC

在服务端流模式的RPC实现中,服务端得到客户端请求后,处理结束返回一个数据应答流。在发送完所有的客户端请求的应答数据后,服务端的状态详情和可选的跟踪元数据发送给客户端。服务端流RPC实现案例如下:

1.1 服务接口定义

在.proto文件中定义服务接口,使用服务端流模式定义服务接口,如下所示:

...
//订单服务service定义
service OrderService {
    rpc GetOrderInfos (OrderRequest) returns (stream OrderInfo) {}; //服务端流模式
}

我们可以看到与之前简单模式下的数据作为服务接口的参数和返回值不同的是,此处服务接口的返回值使用了stream进行修饰。通过stream修饰的方式表示该接口调用时,服务端会以数据流的形式将数据返回给客户端。

1.2 编译.proto文件,生成pb.go文件

使用gRPC插件编译命令编译.proto文件,编译命令如下:

protoc --go_out=plugins=grpc:. message.proto

1.3 自动生成文件的变化

与数据结构体发送携带数据实现不同的时,流模式下的数据发送和接收使用新的功能方法完成。在自动生成的go代码程序当中,每一个流模式对应的服务接口,都会自动生成对应的单独的client和server程序,以及对应的结构体实现。具体编程如下图所示:

1.3.1 服务端自动生成

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方法,将数据以流的形式进行发送

1.3.2 客户端自动生成

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()方法接收服务端发送的流数据。

1.4 服务编码实现

定义好服务接口并编译生成代码文件后,即可根据规则对定义的服务进行编码实现。具体的服务编码实现如下所示:

//订单服务实现
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方法。

1.5 服务的注册和监听的处理

服务的监听与处理与前文所学内容没有区别,依然是相同的步骤:

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)
}

1.6 客户端数据接收

服务端使用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)
    }
...

1.7 运行结果

按照先后顺序,依次运行server.go文件和client.go文件,可以得到运行结果。

1.7.1 服务端运行结果

服务端流 RPC 模式
订单序列号ID: 201907300001
订单详情: {201907300001 衣服 已付款 {} [] 0}
订单序列号ID: 201907310001
订单详情: {201907310001 零食 已付款 {} [] 0}
订单序列号ID: 201907310002
订单详情: {201907310002 食品 未付款 {} [] 0}

1.7.2 客户端运行结果

客户端请求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" 
读取结束

二 客户端流模式

上文演示的是服务端以数据流的形式返回数据的形式。对应的,也存在客户端以流的形式发送请求数据的形式。

2.1 服务接口的定义

与服务端同理,客户端流模式的RPC服务声明格式,就是使用stream修饰服务接口的接收参数,具体如下所示:

...
//订单服务service定义
service OrderService {
    rpc AddOrderList (stream OrderRequest) returns (OrderInfo) {}; //客户端流模式
}

2.2 编译.proto文件

使用编译命令编译.protow文件。客户端流模式中也会自动生成服务接口的接口。

2.2.1 自动生成的服务流接口实现

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方法是客户端流模式下的服务端对象所拥有的方法。

2.2.2 自动生成的客户端流接口实现

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是客户端流模式下的客户端对象所拥有的方法。

2.3 服务的实现

客户端流模式的服务接口具体实现如下:

//订单服务实现
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)
    }
}

2.4 服务的注册和监听处理

依然是采用相同的服务注册和监听处理方式对服务进行注册和监听处理。

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)
}

2.5 客户端实现

客户端调用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())
    }

2.6 程序运行

2.6.1 服务端

运行案例,程序输出如下:

客户端流 RPC 模式
201907300001 衣服 已付款
201907310001 零食 已付款
201907310002 食品 未付款
 读取数据结束 
 客户端流 RPC 模式
201907300001 衣服 已付款
201907310001 零食 已付款
201907310002 食品 未付款
 读取数据结束

2.6.2 客户端

客户端运行程序输出如下:

客户端请求RPC调用:客户端流模式
 读取数据结束 
 读取数据结束了

三 双向流模式

上文已经讲过了服务端流模式和客户端流模式。如果将客户端和服务端两种流模式结合起来,就是第三种模式,双向流模式。即客户端发送数据的时候以流数据发送,服务端返回数据也以流的形式进行发送,因此称之为双向流模式。

3.1 双向流服务的定义

//订单服务service定义
service OrderService {
    rpc GetOrderInfos (stream OrderRequest) returns (stream OrderInfo) {}; //双向流模式
}

3.2 编译.proto文件

3.2.1 服务端接口实现

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
}

3.2.2 客户端接口实现

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
}

  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值