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自带的反射功能类似,但比反射功能更强大。
推荐博文:
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用于本次调用, 调用过程中包含了负载均衡的算法,调用失败后需要重新选择: