RPC的本质是什么?通俗地讲RPC就是要解决远程服务间的调用问题,也就是管理服务配置并提供便捷可靠高效的服务间调用。

我们来看看dubbo的定义:dubbo是一个分布式的服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。

通过定义,我们提出以下几个问题,并通过这几个问题来介绍DUBBO。

  1. DUBBO的实现思想(总体架构)什么?
  2. DUBBO是如何实现透明化使用的?
  3. DUBBO中服务配置与实际调用是怎么结合的(如何实现远程服务调用)?

总体架构

先附DUBBO官网的架构图,

简单解释下架构图,

DUBBO分为四个模块,分别为:注册中心(Registry)、提供者(Provider)、消费者(Consumer)和监控(Monitor)。

  • 注册中心(Registry):可以是zookeeper、redis、multicast、simple(官方推荐使用Zookeeper);
  • 提供者(Provider):服务启动时,Provider引用容器中的服务,并向Registry注册服务,同时暴露服务(Consumer是直接和Provider通讯实现服务调用的)。
  • 消费者(Consumer):服务启动时,Consumer向Registry订阅服务,如果没有订阅到自己想获得的服务,它会不断的尝试订阅。新的服务注册到注册中心以后,注册中心会将这些服务通过notify到消费者。Consumer直接调用Provider提供的服务。
  • 监控(Monitor):Consumer和Provider会通过异步的方式定时向Monitor发送消息,报告服务的状态。Monitor在整个架构中是可选的,Monitor功能需要单独配置,不配置或者配置后挂掉并不会影响服务的调用。

所以,DUBBO的实现思路是通过注册中心实现服务的动态注册与发现,Provider暴露服务,Consumer直接和Provider通讯实现服务间的调用的。

透明化使用

本节需要对Spring的Bean加载机制有一定的了解,如果大家感兴趣,后续我可以再详细介绍

用过DUBBO的,应该都知道,使用DUBBO时,只需要进行一些Spring的配置即能享受DUBBO远程调用的便利。显而易见,DUBBO就是利用的Spring的扩展性,通过全Spring配置方式,使得用户能够透明化接入应用,并且对应用没有任何API侵入。

Dubbo通过扩展Spring Schema完成自定义bean的IoC容器注入。

  • 自定义了XML Schema文件META-INF/dubbo.xsd描述自定义元素;
  • 自定义继承自org.springframework.beans.factory.xml.NamespaceHandlerSupport抽象类的DubboNamespaceHandler处理器类;
  • 自定义实现了org.springframework.beans.factory.xml.BeanDefinitionParser接口的DubboBeanDefinitionParser解析器类;

这样,DUBBO很好地将配置转化成bean,基于Spring容器的应用能够方便地使用。

public class DubboNamespaceHandler extends NamespaceHandlerSupport {static {Version.checkDuplicate(DubboNamespaceHandler.class);}public void init() {registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));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));}}

如上,DubboNamespaceHandler实现NamespaceHandlerSupport接口,通过注册DubboBeanDefinitionParser解析器,将对应配置解析后转化成ApplicationConfig、ModuleConfig、RegistryConfig、MonitorConfig、ProviderConfig、ConsumerConfig、ProtocolConfig、AnnotationBean、ServiceBean和ReferenceBean。

我们着重看ServiceBean和ReferenceBean,这两个bean是服务的调用bean,业务上我们调用的服务就是通过这两个bean来交互的。

服务调用

在看ServiceBean和ReferenceBean前,我们先看一张图,

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

图中我们可以看出,Consumer和Provider之间是通过Protocol交互的,不错,DUBBO可以自定义Protocol完成服务调用(Protocol是什么时候初始化,什么时候调用的,可以看后续对ReferenceBean和ServiceBean介绍)。

@SPI("dubbo")
public interface Protocol {/*** 获取缺省端口,当用户没有配置端口时使用。* * @return 缺省端口*/int getDefaultPort();/*** 暴露远程服务:<br>* 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>* 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>* 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>* * @param <T> 服务的类型* @param invoker 服务的执行体* @return exporter 暴露服务的引用,用于取消暴露* @throws RpcException 当暴露服务出错时抛出,比如端口已占用*/@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;/*** 引用远程服务:<br>* 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>* 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>* 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>* * @param <T> 服务的类型* @param type 服务的类型* @param url 远程服务的URL地址* @return invoker 服务的本地代理* @throws RpcException 当连接服务提供方失败时抛出*/@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;/*** 释放协议:<br>* 1. 取消该协议所有已经暴露和引用的服务。<br>* 2. 释放协议所占用的所有资源,比如连接和端口。<br>* 3. 协议在释放后,依然能暴露和引用新的服务。<br>*/void destroy();
}

Protocol的接口有export方法和refer方法,export方法负责暴露远程Invoker服务,而refer方法获取远程服务的Invoker实现。

这里,Invoker是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

public interface Invoker<T> extends Node {/*** get service interface.* * @return service interface.*/Class<T> getInterface();/*** invoke.* * @param invocation* @return result* @throws RpcException*/Result invoke(Invocation invocation) throws RpcException;}

Invoker接口中的invoke方法需要一个Invocation类型的参数,很容易想象,Invocation是就是会话域,它持有调用过程中的变量,比如方法名,参数等。

Protocol、Invoker和Invocation是Dubbo中有3个很重要的概念,一切都是围绕这三个展开的。

现在我们继续来看ServiceBean和ReferenceBean。

配置与实际调用的关联

Consumer之ReferenceBean


ReferenceBean实现了FactoryBean接口,用于根据加载的配置(xml中dubbo:reference节点的配置或者@Reference注解的配置)创建bean实例。创建实例的过程就是创建引用接口的代理,ReferenceConfig中有个ref属性用于引用该接口的代理。

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {public Object getObject() throws Exception {return get();}
}public class ReferenceConfig<T> extends AbstractReferenceConfig {private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();// 接口代理类引用private transient volatile T ref;private transient volatile Invoker<?> invoker;public synchronized T get() {if (destroyed){throw new IllegalStateException("Already destroyed!");}if (ref == null) {init();}return ref;}private void init() {...// 创建引用的代理,这里的map是一些配置信息,如interface、methods、retried、application、dubbo(版本)等信息ref = createProxy(map);...}private T createProxy(Map<String, String> map) {...// Protocol映射的远程服务的Invoker,有对应Protocol有对应的Invoker实现invoker = refprotocol.refer(interfaceClass, urls.get(0));... // 根据invoker创建服务代理return (T) proxyFactory.getProxy(invoker);}
}

根据代码,我们可以发现ReferenceBean在get Bean的时候就是调用ReferenceConfig的get方法,而get方法最终调用的是createProxy方法,着重看这个方法,会先通过refprotocol的refer方法获得Invoker,refprotocol实际上就是通过ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()创建的Protocol,而后再由Invoker创建该接口的代理。这样ReferenceBean中接口类代理的创建过程就很清晰了。

Provider之ServiceBean

ServiceBean实现了ApplicationListener接口,在容器初始化后调用Protocol的export方法暴露服务。

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {public void onApplicationEvent(ApplicationEvent event) {if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {if (isDelay() && ! isExported() && ! isUnexported()) {if (logger.isInfoEnabled()) {logger.info("The service ready on spring started. service: " + getInterface());}export();}}}
}public class ServiceConfig<T> extends AbstractServiceConfig {private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();// 接口实现类引用private T                   ref;public synchronized void export() {...doExport();...}protected synchronized void doExport() {...doExportUrls();...}/*** 广播注册中心暴露发现服务地址**/private void doExportUrls() {// 将registry配置转化成url,例如:registry://host:port/com.alibaba.dubbo.registry.RegistryService?application=xxx&pid=xxx&registry=zookeeper&timestamp=xxxList<URL> registryURLs = loadRegistries(true);// 这里的protocols是List<ProtocolConfig>,按指定支持的协议暴露服务for (ProtocolConfig protocolConfig : protocols) {doExportUrlsFor1Protocol(protocolConfig, registryURLs);}}private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {...// 将接口实体类引用转化成InvokerInvoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));Exporter<?> exporter = protocol.export(invoker);exporters.add(exporter);...}
}

