阿里巴巴dubbo原理分析

学习 Dubbo 也有很长一段时间了,抽空整理了下学习笔记。主要有 4 个部分:

1. service发布过程

2. consumer启动过程

3. consumer请求过程

4. service响应过程

本篇是开篇,内容相对来说会多一点,因为需要介绍相关联的一些技术点。好了,废话不多说,先列出大纲:

1. RPC 原理

2. Spring 集成

3. 服务发布过程

4. SPI 原理

5. filter 机制

RPC 原理

RPC(Remote Procedure Call)即远程过程调用。大家工作中应该或多或少都有接触到。举个最简单的例子,机器 A 通过 http 请求机器 B 的一个接口,这实际上也是 RPC,机器 A 和机器 B 通过 http 协议来通信。

那如果不通过 http 协议呢,我们还可以用更底层的 tcp 协议来通信,这个时候就需要自定义业务协议,举个简单的例子: 客户端伪代码如下:

Socket socket = new Socket(host, port);

ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());

output.writeUTF(class.getName());

output.writeUTF(method.getName());

output.writeObject(method.getParameterTypes());

output.writeObject(arguments);  

服务端伪代码如下:

ServerSocket server = new ServerSocket(port);

Socket socket = server.accept();

ObjectInputStream input = new ObjectInputStream(socket.getInputStream());

String className = input.readUTF();

String methodName = input.readUTF();

Class<?>[] parameterTypes = (Class<?>[])input.readObject();

Object[] arguments = (Object[])input.readObject();

遵照相同的协议,服务端就能获取到相关的一些信息,得到类和方法的名字以及参数之后,就可以通过反射来调用,这是最基本的一个过程,后续还可以做很多的优化和扩展,比如:

·  netty 或 mina 等框架来代替直接操作 socket

· 类和方法的实例可以通过缓存来提高反射的性能

· 通过动态代理来实现统一的请求过程以及AOP支持

· 引入 etcd 或 zookeeper 等框架来感知配置的变化

· 引入 hessian、protobuf 或 kryo 等框架来提升序列化/反序列化性能

· 加入负载均衡、服务降级、限流、监控等功能

· ……

我想,Dubbo 当初应该也是这么一步步优化迭代过来的。

Spring 集成

Spring 是非常优秀的框架,扩展非常方便,允许自定义 namespace,首先,得准备自定义 schema 约束 xsd 文件,加到 xml 文件中:

 

然后在 xml 中就可以使用 Dubbo 标签了:

 

Spring 允许通过扩展 NamespaceHandler 接口来实现自定义解析 Namespace:

 

Spring 在解析 BeanDefinition 的时候,如果发现有自定义的 namespace,则会调用自定义的 NamespaceHandler 去解析:

 

服务发布过程

通过 DubboBeanDefinitionParser 解析 Dubbo 自定义标签后,我们就得到了相应的 BeanDefinition 对象,接下来就按照 Spring 正常的流程去生成对象放在 BeanFactory 中,那对象有了,怎么发布出去,让消费端感知到呢?

这块内容比较多,我画了一个主流程的时序图来帮助大家理解。

 

首先,从上面 DubboNamespaceHandler 类的截图中,大家可以看到:service 标签对应的是 ServiceBean.class,它继承自 ServiceConfig.class,很容易发现,在 ServiceConfig 类中有一个 export 的方法,从名字上就可以看出,这就是服务发布的入口。

那么,到底有哪些地方调用了这个方法呢,有两个地方,都在 ServiceBean 类中,一个是 afterPropertiesSet 方法,一个是 onApplicationEvent 方法。

回到 ServiceBean 类中,它除了继承 ServiceConfig 类外,还实现了多个接口:InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware

Spring 比较熟悉的同学们就很好理解了,afterPropertiesSet 方法来自 InitializingBean 接口,Spring 在加载 bean 的时候(populateBean 之后),会调用 initializeBean(包含 invokeInitMethods 方法)进行初始化工作 。

 

onApplicationEvent 方法则来自 ApplicationListener 接口,Spring 在加载 bean 结束之后,会调用 finishRefresh 方法,广播 ContextRefreshedEvent 事件出去。

 

 

从上面的分析可以看出,调用 onApplicationEvent 方法的时机会比 afterPropertiesSet 稍晚点,另外,这两个方法在调用 export 之前,都会调用 isDelay 这个方法。

 

这个 supportedApplicationListener 变量是在 setApplicationContext 方法中赋值的,而 setApplicationContext 则来自 ApplicationContextAware 接口。

从上面的分析可以看出,如果对 service 没有配置 delay 属性,那export过程是从 onApplicationEvent 方法处开始的,反之,则从 afterPropertiesSet 方法处开始。

如果有配置 delay,则会延时加载 doExport 方法,默认的话,立即执行 doExport 方法。

 

下面就不具体展开流程讲了,大家可以通过上面的时序图来了解,主要就 4 个部分:

· 生成 DubboExporter 对象并缓存起来

· 添加过滤器和监听器支持

·  zk 上注册相关信息,暴露服务,方便被感知到

· 监听端口,等待通信的到来

接下来主要介绍这中间用到的 SPI 和 filter 这两个关键技术点

SPI 原理

SPI 是 Dubbo 扩展机制的核心,全称是 Service Provider Interface,JDK 自带就有 SPI 的支持,实现可插拔的服务发现机制,Dubbo 的 SPI 是在 JDK 的基础上做了一些扩展:

· 可根据 key 自由选择加载哪些扩展类,避免一次性实例化所有扩展类

· 加入 IOC 和 AOP 支持

这里提一点,约定大于配置的思想。

Java SPI 和所有实现接口的厂商有一个俗称的约定,只要将 META-INF/services 文件夹下生成一个和抽象类全名称(路径 + 类名称)相同的配置文件,那么厂商的 jar 包只要在工程路径下就能找到实现类。

Dubbo 则多增加了两个目录:

· META-INF/dubbo/internal/

· META-INF/dubbo/

ExtensionLoader 类是 Dubbo SPI 的最最最核心类,看懂这个类,也就大致能明白 Dubbo 的 SPI 机制了。

ExtensionLoader 类定义了几个比较重要的缓存相关成员变量:

· volatile Class<~> cachedAdaptiveClass,这个是缓存 AdaptiveClass,如果一个扩展类的类上面带有 @Adaptive 注解,那么这个类就会被缓存在这个地方,每一种类型的扩展类只有一个 AdaptiveClass,如果发现有多个,则会报错。另外,当通过 getAdaptiveExtensionClass 来获取自适应扩展类时,如果当前还没有 AdaptiveClass,则会自动创建一个(动态生成 Java 代码,再编译,典型的比如 Protocol$Adaptive 就是这么生成的)

· Set<~> cachedWrapperClasses,这个是缓存包装类的,Dubbo 判断一个扩展类是否是包装类比较简单,通过构造函数来判断,如果这个扩展类有一个构造函数,其中参数是当前扩展类的类型,那么就是包装类,举个例子,ProtocolFilterWrapper 就是 protocol 扩展类的包装类,因为有这个构造函数——public ProtocolFilterWrapper(Protocol protocol)

· Map<~> cachedActivates,这个是缓存激活的扩展类,当然,@Activate 注解还可以规定激活的条件和时机

· Holder<~> cachedClasses,这个是缓存 Adaptive 和 Wrapper 扩展类之外的普通扩展类

@Adaptive 注解除了可以用在类上外,更多的是用在方法上,表明这个方法是允许被动态生成的 Adaptive 类去实现的。

接下来就是扩展类的加载了。原理也很简单,拿到一个扩展类的类型后,从上面提到的 3 个约定好的目录中去找对应的文件,如果找到,则一行行解析文件,按照 key=value 的格式,用"="来做分隔,前面的是 name,后面是具体的类名,通过 Class.forName 来生成具体的Class对象,再根据一定的规则放入上面说的 4 个缓存中,比如如果带有 @Adaptive注解,则放入 cachedAdaptiveClass。

另外还有一个有必要说的是 URL,Dubbo 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息,如果 URL 中没有找到相应的配置信息,则会根据注解中的值来获取默认信息,比如 @SPI 注解中的 value。

Filter 机制

Filter(过滤器)在很多框架中都有使用过这个概念,基本上的作用都是类似的,在请求处理前或者处理后做一些通用的逻辑,而且 Filter 可以有多个,支持层层嵌套。Dubbo 的 filter 入口在 ProtocolFilterWrapper 这个包装类中。

 

上图可以看到,如果当前 protocol 不是 registry 的话,则会调用 buildInvokerChain 方法,具体如下:

 

这是比较典型的链式做法,里面比较重要的是获取 Filter 列表的逻辑,具体是 getActivateExtension 这个方法。

这个方法可以分成两个部分,第一个部分是获取系统自动激活的 filter:

 

第二个部分是获取用户自定义的 filter:

 

最后两个部分合起来再返回。

整个设计还是比较巧妙和灵活的,通过简单的 - 可以手动剔除带 @Activate 注解的 Filter,-default 可以剔除所有默认激活的 Filter。

但是,有些地方还是略诡异的,主要在顺序这块。如果某一个 Filter 是带有 @Activate 注解,正常情况下,如果用户没有专门在 filter 中配置,它会在 ext 中,具体的顺序也是照着 ext 中的顺序规则来(@Activate 有 before、after、order 等属性,如果明确配置了 before、after,则根据 before、after 来,如果没有的话,就根据 order 来,如果 order 一样,则后面的会排在前面,默认 order=0, 如果两个类 order 相等,则返回 - 1);但是,如果在 filter 中明确指定了一个 filter,比如 accesslog,那么,这个 accesslog 中的 before、after、order 都不起作用了,默认会在 ext 后面,多个指定 filter 之间的顺序按照输入的顺序来排序;另外如果在 filter 里指明了 default,比如 a,b,default,c,那么,a 和 b 会在 ext 前面,c 在 ext 后面。

好了,洋洋洒洒也写了很多东西,中间可能有一些自己理解不到位的地方,还望不吝赐教!

最后,感谢大家的时间~

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北纬32.6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值