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类。
代理模式
不再赘述
代码语言:javascript复制 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内部的反射机制来实现的,反射机制在生成类的过程中比较高效。
代码语言:javascript复制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 项目中,我们在如下文件中配置服务提供方:
代码语言:javascript复制<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 是默认需要的)
代码语言:javascript复制public class People {
private String id;
private String name;
private Integer age;
}
编写 XSD 文件
为上一步设计好的配置项编写 XSD 文件,XSD 是 schema 的定义文件,配置的输入和解 析输出都是以 XSD 为契约,本例中 XSD 如下:
代码语言:javascript复制<?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,简单的方式就是去继承这两个类。本例就是采取这种方式:
代码语言:javascript复制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 就是本例中的解析类:
代码语言:javascript复制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 文件
举例:
代码语言:javascript复制<?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>
代码语言:javascript复制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用于本次调用, 调用过程中包含了负载均衡的算法,调用失败后需要重新选择: