前言
RPC到底是什么?
gRPC又是什么?
与HTTP直接存在什么关系?
本文将讨论一下RPC相关的概念并以Python中自带的xmlrpc为例,简单剖析源码,理解它的实现原理,理解后,自己也可以轻松实现一个玩具RPC框架。
RPC概念
RPC简单定义:
RPC(Remote Procedure Call)– 远程过程调用,通过「网络通信」调用不同的服务,共同支撑一个软件系统,是分布式系统中的基石技术。
阅读完RPC定义,我内心疑惑是:
1.服务之间通过API的方式走HTTP不也可以实现通过网络通信调用不同的服务的目的?
2.它与HTTP有啥差别?
3.相比于API调用的方式有何优势?
首先,RPC 是一种技术思想而非一种规范或协议,RPC可以基于HTTP来实现,也可以基于其他方式,比如常见的TCP或自己定义协议通过Socker来实现。
gRPC是Google知名的RPC框架,RPC框架是对RPC技术思想的现实实现,类似于idea和相应idea的project的关系,gRPC本身是构建于HTTP2.0上的,使用Google的Protobuf协议来传输信息。
那相比与API调用,它有什么优势?
系统内部API调用在系统间交互较少,接口不多的情况下,确实是一种有效的通信手段,它实现简单、直接,但一个大系统,子系统的交互可能很多,如果要使用API,就需要定义非常多的接口,难以维护。
此外,使用RPC框架后,远程调用会与本地调用一样简单,底层网络传输的过程对用户而言是透明的。RPC框架会自动进行数据序列化、协议编码和网络传输等过程。
题外话:很多介绍RPC的博文会聊到系统间API调用走HTTP要进行3次握手、4次挥手,请求量大起来是很耗资源的,而RPC框架调用会维持长连接,从而减少网络开销。
这其实存在概念性错误,HTTP协议可以维持长连接,只需要在包头添加上Connection:keep-alive
,HTTP/1.0默认使用短连接,但HTTP/1.1起,默认使用的就是长连接了,HTTP是应用层协议,它的长连接和短连接,实质上是 TCP 协议的长连接和短连接。
gRPC使用HTTP/2.0默认就会使用长连接来传输数据。
常见RPC框架
- gPRC:Google开源的RPC框架,基于HTTP/2.0实现,可支持常见的多种语言,底层使用了Netty框架。
- Thrift:Facebook开源的RPC框架,一个跨语言服务开发框架。
- Dubbo:阿里巴巴开源的RPC框架,协议和序列化框架都可以插拔。
RPC框架常用的通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)
通信框架:MINA 和 Netty
RPC核心功能
知名开源RPC框架代码比较复杂,具有很多功能,但RPC最核心的功能非常简单,所以这里去繁从简,只了解RPC框架最核心的功能,然后通过Python自带的xmlrpc来学习一下RPC代码层面的实现。
一个RPC框架其核心功能可以分成5个主要部分,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等,之间的关系如下图。
- 客户端(Client):服务调用方。
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
- 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
- 服务端(Server):服务的真正提供者。
- Network Service:底层传输,可以是 TCP 或 HTTP。
通过Python的xmlrpc简单使用一下,加深对上面概念的理解。
首先构建一个Server端。
from xmlrpc.server import SimpleXMLRPCServer
def is_even(n):
return n % 2 == 0
server = SimpleXMLRPCServer(('127.0.0.1', 5000))
print('Listening on port 5000....')
server.register_function(is_even, 'is_even') # 注册服务
server.serve_forever()
上述代码中,使用了SimpleXMLRPCServer类构建了RPC Server实例,然后将Server端可以被远程调用的方法通过register_function方法进行注册,这里将is_even方法注册,客户端可以无感调用is_even方法,最后调用serve_forever方法让Server端一直轮训监听5000端口。
除了使用register_function方法注册方法,你还可以通过register_instance方法注册类实例,此时该类下的所有方法都可以被远程Client端调用,更多用法参考文档。
接着构建一个Client端。
import xmlrpc.client
with xmlrpc.client.ServerProxy('http://127.0.0.1:5000/') as proxy:
res1 = proxy.is_even(3) # 调用服务端的is_even方法
print('res1: ', res1)
res2 = proxy.is_even(100)
print('res2: ', res2)
Cliet端的代码更简洁,通过with上下文机制管理 xmlrpc.client.ServerProxy,然后直接调用Server端的方法,看上去很神奇。
仔细观察Server端的输出,可以发现Client端的两次调用,其实就是两次POST请求,走的是HTTP/1.1,这说明Python的xmlrpc基于HTTP/1.1作为网络传输协议。
xmlrpc源码剖析
Client端如同调用本地方法的形式实现对Server端方法的调用,这效果非常棒,那它是怎么实现的呢?我自己可不可以写一个呢?
剖析源码前,要思考一下从哪里入手,其实从Client端看更易理解(我一开始看Server端,绕了半天…)。
仔细观察下面的代码。
with xmlrpc.client.ServerProxy('http://127.0.0.1:5000/') as proxy:
res1 = proxy.is_even(3) # 调用服务端的is_even方法
print('res1: ', res1)
res2 = proxy.is_even