最近我拜读了mindwind的一片博客文章深入浅出 RPC - 深入篇,希望通过Dubbo深入学习RPC架构设计,在此结合RPC架构的原理,解析Dubbo是如何实现RPC架构的。
RPC架构模型
RPC架构的主要目的是在构建分布式系统时,调用远程方法就如同调用本地方法一样方便快捷,简化开发,提高效率。
我们看看下面这张图,了解一下RPC架构的主要组成部分及调用关系:
以上图片引自mindfloating的博客
上图左侧是调用者,右侧是方法提供端。我们分别解释一下上图的各模块的职责:
从左侧开始,Caller是调用者;RpcClient是Rpc协议定义的客户端,包括远程接口的地址列表及调用方式的信息等,Caller导入RpcClient;RemoteAPI表示远程接口;RpcProxy是一个对远程方法调用的代理组件,封装了远程过程调用的细节,可以包括加载地址列表,寻址,调用集群模块实现负载均衡、高可用,查找调用信息,调用RpcInvoker等;RpcInvoker是具体的RemoteAPI的调用者封装对象,它包含了一个具体接口方法的地址、调用方式等信息,并提供了调用处理方法;RpcConnector负责封装实现网络通信细节,如建立会话通道,发送数据请求和接收返回值数据;RpcProtocol是Rpc协议规范,定义了数据序列化和反序列化方法,如何解析地址信息等细节。
右侧为接口服务端,RpcAcceptor类似RpcConnector,封装实现网络通信细节,接收请求数据,发送本地接口处理后的返回值给客户端;RpcProcessor负责线程池管理,发起本地方法调用等;服务端RpcInvoker是本地API的调用者,通过反射技术调用指定接口的方法;Callee是服务端的本地接口类,导出RPC服务为RpcServer,客户端Caller将其作为RpcClient导入.
我们按照以上RPC的调用流程和各组件职责,聊一聊Dubbo是如何实现的。
Dubbo的RPC架构实现
Dubbo的调用关系如下图所示,以注册中心Registry为中心,Provider即服务提供者,将服务export出来注册(1、register)存储到Registry,Consumer即客户端调用者,从Registry订阅所关心的服务(2、subscribe),首次订阅时拉取订阅服务所有的地址列表(服务提供者注册到Registry上的信息),当订阅服务有更新(地址变更或有新的地址注册加入),Consumer收到注册中心的服务更新通知(3、notify),以上是服务端export的过程。
客户端发起RPC调用时,首先从服务地址列表里refer()指定的服务,这其中有服务寻址和从集群中筛选最终指向一个服务的过程,这就是import的过程。得到某个服务指向,则发起远程调用(4、invoke)。
以上图片引自Dubbo官方用户手册
那么Dubbo具体是如何实现RPC的整个过程的呢?对应RPC模型,Dubbo相应的组件又是如何实现和交互的?下图展示了Dubbo整个设计思路。
以上图片引自Dubbo官方用户手册
图例说明:
- 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
- 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
- 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
- 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
接下来我们对应RPC架构模型的组件谈谈Dubbo的设计。
export和import
Dubbo服务端接口export(导出)是将接口信息注册到注册中心Registry的过程。而客户端import(导入)远程接口是通过从注册中心Registry订阅远程服务接口,收到通知后拉取到本地的过程。
注册和订阅的过程,不需要修改服务端本地的类和方法,只需保证客户端和服务端共同引用一个包含接口的jar包。服务端和客户端分别编写简单的dubbo接口配置xml文件(或注解的方式),容器启动时就自动注册和订阅了。
客户端调用的过程
看上图Config层的橙色小圆点,红色实线剪头为调用链。客户端invoke远程API(Interface),实际是调用了Proxy层的RpcProxy,proxy又调用集群组件Cluster,从集群中筛选出一个Invoker作为调用者发起调用。
我们看到,在Cluster层中筛选的过程调用了Directory(实现类为RegistryDirectory)、Router、LoadBalance,分别通过Directory实现了服务的高可用,通过Router实现了智能路由功能,通过LoadBalance实现了负载均衡。
从集群模块中筛选出一个Invoker后执行invoke()方法执行方法调用,到了Protocol协议层,经过Filter组件做拦截过滤处理,如用户名、密码验证等可在此处理。过滤通过后,调用Protocol实现类如DubboProtocol或HessianProtocol等的invoke()方法。
具体的协议实现类(如DubboProtocol)会请求ExchangeClient组件,它封装了具体的数据通讯细节,是底层数据通信的代理层。因此它自然会调用底层的通信组件(默认是Netty)实现Client建立连接、Server绑定端口和数据传输(request、return)的功能。
数据传输前需要数据序列化,服务端接收到数据需要反序列化,这些都靠序列化组件实现。Codec是序列化组件的代理层,具体序列化协议,默认是Hessian,还可选择Kryo,Thrift(被Dubbo改造,与原Thrift不兼容),dubbo, hessian2, java, json等,具体参见Dubbo用户手册。
服务端调用
服务端接收到客户端的请求后,反序列化数据,通过DubboHandler协议处理请求,找到注册的本地Exporter,触发invoke(),经过过滤器处理后,调用Invoker代理层,触发真正的本地接口调用,返回数据序列化后发送给客户端。
以上内容以RPC模型为基础,分析总结了Dubbo实现RPC的大体流程,后续我还会分别针对某些组件展开讨论Dubbo的设计实现。