Dubbo开发者指南——【详解】

Dubbo开发者指南

 

目录

 

源代码构建

框架设计

SPI加载

实施细节

编码惯例

坏味道的代码

 


提供  使用Spring Boot生成Dubbo项目的源码框架:

导入idea即可(maven)

https://pan.baidu.com/s/1vKJJjaKjsEkUTy-5bc6c-w

源代码构建

查看

使用命令blow结束最新的项目源代码:

git clone https://github.com/apache/incubator-dubbo.git dubbo

分行

我们将其master用作新功能开发的主要分支,并使用其他分支进行维护。可以通过https://github.com/apache/incubator-dubbo/tags查看所有版本的标签。

建造

Dubbo依靠maven作为构建工具。

要求:

  • Java高于1.5版本
  • Maven版本2.2.1或更高版本

MAVEN_OPTS在构建之前应配置以下内容:

export MAVEN_OPTS=-Xmx1024m -XX:MaxPermSize=512m

使用以下命令构建:

mvn clean install

使用以下构建命令跳过测试:

mvn install -Dmaven.test.skip

构建jar包的源代码

用以下命令构建Dubbo源代码jar包,可以调试Dubbo源代码。

mvn clean source:jar install -Dmaven.test.skip

IDE支持

使用下面的命令来生成IDE。

Intellij Idea

mvn idea:idea

日食

mvn eclipse:eclipse

导入eclipse

首先,需要在eclipse中配置maven存储库。M2_REPO单击,定义并将其指向本地maven存储库Preferences -> Java -> Build Path -> Classpath

使用以下maven命令:

mvn eclipse:configure-workspace -Declipse.workspace=/path/to/the/workspace/

1:通过https://github.com/apache/incubator-dubbo查看源代码 2:UNIX下的路径是$ {HOME} /。m2 / repository,Windows下的路径是C:\ Documents and Settings <user>。 M2 \库

框架设计

整体设计

/dev-guide/images/dubbo-framework.jpg

图片描述:

  • 浅蓝色背景的左侧区域显示服务消费者界面,浅绿色背景的右侧区域显示服务提供者界面,中心区域显示两个侧面界面。
  • 图像从底部到顶部分为10层,这些层是单向依赖的。右侧的黑色箭头表示层之间的依赖关系,每层可以从上层剥离以重复使用,Service和Config层是API,其他层是SPI。
  • 绿框是扩展接口,蓝框是实现类,图像仅显示关联层的实现类。
  • 蓝色虚线是初始化过程,启动时为装配链,方法调用过程为红线,运行时调用链,继承紫色三角箭头,可将子类视为父类的同一节点,文本为lines是方法调用。

图层描述

  • config层:外部配置界面,ServiceConfig并且ReferenceConfig是图层的中心,可以直接初始化配置类,也可以通过spring生成配置类。
  • 代理层:服务接口的透明代理,生成客户端Stub服务和服务器Skeletion of service,ServiceProxy是中心,扩展接口是ProxyFactory
  • 注册表层:服务注册表和发现的封装,服务URL是中心,扩展接口是RegistryFactoryRegistryRegistryService
  • 簇层:muliple提供商和负载平衡,和桥接登记中心的簇的封装,Invoker是中心,扩展接口是ClusterDirectoryRouterLoadBalance
  • 监控层:的RPC调用倍显示器和呼叫执行时间,Statistics是中心,扩展接口是MonitorFactoryMonitorMonitorService
  • 协议层:RPC的封装,Invocation并且Result是中心,扩展接口是ProtocolInvokerExporter
  • 交换层:的请求和响应,同步传输异步封装,Request并且Response是中心,扩展接口是ExchangerExchangeChannelExchangeClientExchangeServer
  • 传输层:米娜和网状的抽象,Message是中心,扩展接口是ChannelTransporterClientServerCodec
  • 序列化层:可重复使用的工具,扩展接口SerializationObjectInputObjectOutputThreadPool