根据代码,我们可以发现在Spring在调用onApplicationEvent方法的时候,会调用export方法,而这个方法最终会调用doExportUrlsFor1Protocol方法,方法中很重要的一段逻辑是通过ProxyFactory根据引用的接口对象和拼装的URL生成Invoker,而后通过Protocol暴露出来。Provider暴露一个服务的详细过程如下:

现在我们就可以回答Protocol是如何和服务建立联系的?

ReferenceConfig和ServiceConfig都有一个属性Protocol,服务与Protocol间就是通过这个属性建立联系的。

细心地你会发现ReferenceConfig和ServiceConfig的Protocol属性是static final的,我们知道Dubbo是支持多协议的,那又是怎么实现的呢?

扩展点加载ExtensionLoader

ReferenceConfig和ServiceConfig的Protocol属性赋值是通过ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()赋值的,深入代码,我们可以发现,它返回的是动态生成的实现Protocol接口的类,生成的类会对打了@Adaptive标注的方法重新生成方法,生成的方法调用逻辑是根据传入参数的协议号动态调用对应协议的方法。

public class ExtensionLoader<T> {public T getAdaptiveExtension() {Object instance = cachedAdaptiveInstance.get();if (instance == null) {...instance = createAdaptiveExtension();cachedAdaptiveInstance.set(instance);...}return (T) instance;}private T createAdaptiveExtension() {try {return injectExtension((T) getAdaptiveExtensionClass().newInstance());} catch (Exception e) {throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);}}private Class<?> getAdaptiveExtensionClass() {getExtensionClasses();if (cachedAdaptiveClass != null) {return cachedAdaptiveClass;}return cachedAdaptiveClass = createAdaptiveExtensionClass();}private Class<?> createAdaptiveExtensionClass() {String code = createAdaptiveExtensionClassCode();ClassLoader classLoader = findClassLoader();com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();return compiler.compile(code, classLoader);}private String createAdaptiveExtensionClassCode() {StringBuilder codeBuidler = new StringBuilder();...codeBuidler.append("package " + type.getPackage().getName() + ";");codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");...return codeBuidler.toString();}
}

附.通过ExtensionLoader动态生成的实现Protocol接口的类

package com.alibaba.dubbo.rpc;import com.alibaba.dubbo.common.extension.ExtensionLoader;public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0)throws com.alibaba.dubbo.rpc.Invoker {if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("+ url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.export(arg0);}public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)throws java.lang.Class {if (arg1 == null) throw new IllegalArgumentException("url == null");com.alibaba.dubbo.common.URL url = arg1;String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("+ url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}
}

小结

Dubbo通过扩展Spring Schema完成自定义bean的IoC容器注入。在Spring注入和初始化bean时,通过Protocol和ProxyFactory配合完成服务的暴露和服务代理的创建。这种全Spring的配置方式,使得用户能够透明化接入应用,并且对应用没有任何API侵入。

在Dubbo的服务实际调用中都是围绕着Protocol、Invoker和Invocation三者,这也是DUBBO的核心领域模型:

  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
  • Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
  • Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。

参考

Dubbo官方文档

面试时Dubbo原理记不住?来看看《Dubbo原理浅析——从RPC本质看Dubbo》相关推荐

  1. redis list操作_大厂面试高频Redis,记不住的多操作几次吧

    redis 的基本操作方法 1.redis 的连接 : //实例化redis $redis = new Redis(); //连接$redis->connect('127.0.0.1', 637 ...

  2. kendotabstrip 动态加tab_加你的好友这么多,为什么偏偏记不住我这名微商?

    哥见过许多网友,没做微商前羡慕别人挣钱,一旦入了坑,整个人就焦虑了,微商焦虑的一大特征就是千方百计的群发,和到处学习(没别的办法,卖不出货).哥看在眼里,痛在心里.为此,一如既往分享些好用的微商技巧给 ...

  3. 学习Java时,要记住的知识点太多,记不住怎么办?

    "今天学习,忘记明天",我相信这是每个学习者都会遇到的问题. 我们分析一下原因,逐一解释,然后给出解决方案,最后分享一下学习java的有效方法和途径: 记不住的可能原因 第一个:知 ...

  4. 面试时算法为什么如此重要?高薪,外企,为什么迟迟没有进入?

    为什么明明面试题比工作遇到的算法已经简单那么多,面试的时候面一下算法还是让很多人义愤填膺呢? 收藏人:木立 2014-03-22 | 阅:639  转:0   |   来源   |  分享    13 ...

  5. 面试时经常会被问到的的问题

    面试必备基础题目(虽然不一定常用, 仅用于面试, 面试就是把人搞的都不会然后砍价, 当然您可以讲我可以查资料完成, 但是面试的时候就是没道理的, 起码我是经常看到这些题). 如何把一段逗号分割的字符串 ...

  6. 看完源码记不住,是我记性太差了吗?

    都说大厂面试必问源码,尤其是现在最流行的Java 开发技术--Spring的源码.可很多人看完Spring源码记不住,是记性太差了吗? 当然不是!是因为你没有掌握学习源码的技巧. 看完源码的我- 前段 ...

  7. 英语每日阅读---8、VOA慢速英语(翻译+字幕+讲解):脸肓症患者记不住别人的脸

    英语每日阅读---8.VOA慢速英语(翻译+字幕+讲解):脸肓症患者记不住别人的脸 一.总结 一句话总结: a.neural abnormalities are more widespread:Duc ...

  8. 你都有哪些面试时被虐的经历?

    挖出这个三年前的问题,就为吐槽今天字节跳动的面试. 人们都说,这个世界上有两种人注定单身,一种是太优秀的,另一种是太平凡的. 我一听, 呀?那我这岂不是就不优秀了吗,于是毅然决然和女朋友分了手. 人们 ...

  9. 为什么程序员都很排斥面试时做题?答不出题的程序员会是大牛吗?

    相信很多人都有过这样的经历,就是去面试的时候,先被要求做题. 如果像下面的判断选择还好! 如果是让写个什么算法之类的,那就呵呵了!每天和鼠标握手,还哪会写字啊! 关于这件事,很多小伙伴纷纷吐槽. 网友 ...

最新文章

  1. 03-JDBC学习手册:JDBC中几个重要接口和异常处理
  2. PP生产订单成本的计划、控制和结算
  3. Unable to find 'struts.multipart.saveDir' property setting.
  4. 9-基数排序C实现(待补充插图)
  5. node 版本升级_Node-RED: 自动化事件触发工具的安装与介绍
  6. 从头到尾彻底理解傅里叶变换算法(上)
  7. quartz mysql 报错_7月27 mysql quartz 连接报错
  8. 20个令人称赞的以办公环境为背景的网站作品
  9. 基于Python实现自动慢查询分析,邮件自动发送
  10. NLP--- 将改变你未来沟通方式的7种NLP技术(第二部分)
  11. Setup Factory 安装程序的图标
  12. MySql Server 5.5安装教程
  13. 医院叫号系统与his系统对接(二)
  14. mbedtls入门和使用
  15. python-乌龟吃小鱼(小游戏)
  16. win10显示未安装任何音频输出设备问题解决
  17. 27个最佳免费WordPress主题(2022年精选)
  18. Linux终端欢迎界面
  19. jQuery的id选择器
  20. ESP32s3-EYE ESP-IDF环境搭建Ubuntu18.04 Micropython环境搭建Pycharm 物联网

热门文章

  1. Python模块pathlib
  2. 旅游 - 珠海长隆海洋王国 - 鹦鹉过山车
  3. js删除json中指定的元素
  4. 曾仕强经典语录-《易经的智慧》
  5. fiddler抓包,搞定接口
  6. 证券投资学原理(韩德宗 朱晋)知识点
  7. MySQL里SQL基本语句的使用
  8. Linux命令修改文件名和文件夹名
  9. 关于NorFlash的一点总结
  10. Excel2010数据透视表1