前言
本文试图通过手撕RPC的理论步骤来帮助我们更好的理解其特性,也更好的理解像Dubbo,sofa-RPC等RPC服务的架构。
什么是RPC
RPC(Remote Procedure Call)远程过程调用,现在因为微服务概念的火爆又叫远程服务调用。再翻译一下就是允许像调用本地服务一样调用远程服务。比如说两个应用,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。那这么看的话其实一个http请求就已经是一个RPC调用了,我们为什么还要RPC服务呢?因为我们想达到的效果是像调用本地服务一样调用远程服务。就像下面的例子一样。
B_Interface bInterface;
A_method(){
String arg = "hello roc";
...
String response = bInterface.testRpc(arg);
sout(response);
...
}
那要实现上面的形式要分几步呢?我们分别从被调用方,调用方来大体说下,并做一些扩展思考。
从被调用方(provider)来说
被调用方作为服务提供者要做的内容大体上是下面的内容:
- 暴露provider提供服务的地址列表
- 暴露链接方式和编解码协议(http,socket和bolt,Protocolbuffer,json,xml等)
- 暴露provider提供的服务接口
- 暴露相关自定义参数结构体
- 暴露提供负载均衡、路由规则等内容的自定义策略
- 当暴露的内容有变化的时候需要通知给服务注册中心
- 自身提供容错、重试等服务
很明显的是需要暴露的内容需要提交到一个可以供provider和consumer共同访问的地方,比如说文件、redis,ZooKeeper等。就是服务注册中心的内容了。provider在启动后**(start)可以提供服务的时候将需要暴露的内容注册(register)到注册中心,当提供内容或状态有变化的时候再通知(notify)**注册中心,这样consumer就可以实时的从注册中心拿到相关的数据进行调用。
而provider自身提供的一些策略是和自身的服务业务紧密相关的,比如说根据特定时间段来做负载均衡,根据特定地区来做路由规则等。很明显也不太应该交给consumer端来做
从调用方(consumer)来说
调用方作为服务的消费者需要能够做到下面这些内容
- 找到**(subscribe)**provider提供服务的地址
- 根据提供的链接方式和编解码协议来选择自己合适的
- 找到服务接口并生成一个本地代理对象(Proxy)
- 通过服务接口的Proxy对象来Invoke我们传入的各种参数,并得到返回值。
为什么我们需要用Proxy来生成一个服务接口的对象呢?在Java语言中,我们不能用接口来实例化一个对象呀,而Provider不可能把实现类暴露给Consumer呀,所以只能用Proxy、从cglib的方式来生成一个该接口的代理对象。然后我们就可以在Consumer端来使用它,就像使用本地对象方法一样,而实际上该接口的代理类是去调用了远程的服务。
那很明显我们需要将Proxy的Invoke这一步封装起来,包括查找服务提供地址、协议、参数类型列表、参数列表等。大体步骤为
- 找到体用的地址列表
- 然后根据路由规则筛选出来可用的地址列表子集
- 然后再根据负载均衡策略选中一个地址
- 根据协议,参数类型和参数列表组织一个请求数据包
- 将数据包发送给筛选出来的地址,并拿到回执,解析为return的对象。
扩展思考
框架能完成上面的内容,一个RPC服务我们基本就完成了,那如果需要扩展的话,我们可以继续考虑以下内容:
- 各个环节中有业务方需要自己配置的内容怎么办?
- 有些环节在框架提供的配置规则内,业务方不能满足需要,需要自己写类实现怎么加载?
- 调用中发生错误以后怎么容错或者重试?
- 如果我的服务是A调用B,B再调用C,我中间需要一些业务无关的上下文信息呢?
- 一个注册中心挂了怎么办?
- 如果需要统计provider的服务接口调用次数**(count)**,耗时信息,失败信息呢?
- 要查看**(Monitor)**各接口有多少Provider以及他们的服务状态呢?
- 其他
dubbo架构图
带着上面Provider,Consumer和扩展思考的内容我们再来看下Dubbo的架构图,是不是异常清晰了呢?如果还不清楚的话拿着0->5步的单词作为关键字再看下本文。
|