聊聊RPC

作者: 今年五年级 | 来源:发表于2021-11-03 18:11 被阅读0次

    1. RPC特征

    RPC:remote procedure call 远程过程调用
    即两台服务器A,B,一个应用部署在A服务器上,另一个应用部署在B服务器上,A服务想要调用B服务提供的方法,则

    1. call id映射:我们怎么告诉远程机器我们要调用哪个方法,而不是其他方法?在本地调用中,函数体直接通过函数指针指定,我们调用queryById(int id)方法,编译器自动帮我们调用它对应的函数指针。但是在远程调用中,函数指针不行的,因为两个进程地址空间完全不一样,所以,在RPC中,所有的函数都必须有自己的一个id,这个id在所有进程中都是唯一确定的。客户端在做远程调用时,必须附上该id。我们还需要在A服务和B服务分别维护一个(函数和call id)的映射表,两者的表不一定要完全相同,但是相同的函数对应的call id必须相同。当A服务即客户端需要远程调用时,它就查一下该表,找出对应的call id,然后把它传给B服务即服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行函数代码
    2. 序列化和反序列化:A服务需要把本地参数传给B服务远程函数,本地调用的过程中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程调用过程中,A服务跟B服务是不同的进程,不是一个内存空间,不能通过内存来传递参数,因此需要A服务将参数转化为字节流传给B服务,然后B服务将字节流转化为自身能读取的格式,是一个序列化和反序列化的过程
      序列化可以简单理解为对象 –> 字节的过程,同理,反序列化则是相反的过程。这一过程的目的可以理解为转义,然后方便传输。常见的序列化方式有 JDK 自带序列化(类实现Serializable 接口),Hessian 序列化,Kryo 序列化Protobuf 序列化等
    3. 网络传输:数据准备好以后,传输通过:网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。

    2. RPC具体过程

    RPC的设计由Client,Client stub,Network ,Server stub,Server构成。 其中Client就是用来调用服务的,Cient stub是用来把调用的方法和参数序列化的(因为要在网络中传输,必须要把对象转变成字节),Network用来传输这些信息到Server stub, Server stub用来把这些信息反序列化的,Server就是服务的提供者,最终调用的就是Server提供的方法

    1. Client像调用本地服务似的调用远程服务;
    2. Client stub接收到调用后,将方法、参数序列化
    3. 客户端通过sockets将消息发送到服务端
    4. Server stub 收到消息后进行解码(将消息对象反序列化)
    5. Server stub 根据解码结果调用本地的服务
    6. 本地服务执行(对于服务端来说是本地执行)并将结果返回给Server stub
    7. Server stub将返回结果打包成消息(将结果消息对象序列化)
    8. 服务端通过sockets将消息发送到客户端
    9. Client stub接收到结果消息,并进行解码(将结果消息反序列化)
      客户端得到最终结果

    // Client端
    // int l_times_r = Call(ServerAddr, Multiply, lvalue, rvalue)

    1. 将这个调用映射为Call ID。这里假设用最简单的字符串当Call ID的方法
    2. 将Call ID,lvalue和rvalue序列化。可以直接将它们的值以二进制形式打包
    3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
    4. 等待服务器返回结果
    5. 如果服务器调用成功,那么就将结果反序列化,并赋给l_times_r

    // Server端

    1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用std::map<std::string, std::function<>>
    2. 等待请求
    3. 得到一个请求后,将其数据包反序列化,得到Call ID
    4. 通过在call_id_map中查找,得到相应的函数指针
    5. 将lvalue和rvalue反序列化后,在本地调用Multiply函数,得到结果
    6. 将结果序列化后通过网络返回给Client

    3. RPC代码示例

    https://gitee.com/ctfen_z/rpc
    这里客户端只需要知道服务端的接口userService即可,通过动态代理隐藏了实现细节,服务端执行的时候,会根据注册中心找到具体实现类userServiceImpl,然后调用方法返回结果

    4. 常用RPC框架

    4.1 gRPC

    gRPC是google开源的一个高性能、跨语言的rpc框架,基于最新的HTTP2.0协议(HTTP2.0是基于二进制的HTTP协议升级版本),基于protobuf3.x,还使用到了Netty框架的支持

    客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息

    流程:

    1. 需要使用protobuf定义接口,即.proto文件
    2. 使用compile工具生成特定语言的执行代码,比如java c/c++、python等,为了解决跨语言问题
    3. 启动一个server端,server端通过监听指定端口,来等待client连接请求,通常使用netty来构建,grpc内置了netty的支持
    4. 启动一个或多个client端,client也是基于netty,client通过与server建立TCP长连接,并发送请求,Request与Response均被封装成HTTP2的stream Frame,通过Netty Channel进行交互
    4.2 Dubbo

    Dubbo是阿里开源的一个高性能优秀的Java RPC框架,可以和Spring无缝集成,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现

    5. RPC和HTTP

    这里的RPC服务一般是指基于TCP/IP协议上开发的二进制协议服务
    Http服务一般是指基于Http1.0 / Http1.1协议上开发的REST服务(注意:不包含HTTP2.0)

    • 序列化方式:
      RPC服务序列化是针对二进制协议(0/1)来做序列化和反序列化,所以性能高
      而Http服务是基于文本的序列化和反序列化,需要读一行一行的文本(比如json格式的),进行序列化和反序列化,所以性能低。
    • 报文长度:
      RPC服务是自定义的传输协议,传输的报文都是干货
      而Http服务里面包括很多没用的报文内容,比如Http Header里面的accept,referer等等
    • 连接的复用:
      RPC服务是基于TCP/IP协议的,是长连接
      而Http服务大都是短连接,虽然Http1.1支持长连接,但是这个也是要取决于服务端是否支持长连接,不太可控

    http 和 rpc 并不是一个并行概念
    rpc是远端过程调用,其调用协议通常包含传输协议和序列化协议
    传输协议包含: 如著名的 gRPC使用的 http2.0 协议,也有如dubbo一类的自定义报文的tcp协议
    序列化协议包含: 如基于文本编码的 xml json,也有二进制编码的 protobuf hessian等
    常用的http客户端

    谁能用通俗的语言解释一下什么是 RPC 框架? - 慕课网的回答 - 知乎
    https://www.zhihu.com/question/25536695/answer/2283227217
    https://www.jianshu.com/p/7ac2b521e41b

    相关文章

      网友评论

          本文标题:聊聊RPC

          本文链接:https://www.haomeiwen.com/subject/wxdczltx.html