Dubbo和Motan
这个Rpc博客系列的重点主要是记录我自己从零开发一款Rpc框架的过程,所以不会详细去介绍市面上已有的框架,但是在我们正式开始之前,还是不得不介绍一下市面上有代表性的Rpc框架,这里选了两款,dubbo和motan,dubbo是阿里研发的,已经捐献给了Apache;motan是微博的于2016年开源。
先说一下Rpc框架的分类,大致可以分为两类,这两类代表了两个不同的发展方向:
- 服务治理型
- 跨语言型
dubbo和motan框架都属于服务治理型框架,在具备基础的Rpc调用功能之上,提供了集群容错、路由、负载均衡、服务注册发现等具有集群治理属性的功能。而跨语言型Rpc框架,顾名思义是为了不同语言之间的服务能够互相调用彼此的服务,比较有名的有ICE,Thrift等,这些服务一般提供一种更加抽象的描述语言,这种语言用来抽象一个服务接口,然后根据不同的语言类型转化为不同的具体代码,这种框架不是我们要讨论的重点。
目前Java开发的Rpc框架更加侧重于服务治理型,这里再说一下Spring Cloud,Spring Cloud实际上属于微服务框架,Rpc只是Spring Cloud提供的其中一个功能,所以Spring Cloud是Rpc框架的更加先进的版本,但是Spring Cloud太庞大,其中集成的框架过于繁多,而dubbo和motan属于轻量型框架。
dubbo和motan本身非常的轻巧,dubbo和motan分别只含有13.6W行和3.5W行代码(包含测试代码),要知道SpringFramework有60W行代码。dubbo的代码稍多一下,因为dubbo对于不同的层提供了不同的实现,举个例子,dubbo在服务注册发现层,提供了consul、redis、default、multicast、nacos、sofa、zookeeper、etcd3这n种实现,在实际使用过程中,只会用得着其中一个,并且里面有些实现并没与太大的用处,可能仅供学习,比如没人能拿redis去做服务注册。相对来说motan的实现就轻巧很多,服务注册只支持了zookeeper和consul这两种最常用的框架。
两个框架的区别
- dubbo在代理层使用了javassist框架作为代理生成器,javassist框架可以动态编译加载代码,这使得有些地方看起来十分容易让人疑惑,motan更加直接的使用了Java原生的动态代理,看起来更加亲切一些。
- dubbo对于很多调用都抽象成了Invoke这个接口,然后这个接口上通常都会包一层Proxy,有的时候看起来给人感觉有点强行设计的感觉,没那么明了,motan的调用更加清晰一些。
- dubbo提供了配置中心,而motan没有。
- dubbo提供了大而全的各层的实现,motan只提供了最常用的实现。
- 一些细节的实现不同,比如dubbo过期使用了哈希轮转算法(代码是复制的netty的代码),motan使用了最朴素的实现方式。
- dubbo提供了更丰富的请求重发策略。
- dubbo提供了monitor模块。
- motan使用了连接池,dubbo的dubbo协议使用单一链接。
两个框架的相同点:
看过源码就会发现,motan简直就是dubbo的精缩版,两个框架不仅在架构分层上惊人的相似,就连很多类的命名都一样,两者的设计几乎如出一辙。
dubbo:
- config 配置层:对外配置接口,以
ServiceConfig
,ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类 - proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以
ServiceProxy
为中心,扩展接口为ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为
RegistryFactory
,Registry
,RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以
Invoker
为中心,扩展接口为Cluster
,Directory
,Router
,LoadBalance
- monitor 监控层:RPC 调用次数和调用时间监控,以
Statistics
为中心,扩展接口为MonitorFactory
,Monitor
,MonitorService
- protocol 远程调用层:封装 RPC 调用,以
Invocation
,Result
为中心,扩展接口为Protocol
,Invoker
,Exporter
- exchange 信息交换层:封装请求响应模式,同步转异步,以
Request
,Response
为中心,扩展接口为Exchanger
,ExchangeChannel
,ExchangeClient
,ExchangeServer
- transport 网络传输层:抽象 mina 和 netty 为统一接口,以
Message
为中心,扩展接口为Channel
,Transporter
,Client
,Server
,Codec
- serialize 数据序列化层:可复用的一些工具,扩展接口为
Serialization
,ObjectInput
,ObjectOutput
,ThreadPool
[来源]:dubbo框架设计
motan相对应的层:
- config 几乎一样
- proxy 几乎一样,使用的Java动态代理而不是javassist
- registry 几乎一样,支持了zookeeper和consul
- cluster 没有Directory和Router,提供了
Cluster
和LoadBalance
接口 - 没有moniter,但是提供了
Switcher
,可以实现简单的熔断功能 - protocol 使用了motan协议
- 没有exchange,但提供了rpc层,用来封装底层的通信
- transport 几乎一样,提供了netty和netty4的实现
- serialize 几乎一样,提供了fastjson、Hessian2和Java原生实现
在其他设计方面,两者都是用自定义的URL作为整个通信的总线,都是用SPI作为扩展接口,并都实现了ExtensionLoader,两者底层都使用netty作为传输层,当然dubbo支持其他的协议例如http等,两者都没有写什么代码注释,两者相似的地方还有很多很多,不一一列举了。
这里有关于motan更加详细的介绍:从motan看RPC框架设计
关于两个框架更加详细的内容就不再继续介绍了,以后可能会专门写系列博客分析他们的代码,但是不是本系列的重点。
比较的目的
我们比较了半天这两个框架,主要的目的是为了学习和弄明白一个Rpc框架需要哪些层次结构,或者说,一次Rpc调用需要经过哪些步骤,弄清楚了这些,一个Rpc的蓝图就已经在脑海里面了,之后要做的事情就是使用自己编程技巧将自己理解的Rpc框架写出来。
在开始敲代码之前,我们看看一次Rpc调用要经过哪些具体的步骤,一个Rpc框架需要分为Client端和Server端两边来看:Client端需要拿到接口的引用,Server端需要给Client暴露服务:
1.Client端要拿到引用
例如我们有一个接口:
1 | public interface IService { |
那我们的Client端的代码希望是这样的(具体的步骤我们写在代码注释里面了):
1 | public class ClientTest { |
Client端是没有IService
的具体实现的,具体实现在Server端,所以可以想象的到,在调用IService service = reference.getRefer();
时,我们返回的应该是IService
的一个动态代理对象,这个对象里面封装了调用方法时具体执行的步骤,在调用String result = service.say("zrj");
方法时,获取这次方法调用的各种参数、具体的方法信息等元数据,然后将这些元数据封装成一个请求,最后将这个请求序列化为byte数组,然后调用传输层去连接Server,并将请求发送给Server。最后Server返回一个封装后的结果,Client端收到这个结果后,获取结果并返回。
2.Server端要暴露服务
在Client端调用之前,服务端必须先暴露自己的服务,我们希望服务端这样暴露服务:
1 | public class ServerTest { |
Server端需要根据Client端传过来的参数确定需要调用哪个服务的哪个方法,所以当我们调用exporter.setInterfaceClazz(IService.class);
时,就使用反射的方法,找到这个接口中所有的方法签名,并缓存在Map中。当我们调用export的时候,实际上底层肯定是开启了一个端口去监听网络,如果收到了网络请求,那么就先反序列化,然后从请求中拿到需要调用的方法的类和名称,以及方法参数的值,然后去调用本地IServiceImpl
中的具体方法,得到结果封装成一个类,然后序列化后通过传输层放回。
这就是Client端和Server端需要做的最基本的工作了。
可以看到,我们至少需要四个层次:Exporter&Reference是最上层的Config层,Proxy代理层用来代理接口并封装底层的网络传输,Transport层用来封装网络传输,Serialization用来封装序列化和反序列化的相关操作。
3.协议设计
当我们在传输数据之前,需要写入额外的信息到数据包中,也就是所谓的协议头,这个协议头需要我们自己来定义。协议头中放入了一些基本的协议信息,例如MagicNumber、协议版本、传输数据类型、数据长度、请求Id等。之后会看到我自己设计的网络协议。
总结:
最终我按照这条思路进行了简单的实现,下章会具体讲解。
通过看dubbo和motan两个框架的源码,我们弄懂了一个Rpc框架的内部原理,并对基本功能做了一个简单梳理。但是我们还差很多很多的功能以及面临很多的问题:
- 请求重发怎么做
- 请求失败后的策略怎么抽象
- Cluster怎么封装,负载均衡怎么做
- 在Transport层一个Client对一个Server是一个连接还是多个连接,是长连接还是短连接
- 各种设置如何从最上层传递到最下层,不同层的设置怎么区分
- 缓存怎么做
- 异步怎么做
- 服务注册发现怎么做
- 生命周期如何管理
这些问题都会在之后的博客中一步一步解决。
GitHub项目地址:susu