面试整理-Dubbo

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/zhangdong2012/article/details/100714812

消费端直连服务端

服务消费端配置
<dubbo:application name="consumer"/>
<dubbo:registry address="N/A"/>
<dubbo:reference id="someServicce" interface="com.test.SomeService" url="dubbo://localhost:20880"/>

服务提供端配置
<dubbo:application name="provider"/>
<dubbo:protocol name="dubbo" port="20880"/>
<bean id="someService" class="com.test.SomeServiceImpl"/>
<dubbo:service interface="com.test.SomeService"/>

直连方式不能配置考可用,并且不利于服务治理,一般在测试的时候用。

配置注册中心

<dubbo:registry protocol="zookeeper" address="localhost:2181">

管控平台

git 地址
https://github.com/apache/dubbo-admin
下载
git clone [email protected]:apache/dubbo-admin.git
mvn clean package
cd /workspace/dubbo-admin/dubbo-admin/dubbo-admin-distribution/target
运行
java -jar dubbo-admin-0.1.jar 

关闭服务检查

避免当provider端没有启动时,启动consumer端保存

check="false"
<dubbo:reference id="someService" interface="com.test.SomeService" check="false"/>

多版本与服务分组

provider:
<dubbo:application name="providerapp"/>
<dubbo:registry protocol="zookeeper" address="localhost:2181"/>
<dubbo:protocol name="dubbo" port="2181"/>

<bean id="aliPay01" class="com.pay.AliPay01"/>
<bean id="aliPay02" class="com.pay.AliPay01"/>
<bean id="jdPay" class="com.pay.JDPay"/>

<dubbo:service interface="com.pay.Pay" ref="aliPay01" version="0.0.1" group="ali"/>
<dubbo:service interface="com.pay.Pay" ref="aliPay02" version="0.0.2" group="ali"/>
<dubbo:service interface="com.pay.Pay" ref="jdPay" group="jdPay"/>


consumer:
<dubbo:application name="consumerapp"/>
<dubbo:registry protocol="zookeeper" address="localhost:2181"/>
<dubbo:reference interface="com.pay.Pay" id="aliPay01" group="ali" version="0.0.1"/>
<dubbo:reference interface="com.pay.Pay" id="aliPay02" group="ali" version="0.0.2"/>
<dubbo:reference interface="com.pay.Pay" id="jdPay" group="jd"/>

多协议支持

dubbo支持多种协议

  • dubbo
  • http
  • rest
  • webservice
  • rmi
  • thrift
  • hession

负载均衡

  • rondom 随机(默认)
  • roundrobin 轮训
  • lastactive 流量少的节点优先级最低
  • consistenthash 一致性哈希(根据方法参数)

容错策略

  • failover 失效转移(默认)
  • fastfail 快速失效,通常用在非幂等操作上
  • fastsafe 直接忽略
  • failback 稍后重试
  • broadcast 广播策略,有一个节点返回成功,则认为成功

负载均衡策略既可以设置在provider端也可以设置在consumer端,consumer端的配置优先级高于provider。

服务降级

<dubbo:reference mock=“return null”/>
XxxServiceMock

超时与重试

<dubbo:reference timeout="2000" retries="5"/>

声明式缓存

dubbo声明式缓存默认存储1000个结果,通过用来存储不会发生变更的数据,例如订单号,身份证号等。

<dubbo:reference id="someService" interface="com.test.SomeService" cache="true"/>

服务延迟暴露

<dubbo:service interface="" ref="" delay="500"/>

源码

配置解析

dubbo最常用的形式是与spring结合,使用xml进行配置,以xml为例看一下配置的解析过程。解析dubbo配置还要从spring说起,在spring容器启动时,首先创建Bean工程DefaultLIstableBeanFactory,然后定位配置文件,转换成Resource对象交给BeanDefinitionReader,BeanDefinitionReader将其转换成dom4j的Element对象,委托给BeanDefinitionDocumentReader,然后对Element对象进行逐行解析,当解析到<dubbo:xxx>标签时,就会根据spring配置文件中的名称空间,找到DubboNameSpaceHandler,最终调用DubboBeanDefinitionParser中的parse方法,将标签解析成BeanDefintion,并通过BeanDefinitionRegistry最终注册到容器中,供初始化使用。

public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    public DubboNamespaceHandler() {
    }

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

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

服务暴露

ServiceBean -> ContextRefreshEvent -> export() -> checkParameters() -> nvoker(proxyFactory.getInvoker) -> protocol.export(invoker);

服务暴露开始于ServiceBean(DubboBeanDefinitionParser会将<dubbo:service>解析成ServiceBean对象),由于ServiceBean实现了spring的ApplicationListener接口,监听容器启动完成事件,所以,当容器初始化完成后,就会触发服务暴露。

