文章插图
图 7:基于 HTTP 协议的 RPC 连接过程
详细调用过程
Python 自带 RPC 的 Demo 小程序的实现过程,流程和分工角色可以用下图来表示:
文章插图
图 8:RPC 调用详细流程图
一次 RPC 调用流程如下:
- 服务消费者(Client 客户端)通过本地调用的方式调用服务 。
- 客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体 。
- 客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端 。
- 服务端存根(Server Stub)收到消息后进行解码(反序列化操作) 。
- 服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理
- 服务端(Server)本地服务业务处理 。
- 处理结果返回给服务端存根(Server Stub) 。
- 服务端存根(Server Stub)序列化结果 。
- 服务端存根(Server Stub)将结果通过网络发送至消费方 。
- 客户端存根(Client Stub)接收到消息,并进行解码(反序列化) 。
- 服务消费方得到最终结果 。
RPC 核心之功能实现
RPC 的核心功能主要由 5 个模块组成,如果想要自己实现一个 RPC,最简单的方式要实现三个技术点,分别是:
- 服务寻址
- 数据流的序列化和反序列化
- 网络传输
服务寻址可以使用 Call ID 映射 。在本地调用中,函数体是直接通过函数指针来指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的 。
所以在 RPC 中,所有的函数都必须有自己的一个 ID 。这个 ID 在所有进程中都是唯一确定的 。
客户端在做远程过程调用时,必须附上这个 ID 。然后我们还需要在客户端和服务端分别维护一个函数和Call ID的对应表 。
当客户端需要进行远程调用时,它就查一下这个表,找出相应的 Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码 。
实现方式:服务注册中心 。
要调用服务,首先你需要一个服务注册中心去查询对方服务都有哪些实例 。Dubbo 的服务注册中心是可以配置的,官方推荐使用 Zookeeper 。
实现案例:RMI(Remote Method Invocation,远程方法调用)也就是 RPC 本身的实现方式 。
文章插图
图 9:RMI 架构图
Registry(服务发现):借助 JNDI 发布并调用了 RMI 服务 。实际上,JNDI 就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象 。
RMI 服务在服务端实现之后需要注册到 RMI Server 上,然后客户端从指定的 RMI 地址上 Lookup 服务,调用该服务对应的方法即可完成远程方法调用 。
Registry 是个很重要的功能,当服务端开发完服务之后,要对外暴露,如果没有服务注册,则客户端是无从调用的,即使服务端的服务就在那里 。
序列化和反序列化
客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行 。
但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数 。
这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式 。
只有二进制数据才能在网络中传输,序列化和反序列化的定义是:
- 将对象转换成二进制流的过程叫做序列化
- 将二进制流转换成对象的过程叫做反序列化
网络传输
网络传输:远程调用往往用在网络上,客户端和服务端是通过网络连接的 。
所有的数据都需要通过网络传输,因此就需要有一个网络传输层 。网络传输层需要把 Call ID 和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端 。
只要能完成这两者的,都可以作为传输层使用 。因此,它所使用的协议其实是不限的,能完成传输就行 。
尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也可以,而 gRPC 干脆就用了 HTTP2 。
推荐阅读
- 程序员=青春饭”?不,程序员是一个具备长久生命力的职业
- 中药枕头哪种好
- |您以为钓鱼是“娱乐”?有时候它还是一个“悲伤”的故事
- 梦见收养了一个孩子表示什么 做梦梦见收养了一个女儿
- 红薯腊八粥
- 梦见一个钩上有很多鱼 梦见钩了很多鱼
- 在一个千万级的数据库查寻中,如何提高查询效率?
- 一个男人一旦提出离婚 男人比女人更害怕离婚
- 松茸蒸蛋羹
- 睡了一个比自己小9岁的男人靠谱吗,姐弟恋对女人生理好不好