关系描述

  • 在RPC中,Protocol是核心层,它意味着您可以通过Protocol + Invoker + Exporter完成RPC调用,然后在Invoker的主进程中进行过滤。
  • Consumer和Provider是抽象概念,只是希望您更直观地了解哪些类属于客户端和服务器端,不使用Client和Server的原因是Dubbo使用Provider,Consumer,Registry,Monitor划分逻辑拓扑节点。场景,保持团结的概念。
  • Cluster是外部概念,Cluster的目的是让各种Invoker伪装成一个Invoker,这样我们只关注Invoker in Protocol层,添加Cluster或删除Cluster不会影响其他层,因为我们不需要Cluster什么时候只有一个提供者。
  • Proxy层封装了所有接口的透明代理,在Invoker作为中心的其他层中,将Invoker转换为接口,或者仅在暴露给用户时将接口实现转换为Invoker by Proxy。RPC仍然可以工作,甚至删除代理层,但不是那么透明,使得远程服务调用看起来不像本地服务调用。
  • 远程处理是Dubbo协议的实现,如果选择RMI,您可以删除远程处理。Remoting分为Transport层和Exchange层,Transport层负责单向消息传输,它是Mina,Netty,Grizzly的抽象,它还可以扩展UDP传输。Exchange层在传输层上封装了Request-Response语义。
  • 实际上Registry和Monitor不在同一层,它们是独立的节点,只是为了全局视图而一层一层地绘制它们。

模块包装

/dev-guide/images/dubbo-modules.jpg

模块说明:

  • dubbo-common模块:包括Util类和通用模块。
  • dubbo-remoting模块:是Dubbo协议实现,如果使用RMI for RPC则无需使用此模块。
  • dubbo-rpc模块:各种协议的抽象,和动态代理,只有一对一的调用,不关心集群的管理。
  • dubbo-cluster模块:将许多服务提供者伪装成一个提供者,包括负载平衡,容错,路由等。群集的地址列表可以是静态的,也可以是注册表发送的。
  • dubbo-registry模块:基于注册表发送地址的集群和各种注册中心的抽象。
  • dubbo-monitor模块:服务呼叫时间统计,呼叫时间,呼叫链跟踪服务。
  • dubbo-config模块:是Dubbo外部API,用户使用Dubbo by Config,隐藏Dubbo的详细信息。
  • dubbo-container模块:是一个Standlone容器,只需使用Main方法加载Spring,因为通常服务不需要Tomcat / JBoss功能。

包层根据层结构划分,与层划分的区别:

  • 容器是服务容器,用于服务运行部署,未在映像中显示。
  • 协议层和代理层都放在RPC模块中,它们是RPC模块的核心,当只有1个提供者时,可以使用这2层完整的RPC调用。
  • 传输层和交换层放置在远程模块中,用于RPC呼叫基础通信。
  • 序列化层放在通用模块中,以便重用。

依赖关系

/dev-guide/images/dubbo-relation.jpg

图片描述:

  • 图像,协议,群集,代理,服务,容器,注册表,监视器中的框表示层或模块,蓝调表示与业务交互,绿色表示仅与Dubbo的内部交互。
  • 图像,消费者,提供者,注册表,监视器中的背景框表示部署逻辑拓扑节点。
  • 调用图像中的蓝色虚线进行初始化,为运行时异步调用红色虚线,并为运行时同步调用红线。
  • 图像仅包含RPC层,不包括Remoting层,整个Remoting隐藏在Protocol层中。

呼叫链

展开整个设计地图的红色调用链:

/dev-guide/images/dubbo-extension.jpg

公开服务顺序

展开服务提供者公开服务的初始化链,在整个设计图的左侧,序列图如下所示:

/dev-guide/images/dubbo-export.jpg

参考服务序列

展开服务使用者参考服务的初始化链,在整个设计图的右侧,序列图如下所示:

/dev-guide/images/dubbo-refer.jpg

领域模型

达博的核心领域模型:

  • 协议是服务域,它是Invoker暴露和参考的主要功能入口,它负责Invoker的生命周期管理。
  • Invoker是实体域,它是Dubbo的核心模型,所有其他模型都受到干扰,或转换为它,它代表一个可执行文件,你可以通过调用invoke来调用它,它可以是一个本地实现,一个远程实现,或者集群实现。
  • 调用是会话域,它在调用进程中保存变量,例如方法名称,参数等。

基本设计原则

 

 

  • 使用Microkernel + Plugin设计模式,Microkernel只负责组装插件,Dubbo的功能是通过扩展点实现的,这意味着Dubbo的所有功能都可以被用户自定义扩展替换。
  • 使用URL作为配置信息的startdard格式,所有扩展点都通过URL传输配置信息。
  • 更多设计原则参考:框架设计原则

SPI加载

SPI配置

资源:

Dubbo SPI继承自标准JDK SPI(服务提供商接口),使其功能更强大。

Dubbo修复了以下标准JDK SPI的问题:

标准的JDK SPI将立即加载并实例化所有实现。如果实施是时间限制的,那将是浪费资源,但永远不会被使用。 如果加载SPI实现失败,我们无法获取SPI名称。例如:标准JDK ScriptEngine,通过调用方法getName()获取脚本类型。如果缺少dependnency jar jruby.jar,RubyScriptEngine类将加载失败,并且真正的错误信息将丢失。当用户执行ruby脚本时,程序抛出异常,告诉不支持ruby,但这不是真正的原因。 通过支持IoC和AOP来增强SPI功能,只需使用setter就可以通过另一个SPI轻松注入一个SPI。

import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
 
@Activate(group = "provider", value = "xxx") // only activate for provider,group can be "provider" or "consumer"
public class XxxFilter implements Filter {
    // ...
}

约定:

在包含扩展类[1]的jar文件中,放置一个配置文件 META-INF/dubbo/full interface name,文件内容模式:SPI name=the fully qualified name for the extension class,使用新行分隔符进行多次实现。

例:

要扩展Dubbo协议,请将文本文件放在扩展jar文件中:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,content:

xxx=com.alibaba.xxx.XxxProtocol

实施内容[2]

package com.alibaba.xxx;
 
import com.alibaba.dubbo.rpc.Protocol;
 
public class XxxProtocol implements Protocol { 
    // ...
}

配置模块中的配置

在Dubbo配置模块中,所有SPI点都有相关的属性或标签,我们可以通过使用其名称来选择特定的SPI实现。喜欢:

<dubbo:protocol name="xxx" />

SPI功能

SPI自动换行

自动封装SPI的Wrapper类。ExtensionLoader 加载SPI实现,如果SPI有复制指令,它将被视为SPI的Wrapper类。

包装类内容:

package com.alibaba.xxx;
 
import com.alibaba.dubbo.rpc.Protocol;
 
public class XxxProtocolWrapper implements Protocol {
    Protocol impl;
 
    public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }
 
    //after interface method is executed,the method in extension will be executed
    public void refer() {
        //... some operation
        impl.refer();
        // ... some operation
    }
 
    // ...
}

Wrapper类也实现了相同的SPI接口,但Wrapper并不是真正的实现。它用于包装从ExtensionLoader实际返回的实现。真正返回的实例 ExtensionLoader 是Wrapper类实例,Wrapper持有者是真正的SPI实现类。

一个spi可以有很多Wrapper,如果需要可以添加一个。

通过Wrapper类,您可以将相同的逻辑移动到所有SPI的Wrapper。新添加的Wrapper类为所有spis添加外部逻辑,看起来像AOP,Wrapper充当SPI的代理。

SPI自动加载

加载SPI时,Dubbo将自动加载依赖SPI。当一个SPI实现包含属性也是另一种类型的SPI时,ExtensionLoader将自动加载依赖SPI。ExtensionLoader通过扫描所有实现类的setter方法来了解特定SPI的所有成员。

演示:两个SPI CarMaker(汽车制造商),WheelMaker(车轮制造商)

接口看起来像:

public interface CarMaker {
    Car makeCar();
}
 
public interface WheelMaker {
    Wheel makeWheel();
}

CarMaker 执行:

public class RaceCarMaker implements CarMaker {
    WheelMaker wheelMaker;
 
    public setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }
 
    public Car makeCar() {
        // ...
        Wheel wheel = wheelMaker.makeWheel();
        // ...
        return new RaceCar(wheel, ...);
    }
}

ExtensionLoader加载CarMaker 实现时RaceCarsetWheelMaker 需要paramType WheelMaker 也是一个SPI,它会被自动加载。

这带来了一个新问题:如何ExtensionLoader确定在加载注入的SPI时使用哪种实现。对于此演示,当现有的多WheelMaker 实现时,应该ExtensionLoader 选择哪一个。

好问题,我们将在下一章解释它:SPI Auto Adaptive。

SPI自适应

ExtensionLoader 注入的依赖SPI 是一个实例Adaptive,真正的spi实现是已知的,直到执行自适应实例。

Dubbo使用URL(包含Key-Value)来传递配置。

SPI方法调用具有URL参数(具有URL属性的实体)

这样依赖SPI可以从URL获取配置,配置完所有需要的SPI密钥后,配置信息将通过URL从外部传递.URL在传递配置信息时充当总线。

演示:两个SPI CarMakerWheelMaker

界面看起来像:

public interface CarMaker {
    Car makeCar(URL url);
}
 
public interface WheelMaker {
    Wheel makeWheel(URL url);
}

CarMaker 执行:

public class RaceCarMaker implements CarMaker {
    WheelMaker wheelMaker;
 
    public setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }
 
    public Car makeCar(URL url) {
        // ...
        Wheel wheel = wheelMaker.makeWheel(url);
        // ...
        return new RaceCar(wheel, ...);
    }
}

执行上面的代码时

// ...
Wheel wheel = wheelMaker.makeWheel(url);
// ...

中,注入的Adaptive 对象确定 WheelMaker的 makeWheel 方法将通过预定Key..Such执行如wheel.type,键url.get("wheel.type") 将确定WheelMake 的implementation.The逻辑Adaptive的固定例如,获取URL的预定义键,动态地创建所述实实现并执行它。

对于Dubbo, 当dubbo加载SPI时Adaptive ,ExtensionLoader动态创建SPI 实现。 从URL获取Key,Key将通过@Adaptive接口方法定义的注释提供。

以下是Dubbo Transporter SPI代码:

public interface Transporter {
    @Adaptive({"server", "transport"})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
 
    @Adaptive({"client", "transport"})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

对于方法bind(),Adaptive将首先搜索server密钥,如果没有Key,那么将搜索transport密钥,以确定代理所代表的实现。

 

  • 注意:这里的配置文件在你自己的jar文件中,而不是在dubbo release jar文件中,Dubbo会在ClassPath中扫描所有jar文件中相同的文件名然后合并在一起↩︎

  • 注意:SPI将以单件模式加载(请确保线程安全),缓存在 ExtensionLoader ↩︎中

SPI自动激活

至于类别SPI,如:FilterInvokerListenerExportListenerTelnetHandlerStatusChecker 等,多实现可以在同一时间加载。用户可以使用自动激活简化配置,如:

import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
 
@Activate // Active for any condition
public class XxxFilter implements Filter {
    // ...
}

要么:

import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
 
@Activate("xxx") // when configed xxx parameter and the parameter has a valid value,the SPI is activated,for example configed cache="lru",auto acitivate CacheFilter。
public class XxxFilter implements Filter {
    // ...
}

要么:

import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
 
@Activate(group = "provider", value = "xxx") // only activate for provider,group can be "provider" or "consumer"
public class XxxFilter implements Filter {
    // ...
}

  • 注意:这里的配置文件在你自己的jar文件中,而不是在dubbo release jar文件中,Dubbo会在ClassPath中扫描所有jar文件中相同的文件名然后合并在一起↩︎

  • 注意:SPI将以单件模式加载(请确保线程安全),缓存在 ExtensionLoader ↩︎中

实施细节

初始化细节

服务解析

基于META-INF/spring.handlersdubbo.jar中的config,Spring会DubboNamespaceHandler在遇到dubbo命名空间时调用。

DubboBeanDefinitionParser基于一对一属性映射,所有Dubbo标签都被解析,XML标签被解析为Bean对象。

转移豆对象URL,并bean的所有属性转移到URL参数时ServiceConfig.export()ReferenceConfig.get()初始化。

然后根据URL协议头,根据扩展点的扩展点自适应机制,处理服务暴露或不同协议的引用,将URL 扩展协议扩展点

服务曝光

1.仅公开服务端口:

没有Registry,[1]时直接暴露给提供者,解析的URL格式为ServiceConfig: dubbo://service-host/com.foo.FooService?version=1.0.0

基于扩展点自适应机制,通过识别URL的协议头来调用和打开服务器端口的export()方法。DubboProtocoldubbo://

2.公开登记:

暴露提供商地址到注册表[2] ,其通过解析URL格式ServiceConfigregistry://registry-host/com.alibaba.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")

基于扩展点自适应机制,通过识别 协议头的调用export()方法,向Registry 注册提供者URL参数。RegistryProtocolregistry://export

重新发送到Protocol扩展点进行曝光:dubbo://service-host/com.foo.FooService?version=1.0.0然后基于扩展点自适应机制,通过识别提供者URL的协议头来调用export()方法DubboProtocol和打开服务器端口dubbo://

服务参考

1.直接连接服务

没有Registry [3]时直接连接提供者,解析的URL格式为ReferenceConfigdubbo://service-host/com.foo.FooService?version=1.0.0

基于扩展点自适应机制,通过识别URL的协议头,并返回提供者引用的调用refer()方法。DubboProtocoldubbo://

2.服务注册表发现

发现由注册表提供的地址[4] ,其通过解析URL格式ReferenceConfig: registry://registry-host/com.alibaba.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")

基于扩展点自适应机制,通过识别URL的协议头,然后根据搜索提供者URL 的参数条件调用refer()方法,例如:。RegistryProtocolregistry://referdubbo://service-host/com.foo.FooService?version=1.0.0

然后基于扩展点自适应机制,通过识别提供者URL的协议头来调用提供者参考的refer()方法。DubboProtocoldubbo://

然后RegistryProtocol通过Cluster扩展点和返回来伪装对单个提供者的各种提供者引用。

服务过滤器

基于扩展点自适应机制,所有Protocol扩展点都是自动包装Wrapper类。

基于ProtocolFilterWrapper类,将all Filter作为链,在链的末尾调用实际引用。

基于ProtocolListenerWrapper类,make all InvokerListenerExporterListeneras list,在曝光和参考之前和之后执行回调。

所有其他功能都将由Filter包括Monitor在内的所有其他功能实现。

RPC细节

服务提供商公开服务的详细过程

/dev-guide/images/dubbo_rpc_export.jpg

上图显示了服务提供商公开服务的主要过程:

第一ServiceConfig类得到实际的类ref,提供服务(例如如:HelloWorldImpl),则产生一AbstractProxyInvoker通过实例getInvoker的方法ProxyFactory的类,该步骤,完成特定服务的转变Invoker,其次是转换的过程InvokerExporter

Dubbo处理服务曝光的关键是转换InvokerExporter上图中红色部分的过程。这里我们介绍两种典型协议Dubbo和RMI的实现:

达博实施

InvokerDubbo协议到类Exporterexport方法的转换DubboProtocol,主要是打开套接字来监听服务并接收客户端发送的各种请求,通信细节由Dubbo本身实现。

RMI实施

InvokerRMI协议到类Exporterexport方法的转换RmiProtocol,RMI服务由Spring,Dubbo或JDK实现,通信细节由JDK实现,节省了大量的工作。

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

/dev-guide/images/dubbo_rpc_refer.jpg

以上图片是服务消费的主要过程:

首先,init的方法ReferenceConfig类调用refer的方法Protocol,以产生Invoker实例(如上述图像中的红色部分),这是服务消费的关键。然后将Invoker其转换为客户端所需的接口(例如:HelloWorld)。

对于每个协议(如RMI / Dubbo / Web服务),它们调用refer方法生成Invoker实例的详细信息与上一节类似。

到处都是祈求者

由于Invoker在Dubbo域模型中是一个非常重要的概念,许多设计思想都接近它。这使得Invoker渗透整个实现代码,并且很容易混淆刚刚开始使用Dubbo的人。

让我们使用下面的简单图片来描述两个重要的Invoker:服务提供者Invoker和服务消费者Invoker

/dev-guide/images/dubbo_rpc_invoke.jpg

为了更好地解释上面的图像,我们提供了以下服务消费和提供者的代码示例:

服务消费者代码:

public class DemoClientAction {
 
    private DemoService demoService;
 
    public void setDemoService(DemoService demoService) {
        this.demoService = demoService;
    }
 
    public void start() {
        String hello = demoService.sayHello("world" + i);
    }
}

DemoService上面的代码中是在上述图像服务消费者的代理,用户可以呼叫Invoker [5] ,其由代理implementate真正RPC。

服务提供商代码:

public class DemoServiceImpl implements DemoService {
 
    public String sayHello(String name) throws RemoteException {
        return "Hello " + name;
    }
}

上面的类将被封装为一个AbstractProxyInvoker实例,并创建一个新Exporter实例,然后在网络通信层接收请求时找到相应的Exporter实例并调用其对应的AbstractProxyInvoker实例,以便真正调用服务提供者代码。还有一些其他Invoker类,但上面的2是最重要的。

远程通信细节

协议标头协议

/dev-guide/images/dubbo_protocol_header.jpg

线程调度模型

/dev-guide/images/dubbo-protocol.jpg

  • Dispather: ,alldirect,,messageexecutionconnection
  • ThreadPool : fixed,cached

  1. <dubbo:service regisrty="N/A" /><dubbo:registry address="N/A" /> ↩︎

  2. <dubbo:registry address="zookeeper://10.20.153.10:2181" /> ↩︎

  3. <dubbo:reference url="dubbo://service-host/com.foo.FooService?version=1.0.0" /> ↩︎

  4. <dubbo:registry address="zookeeper://10.20.153.10:2181" /> ↩︎

  5. 是以下之一DubboInvokerHessianRpcInvokerInjvmInvokerRmiInvokerWebServiceInvoker↩︎

编码惯例

代码风格

Dubbo的源代码和JavaDoc遵循以下规范:

异常和日志记录

  • 记录更多上下文信息,例如错误原因,错误服务器地址,客户端地址,注册中心地址,dubbo版本等。
  • 尝试将主要原因放在前面,然后用键值巴黎显示所有其他上下文信息。
  • 抛出异常时不会打印日志,日志级别由最终异常处理程序确定,并且必须在指定异常时打印日志。
  • ERRORlog表示需要ALARM,WARNlog表示COULD AUTO RECOVERY,INFOlong表示NORMAL。
  • 建议:配置ERROR登录监控中心进行实时报警,汇总并WARN每周发送日志。
  • RpcException是Dubbo唯一的外部例外,RpcException如果需要抛弃给用户,所有内部异常都会被转移。
  • RpcException 不能有子类,所有类型的信息都用ErrorCode标识,以保持兼容。

配置和URL

  • 对于对象属性,使用首字母和camelCase作为多个单词[1]
  • 对于配置属性[2],使用小写并用“ - ”分隔多个单词。
  • 使用小写并按'分隔'。用于URL属性的多个单词[3]
  • 尽可能使用URL传输参数,不要定义Map或其他类型,配置信息也会转移到URL样式。
  • 最小化URL嵌套以保持URL简单性。

单元测试和集成测试

  • 使用JUnit和EasyMock进行单元测试,使用TestNG进行集成测试,使用DBUnit进行数据库测试。
  • 不要在单元测试中放置大型集成测试用例来测试单元测试用例的运行速度。
  • 使用try...finallytearDown释放所有单元测试测试用例的资源。
  • 最小化带有while需要等待repsonse的循环的测试用例,用于使定时器中的逻辑作为定时器和网络测试的功能。
  • 对于故障安全测试,统一使用LogUtil断言日志输出。

扩展点基类和AOP

  • AOP类应该命名为XxxWrapper,Base类应该命名为AbstractXxx
  • 使用AOP来表示扩展点之间的组合关系,ExtensionLoader仅加载扩展点,包括AOP扩展。
  • 尝试使用Ioc注入扩展点的依赖关系,不要直接依赖于工厂方法ExtensionLoader
  • 尝试使用AOP实现扩展点的通用操作,而不是使用基类,例如isAvailable在负载平衡之前进行检查,这与负载平衡无关。关闭不需要检查的URL参数。
  • 使用基类对各种相似类型进行抽象,例如RMI,Hessian 3rd协议,它们生成了接口代理,只有传输接口代理Invoker来完成桥接,公共基类可以做逻辑。
  • 基类也是SPI的一部分,每个扩展都应该有一个方便的基类支持。

模块和包装