public void onApplicationEvent(ContextRefreshedEvent event) {
        if (this.isDelay() && !this.isExported() && !this.isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + this.getInterface());
            }

            this.export();
        }

    }

启动日志:
[11/09/19 08:44:52:052 CST] main  INFO config.AbstractConfig:  [DUBBO] The service ready on spring started. service: com.learn.dubbo.chapter1.echo.api.EchoService, dubbo version: 2.6.3, current host: 192.168.199.238

紧接着就是一些列的参数检查,最终走到doExportUrls方法

private void doExportUrls() {
        List<URL> registryURLs = this.loadRegistries(true);
        Iterator i$ = this.protocols.iterator();
        while(i$.hasNext()) {
            ProtocolConfig protocolConfig = (ProtocolConfig)i$.next();
            this.doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

这里的Iterator主要是用来支持dubbo的多协议暴露,循环出来的每个ProtocolConfig对象就是配置文件中的<dubbo:protocol/>标签。

下面在来看两条启动日志:

[11/09/19 08:44:52:052 CST] main  INFO config.AbstractConfig:  [DUBBO] Export dubbo service com.learn.dubbo.chapter1.echo.api.EchoService to local registry, dubbo version: 2.6.3, current host: 192.168.199.238
[11/09/19 08:44:52:052 CST] main  INFO config.AbstractConfig:  [DUBBO] Export dubbo service com.learn.dubbo.chapter1.echo.api.EchoService to url dubbo://192.168.199.238:20880/com.learn.dubbo.chapter1.echo.api.EchoService?accepts=10&anyhost=true&application=echo-provider&bind.ip=192.168.199.238&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.learn.dubbo.chapter1.echo.api.EchoService&methods=echo&pid=2315&revision=0.0.1&side=provider&timestamp=1568162692238&version=0.0.1, dubbo version: 2.6.3, current host: 192.168.199.238

这说明dubbo将同一个服务同时暴露到了本地和远程,暴露的本地的目的是为了解决本地调用的问题,也就是当前服务即是消费者也是提供者,为了避免本地调用过程中跨网络请求,所以把一个服务暴露到远程的同时也暴露到了本地。

Invoker<?> invoker = proxyFactory.getInvoker(this.ref, this.interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
this.exporters.add(exporter);

由于Protocol本身也是一个SPI扩展点,当发布到本地时,得到的是一个InjvmInvoker实例,最后通过InjvmProtocol发布。对于远程服务则是由DubboProtocol通过NettyService进行远程发布。

发布完成以后就是向服务中心注册服务,从provider启动日志中也可以看出来

11/09/19 08:44:52:052 CST] main  INFO config.AbstractConfig:  [DUBBO] Register dubbo service com.learn.dubbo.chapter1.echo.api.SearchBalanceService url injvm://192.168.199.238:59854/com.learn.dubbo.chapter1.echo.api.SearchBalanceService?anyhost=true&application=echo-provider&bind.ip=192.168.199.238&bind.port=59854&dubbo=2.0.2&generic=false&group=wechat&interface=com.learn.dubbo.chapter1.echo.api.SearchBalanceService&methods=searchBalance&notify=false&pid=2315&register=false&side=provider&timestamp=1568162692952 to registry registry://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=echo-provider&dubbo=2.0.2&pid=2315&registry=zookeeper&timestamp=1568162692930, dubbo version: 2.6.3, current host: 192.168.199.238
[11/09/19 08:44:52:052 CST] main  INFO zookeeper.ZookeeperRegistry:  [DUBBO] Subscribe: provider://192.168.199.238:59854/com.learn.dubbo.chapter1.echo.api.SearchBalanceService?anyhost=true&application=echo-provider&category=configurators&check=false&dubbo=2.0.2&generic=false&group=wechat&interface=com.learn.dubbo.chapter1.echo.api.SearchBalanceService&methods=searchBalance&notify=false&pid=2315&register=false&side=provider&timestamp=1568162692952, dubbo version: 2.6.3, current host: 192.168.199.238
[11/09/19 08:44:52:052 CST] main  INFO zookeeper.ZookeeperRegistry:  [DUBBO] Notify urls for subscribe url provider://192.168.199.238:59854/com.learn.dubbo.chapter1.echo.api.SearchBalanceService?anyhost=true&application=echo-provider&category=configurators&check=false&dubbo=2.0.2&generic=false&group=wechat&interface=com.learn.dubbo.chapter1.echo.api.SearchBalanceService&methods=searchBalance&notify=false&pid=2315&register=false&side=provider&timestamp=1568162692952, urls: [empty://192.168.199.238:59854/com.learn.dubbo.chapter1.echo.api.SearchBalanceService?anyhost=true&application=echo-provider&category=configurators&check=false&dubbo=2.0.2&generic=false&group=wechat&interface=com.learn.dubbo.chapter1.echo.api.SearchBalanceService&methods=searchBalance&notify=false&pid=2315&register=false&side=provider&timestamp=1568162692952], dubbo version: 2.6.3, current host: 192.168.199.238
public <T> Exporter<T> export(Invoker<T> originInvoker) throws RpcException {
        RegistryProtocol.ExporterChangeableWrapper<T> exporter = this.doLocalExport(originInvoker);
        URL registryUrl = this.getRegistryUrl(originInvoker);
        Registry registry = this.getRegistry(originInvoker);
        URL registedProviderUrl = this.getRegistedProviderUrl(originInvoker);
        boolean register = registedProviderUrl.getParameter("register", true);
        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
        if (register) {
            #向zookeeper注册provider的url,在providers下面
            this.register(registryUrl, registedProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }
        #订阅自己	
        URL overrideSubscribeUrl = this.getSubscribedOverrideUrl(registedProviderUrl);
        RegistryProtocol.OverrideListener overrideSubscribeListener = new RegistryProtocol.OverrideListener(overrideSubscribeUrl, originInvoker);
        this.overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        return new RegistryProtocol.DestroyableExporter(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }

注册的过程是首先向/providers下注册当前服务,然后监听自己,同时监听routers/configuators等,用于服务治理。

服务引用

getObject() -> init() -> checkParameters() -> createProxy() -> getInvoker() -

服务引用其实与服务暴露一样,也开始于对<dubbo:reference>的解析,这个标签会被DubboBeanDefinitionParser解析成ReferenceBean,这个ReferenceBean是一个FactoryBean,当用@Autowired注入相关的接口依赖时,就会调用ReferenceBean的getObject方法(这个方法是覆盖FactoryBean中的方法),getObject方法首先会尝试获取被注入的实例,如果为空,则创建,创建过程就设计到了dubbo层的proxyFactory和Invoker等组件。

public synchronized T get() {
        if (this.destroyed) {
            throw new IllegalStateException("Already destroyed!");
        } else {
            if (this.ref == null) {
                this.init();
            }
            return this.ref;
        }
    }

其中init()方法就是初始化ref的入口,接下来就会检查配置参数,通过RegistryProtocol从注册中心拉去相关配置,创建Invoker List,然后将这些Invoker list 合并到Cluster,默认Cluster为FailoverCluster,然后向注册中心的/consumers中注册自己,同时订阅其他目录,例如providers/configuators/routers等。

SPI原理

Java SPI的不足:

  • 一次性加载全部扩展点,性能较差
  • 不能按需获取实例

Dubbo SPI的改进

  • 增加缓存(缓存Class对象及对应的实例对象)
  • 能够按key获取实例对象
  • 增加IoC和Aop实现
  • 能够制定默认实现

加载目录,优先级从低到高

  • META-INF/dubbo/internal
  • META-INF/dubbo
  • META-INF/services

Dubbo SPI Aop的实现原理:
在接口实现中,如果某个实现类的构造函数依赖于这个接口时,那么这个类就会被视为包装类。加载时,会将其单独存放在一个缓存对象中,当构造其他非包装类的实例对象时,就会依次用包装类进行包装。

依赖注入的实现原理:
dubbo spi的依赖注入是利用setter方法完成的,如果某个实现类里声明了setXxx方法,那么在对该类进行实例化时,就会解析setter方法,提取出xxx作为key,利用ExtensionLoader进行加载,然后利用反射调用这个setter方法进行属性注入。

另外dubbo spi还可以在@SPI注解中制定当前接口的默认扩展点实现的key,例如@SPI(“dubbo”)。

同时还提供了@Adaptive注解,用来实现基于URL的自适应扩展点。 当@Adaptive被添加在类上时,当前实现类会被认为是默认的扩展点实现,并不会进行动态的自适应处理,但是如果@Adaptive被添加在方法上,那么在获取扩展点实例时,首先需要传入URL参数,然后dubbo会利用动态编译技术生成一个$Adaptive结尾的类,里面的主要逻辑是,从URL参数中获取@Adaptive(“key”)中key对应的value,然后再利用ExtensionLoader加载这个value对应的bean对象,最终调用时,实际上就调用到了value对应的扩展点实现。

另外还要一个需要注意的地方,由于ExtensionFactory实际上也是一个SPI扩展点,所以,dubbo提供了三种ExtensionFactory扩展点的实现,分别是AdaptiveExtensionFactory、SpiExtensionFactory和SpringExtensionFactory,第一个是默认的实现,类声明上具有@Adaptive注解,里面持有了后两种实现,当从中获取实例对象时,会同时遍历这两个Factory,由于SpringExtensionFactory内部持有了ApplicationContext,所以也同时兼容了从spring容器中获取实例对象。

猜你喜欢

转载自blog.csdn.net/zhangdong2012/article/details/100714812