张伦聪的技术博客 Research And Development

dubbo源码解析

2018-03-15

dubbo工程模块分包

模块说明:

  • dubbo-common 公共逻辑模块:包括 Util 类和通用模型。
  • dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
  • dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
  • dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
  • dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
  • dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
  • dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用D ubbo,隐藏 Dubbo 所有细节。
  • dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。

依赖关系

图例说明:

  • 图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。
  • 图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。
  • 图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。
  • 图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。

调用链

代理

dubbo里面主要用到了三种代理,代理设计模式,jdk代理,javassist代理。如JavassistProxyFactory,JdkProxyFactory类。

代理模式

不再赘述

	public interface Sourceable {  
		public void method();  
 	}

	public class Source implements Sourceable {  
  		@Override  
		public void method() {  
 			System.out.println("the original method!");  
  		}  
 	}

	public Proxy implements Sourceable {  
		private Source source;  
		public Proxy(){  
			super ();  
			this.source = new Source();  
		}  
 		@Override  
 		public void method() {  
			before();  
			source.method();  
			atfer();  
		}  
		private void atfer() {  
	 		System.out.println("after proxy!");  
 		}  
		private	void before() {  
			System.out.println("before proxy!");  
		}  
	 }

 	public class ProxyTest {  
 		public static void main(String[] args) {  
 		Sourceable source = new Proxy();  
 		source.method();  
 		}  
	}

jdk代理

jdk动态代理是由java内部的反射机制来实现的,反射机制在生成类的过程中比较高效。

public interface UserService {  
    public String getName(int id);  
    public Integer getAge(int id);  
} 

public class UserServiceImpl implements UserService {  
    @Override  
    public String getName(int id) {  
        System.out.println("------getName------");  
        return "Tom";  
    }  

    @Override  
    public Integer getAge(int id) {  
        System.out.println("------getAge------");  
        return 10;  
    }  
} 

public class MyInvocationHandler implements InvocationHandler {  
    private Object target;  

    MyInvocationHandler() {  
        super();  
    }  

    MyInvocationHandler(Object target) {  
        super();  
        this.target = target;  
    }  

    @Override  
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {  
        if("getName".equals(method.getName())){  
            System.out.println("++++++before " + method.getName() + "++++++");  
            Object result = method.invoke(target, args);  
            System.out.println("++++++after " + method.getName() + "++++++");  
            return result;  
        }else{  
            Object result = method.invoke(target, args);  
            return result;  
        }  

    }  
}  

public class Main1 {  
    public static void main(String[] args) {  
        UserService userService = new UserServiceImpl();  
        InvocationHandler invocationHandler = new MyInvocationHandler(userService);  
        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),  
                userService.getClass().getInterfaces(), invocationHandler);  
        System.out.println(userServiceProxy.getName(1));  
        System.out.println(userServiceProxy.getAge(1));  
    }  
}

javassist代理

Javassist是一个动态类库,可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。

推荐博文:

javassist入门

javassist学习笔记

dubbo拓展点机制

主要使用了java spi机制。 扩展点机制有几个要点:

  • 根据关键字去读取配置文件,获得具体的实现类; 比如在 dubbo-demo-provider.xml 文件中配置:
<dubbo:service protocol="rmi" interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />

则会根据 rmi 去读取具体的协议实现类 RmiProtocol.java;

  • 注解@SPI 和@Adaptive
    • @SPI 注解: 可以认为是定义默认实现类; 比如 Protocol 接口中,定义默认协议时 dubbo; @SPI(“dubbo”) public interface Protocol {}

    • @Adaptive 注解:

      该注解打在接口方法上;调 ExtensionLoader.getAdaptiveExtension()获 取设配类,会先通过前面的过程生成 java 的源代码,在通过编译器编译成 class 加载。 但是 Compiler 的实现策略选择也是通过 ExtensionLoader.getAdaptiveExtension(),如果也 通过编译器编译成 class 文件那岂不是要死循环下去了吗? 此时分析 ExtensionLoader.getAdaptiveExtension()函数,对于有实现类上去打了注解 @Adaptive 的 dubbo spi 扩展机制,它获取设配类不在通过前面过程生成设配类 java 源代码, 而是在读取扩展文件的时候遇到实现类打了注解@Adaptive 就把这个类作为设配类缓存在 ExtensionLoader 中,调用是直接返回。