  • 基于包装的可重用性,将接口,基类和大型实现划分为单独的模块。
  • 将所有接口放在模块的基础包下,并将基类放在支持子包中,不同的实现放在扩展点命名的子包下。
  • 尝试保持子包依赖于父包,而不是反向。

坏味道的代码

Ugly Dubbo的设计或实施将在这里进行记录。

网址转换

1.点对点服务导出和引用

服务直接出口:

EXPORT(dubbo://provider-address/com.xxx.XxxService?version=1.0.0")

服务直接参考:

REFER(dubbo://provider-address/com.xxx.XxxService?version=1.0.0)

2.通过注册表导出服务

出口服务到注册表:

EXPORT(registry://registry-address/com.alibaba.dubbo.registry.RegistrySerevice?registry=dubbo&export=ENCODE(dubbo://provider-address/com.xxx.XxxService?version=1.0.0))

收购登记处:

url.setProtocol(url.getParameter("registry", "dubbo"))
GETREGISTRY(dubbo://registry-address/com.alibaba.dubbo.registry.RegistrySerevice)

注册服务地址:

url.getParameterAndDecoded("export"))
REGISTER(dubbo://provider-address/com.xxx.XxxService?version=1.0.0)

3.从注册表中引用服务

从注册表中引用服务:

REFER(registry://registry-address/com.alibaba.dubbo.registry.RegistrySerevice?registry=dubbo&refer=ENCODE(version=1.0.0))

收购登记处:

url.setProtocol(url.getParameter("registry", "dubbo"))
GETREGISTRY(dubbo://registry-address/com.alibaba.dubbo.registry.RegistrySerevice)

订阅服务地址:

url.addParameters(url.getParameterAndDecoded("refer"))
SUBSCRIBE(dubbo://registry-address/com.xxx.XxxService?version=1.0.0)

通知服务地址:

url.addParameters(url.getParameterAndDecoded("refer"))
NOTIFY(dubbo://provider-address/com.xxx.XxxService?version=1.0.0)

4.注册表推送路由规则

注册表推送路由规则:

NOTIFY(route://registry-address/com.xxx.XxxService?router=script&type=js&rule=ENCODE(function{...}))

收购路由器:

url.setProtocol(url.getParameter("router", "script"))
GETROUTE(script://registry-address/com.xxx.XxxService?type=js&rule=ENCODE(function{...}))

5.从文件加载路由规则

从文件加载路由规则:

GETROUTE(file://path/file.js?router=script)

收购路由器:

url.setProtocol(url.getParameter("router", "script")).addParameter("type", SUFFIX(file)).addParameter("rule", READ(file))
GETROUTE(script://path/file.js?type=js&rule=ENCODE(function{...}))

调用参数

  • 路径服务路径
  • 集团服务组
  • 版本服务版本
  • dubbo当前的dubbo发行版
  • 令牌验证令牌
  • 超时调用超时

SPI加载

1. SPI自适应

当ExtensionLoader加载SPI时,它将检查spi属性(使用set方法)。如果一个属性是SPI,则ExtensionLoader将加载SPI实现。自动注入对象是一个自适应实例(代理),因为真正的实现仅在执行阶段被确认。当调用自适应spi时,Dubbo将选择真实的实现并执行它。Dubbo根据mehod定义的参数选择正确的实现。

Dubbo定义的所有内部SPI都具有为方法调用定义的URL参数。Adaptive SPI使用URL来确定所需的实现。URL中的一个特定Key和Value确认了特定实现的用法,所有这些都是通过添加@Adaptive 注释来完成的。

@Extension
public interface Car {
    @Adaptive({"http://10.20.160.198/wiki/display/dubbo/car.type", "http://10.20.160.198/wiki/display/dubbo/transport.type"})
    public run(URL url, Type1 arg1, Type2 arg2);
}

对于上述规则,ExtensionLoader将为每个注入的SPI创建一个自适应实例。

ExtensionLoader生成的自适应类看起来像:

package <package name for SPI interface>;
 
public class <SPI interface name>$Adpative implements <SPI interface> {
    public <contains @Adaptive annotation method>(<parameters>) {
        if(parameters containing URL Type?) using URL parameter
        else if(method returns URL) using the return URL
        # <else throw exception,inject SPI fail!>
         
        if(URL accquired == null) {
            throw new IllegalArgumentException("url == null");
        }
 
        According to the Key order from @Adaptive annotation,get the Value from the URL as the real SPI name
        if no value is found then use the default SPI implementation。If no SPI point, throw new IllegalStateException("Fail to get extension");
 
        Invoke the method using the spi and return the result.
    }
 
    public <method having annotation @Adaptive>(<parameters>) {
        throw new UnsupportedOperationException("is not adaptive method!");
    }
}

@Adaptive 注释用法:

如果没有为URL中的那些键配置值,则使用默认的SPI实现。例如,String [] {“key1”,“key2”},首先Dubbo将查找key1的值并将其用作SPI名称;如果是key1如果没有建立值,则查找key2,如果找不到key2的值,则使用默认的spi实现。如果没有默认实现被绑定,那么该方法将抛出IllegalStateException.if而不是configed,那么默认实现是接口类完整包名称的小写。对于Extension接口com.alibaba.dubbo.xxx.YyyInvokerWrapper,默认值为new String[] {"yyy.invoker.wrapper"}

回调函数

1.参数回调

主要理论:在一个消费者 - >提供者的持久连接中,在消费者端输出服务,提供者端可以反向调用消费者端的实例。

实施细节:

  • 为了在传输中交换接口实例,在DubboCodec中实现了自动导出和自动引用。需要分离业务逻辑和编解码逻辑。
  • 你需要判断在从调用中获取导出器时是否需要回调,如果需要,从附件中获取回调实例id。通过使用此方法,消费者端可以使用不同的实现来实现回调接口。

2.事件通知

主要理论:当Consumer执行invoke方法时,判断是否有任何onreturn / onerror的配置...将onreturn的方法放到异步invocatioin的回调列表中。

实现细节:参数是使用URL传递的,但URL不支持string-object,因此该方法存储在staticMap中,需要进行优化。

懒惰连接

DubboProtocol特定功能,默认禁用

当客户端为服务器创建代理时,首先不要建立TCP持久连接,只在数据需要传输时才启动connecton。

此羽毛将禁用连接重试策略,再次重新发送数据(如果在发送数据时连接丢失,请尝试建立新连接以发送数据)

分享连接

DubboProtocol特定功能,默认启用。

JVM A导出许多服务,JVM B引用A的多个服务,共享连接意味着A和B之间的那些不同服务调用使用相同的TCP连接来传输数据,从而减少服务器连接。

实现细节:当对同一地址使用共享连接时,你需要更多地关注调用者的销毁动作。一方面,当所有调用者引用相同的地址被破坏时,你应该关闭连接,另一方面,你不应该当并非所有调用者都被销毁时关闭连接。在设计实现中,我们使用一种称为引用计数的策略,我们创建一个名为Lazy connection的连接,用于在关闭连接时不会影响业务的异常,以防万一。

粘性政策

当存在许多提供者并配置粘性策略时,调用将被发送到与上次调用相同的提供者。Sticky Policy打开了连接的惰性属性,以避免打开无用的连接。

提供商选择逻辑

  1. 现有的多提供商,首先通过Loadbalance选择。如果选择的提供商可用,那么只需进行调用
  2. 如果选定的提供者在阶段1中不可用,则从剩余部分中选择,如果可用,则进行创新
  3. 如果所有提供者都不可用,则重新扫描列表(不首先选择调用者),如果有任何提供者可用,如果存在,则进行调用。
  4. 如果阶段3中没有可用的提供者,那么将选择阶段1的调用者的下一个调用者(如果不是最后一个),避免冲突。

 

原文引自Dubbbo官方:http://dubbo.apache.org/en-us/docs/user/quick-start.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值