Spring 可扩展 Schema

在 dubbo-demo-provider 项目中,我们在如下文件中配置服务提供方:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
<dubbo:service protocol="rmi" interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
</beans>

Spring 提供了可扩展 Schema 的支持,这是一个不错的折中方案,完 成一个自定义配置一般需要以下步骤:

  • 设计配置属性和 JavaBean
  • 编写 XSD 文件
  • 编写 NamespaceHandler 和 BeanDefinitionParser 完成解析工作
  • 编写 spring.handlers 和 spring.schemas 串联起所有部件
  • 在 Bean 文件中应用

设计配置属性和 JavaBean

首先当然得设计好配置项,并通过 JavaBean 来建模,本例中需要配置 People 实体,配 置属性 name 和 age(id 是默认需要的)

public class People {
	private String id;
	private String name;
	private Integer age;
}

编写 XSD 文件

为上一步设计好的配置项编写 XSD 文件,XSD 是 schema 的定义文件,配置的输入和解 析输出都是以 XSD 为契约,本例中 XSD 如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
xmlns="http://blog.csdn.net/cutesource/schema/people"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://blog.csdn.net/cutesource/schema/people"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:element name="people">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="age" type="xsd:int" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>

完成后需把 xsd 存放在 classpath 下,一般都放在 META-INF 目录下

编写 NamespaceHandler 和 BeanDefinitionParser 完成解析工作

下面需要完成解析工作,会用到 NamespaceHandler 和 BeanDefinitionParser 这两个概念。 具体说来 NamespaceHandler 会根据 schema 和节点名找到某个 BeanDefinitionParser,然后由BeanDefinitionParser 完成具体的解析工作。因此需要分别完成 NamespaceHandler 和BeanDefinitionParser 的实现类,Spring 提供了默认实现类 NamespaceHandlerSupport 和AbstractSingleBeanDefinitionParser,简单的方式就是去继承这两个类。本例就是采取这种方式:

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
	public void init() {
		registerBeanDefinitionParser("people", new PeopleBeanDefinitionParser());
	}
}

其中 registerBeanDefinitionParser(“people”, new PeopleBeanDefinitionParser());就是用来把节点名和解析类联系起来,在配置中引用 people 配 置 项 时 , 就 会 用PeopleBeanDefinitionParser 来解析配置。PeopleBeanDefinitionParser 就是本例中的解析类:

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
public class PeopleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
	protected Class getBeanClass(Element element) {
	return People.class;
	}
	protected void doParse(Element element, BeanDefinitionBuilder bean) {
		String name = element.getAttribute("name");
		String age = element.getAttribute("age");
		String id = element.getAttribute("id");
		if (StringUtils.hasText(id)) {
			bean.addPropertyValue("id", id);
		}
		if (StringUtils.hasText(name)) {
			bean.addPropertyValue("name", name);
		}
		if (StringUtils.hasText(age)) {
			bean.addPropertyValue("age", Integer.valueOf(age));
		}
	}
}

编写 spring.handlers 和 spring.schemas 串联起所有部件

上面几个步骤走下来会发现开发好的 handler 与 xsd 还没法让应用感知到,就这样放上 去是没法把前面做的工作纳入体系中的,spring 提供了 spring.handlers 和 spring.schemas 这 两个配置文件来完成这项工作,这两个文件需要我们自己编写并放入 META-INF 文件夹中, 这两个文件的地址必须是 META-INF/spring.handlers 和 META-INF/spring.schemas,spring 会默 认去载入它们,本例中 spring.handlers 如下所示: http://blog.csdn.net/cutesource/schema/people=study.schemaExt.MyNamespaceHandler 以上表示当使用到名为”http://blog.csdn.net/cutesource/schema/people”的 schema 引用时, 会通过 study.schemaExt.MyNamespaceHandler 来完成解析 spring.schemas 如下所示: http://blog.csdn.net/cutesource/schema/people.xsd=META-INF/people.xsd 以上就是载入 xsd 文件

举例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="hello-world-app" />
<dubbo:registry protocol="zookeeper" address="10.125.195.174:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="demo.service.DemoService"
ref="demoService" /> <!-- 和本地 bean 一样实现服务 -->
<bean id="demoService" class="demo.service.DemoServiceImpl" />
</beans>
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

static {
	Version.checkDuplicate(DubboNamespaceHandler.class);
}

 public void init() {
	registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, tru
e));
	registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
	registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
 	registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
 	registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
	registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
	registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
 	registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
 	registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
 	registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
 }
 }

提供者暴露一个服务的详细过程

首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl),然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。接下来就是Invoker转换到Exporter的过程。

dubbo协议:

Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。

消费者消费一个服务的详细过程

首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例(如上图中的红色部分),这是服务消费的关键。接下来把Invoker转换为客户端需要的接口(如:HelloWorld)。

集群

cluster将 Directory 中的多个 Invoker 伪装成一个 Invoker, 对上层透明,包含集群的容错机制

集群方案

  • AvailableCluster: 获取可用的调用。遍历所有 Invokers 判断 Invoker.isAvalible,只要一个有 为 true 直接调用返回,不管成不成功;
  • BroadcastCluster: 广播调用。遍历所有 Invokers, 逐个调用每个调用 catch 住异常不影响 其他 invoker 调用;
  • FailbackCluster: 失败自动恢复, 对于 invoker 调用失败, 后台记录失败请求,任务定时 重发, 通常用于通知;
  • FailfastCluster: 快速失败,只发起一次调用,失败立即保错,通常用于非幂等性操作;
  • FailoverCluster: 失败转移,当出现失败,重试其它服务器,通常用于读操作,但重试会 带来更长延迟;

    (1) 目录服务 directory.list(invocation) 列出方法的所有可调用服务 获取重试次数,默认重试两次

    (2) 根据 LoadBalance 负载策略选择一个 Invoker

    (3) 执行 invoker.invoke(invocation)调用

    (4) 调用成功返回

    调用失败小于重试次数,重新执行从 3)步骤开始执行调用次数大于等于重试次数抛出调用失败异常

  • FailsafeCluster: 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作。
  • ForkingCluster: 并行调用,只要一个成功即返回,通常用于实时性要求较高的操作,但 需要浪费更多服务资源。
  • MergeableCluster: 分组聚合, 按组合并返回结果,比如菜单服务,接口一样,但有多 种实现,用 group 区分,现在消费方需从每种 group 中调用一次返回结果,合并结果返回, 这样就可以实现聚合菜单项。

目录服务Directory

集群目录服务 Directory, 代表多个 Invoker, 可以看成 List,它的值可能是动态 变化的比如注册中心推送变更。集群选择调用服务时通过目录服务找到所有服务;

路由服务router

Router 服务路由, 根据路由规则从多个 Invoker 中选出一个子集 AbstractDirectory 是所 有目录服务实现的上层抽象, 它在 list 列举出所有 invokers 后,会在通过 Router 服务进行 路由过滤。

负载均衡LoadBalance

LoadBalance负载均衡,负责从多个 Invokers中选出具体的一个Invoker用于本次调用, 调用过程中包含了负载均衡的算法,调用失败后需要重新选择:


如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏博主

下一篇 1. 两数之和

留言