白话DUBBO原理,通俗易记,再也不怕面试时讲不清楚了
liuian 2025-06-15 17:36 59 浏览
现在的各种面试免不了要问些中间件,尤其是互联网公司,更注重获选人对中间件的掌握情况。在中间件中,有一大类是关于RPC框架的,Dubbo即是阿里出品的一款很著名的RPC中间件,很多互联网公司都在用,面试也是经常被问到的。了解其原理在面试时才能有的放矢,游刃有余。现在关于Dubbo原理讲的有很多,本文以RPC的本质为视角来白话其原理,希望能够帮助大家更好理解和记忆。
RPC的本质
RPC的本质是什么?通俗地讲RPC就是要解决远程服务间的调用问题,也就是管理服务配置并提供便捷可靠高效的服务间调用。
我们来看看dubbo的定义:dubbo是一个分布式的服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
通过定义,我们提出以下几个问题,并通过这几个问题来介绍DUBBO。
- DUBBO的实现思想(总体架构)什么?
- DUBBO是如何实现透明化使用的?
- 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很好地将配置(dubbo:provider和dubbo:reference标签定义的bean)转化成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。
配置与实际调用的关联
我们知道ServiceBean和ReferenceBean,也知道了远程调用是通过Protocol交互的,但是ServiceBean、ReferenceBean和Protocol间又是怎么关联的呢?
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(R)istry=zookeeper×tamp=xxx
List<URL> registryURLs = loadRegistries(true);
// 这里的protocols是List<ProtocolConfig>,按指定支持的协议暴露服务
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
...
// 将接口实体类引用转化成Invoker
Invoker<?> 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 是会话域,它持有调用过程中的变量,比如方法名,参数等。
相关推荐
- 搭建一个20人的办公网络(适用于20多人的小型办公网络环境)
-
楼主有5台机上网,则需要一个8口路由器,组网方法如下:设备:1、8口路由器一台,其中8口为LAN(局域网)端口,一个WAN(广域网)端口,价格100--400元2、网线N米,这个你自己会看了:)...
- 笔记本电脑各种参数介绍(笔记本电脑各项参数新手普及知识)
-
1、CPU:这个主要取决于频率和二级缓存,频率越高、二级缓存越大,速度越快,现在的CPU有三级缓存、四级缓存等,都影响相应速度。2、内存:内存的存取速度取决于接口、颗粒数量多少与储存大小,一般来说,内...
- 汉字上面带拼音输入法下载(字上面带拼音的输入法是哪个)
-
使用手机上的拼音输入法打成汉字的方法如下:1.打开手机上的拼音输入法,在输入框中输入汉字的拼音,例如“nihao”。2.根据输入法提示的候选词,选择正确的汉字。例如,如果输入“nihao”,输...
- xpsp3安装版系统下载(windowsxpsp3安装教程)
-
xpsp3纯净版在采用微软封装部署技术的基础上,结合作者的实际工作经验,融合了许多实用的功能。它通过一键分区、一键装系统、自动装驱动、一键设定分辨率,一键填IP,一键Ghost备份(恢复)等一系列...
- 没有备份的手机数据怎么恢复
-
手机没有备份恢复数据方法如下1、使用数据线将手机与电脑连接好,在“我的电脑”中可以看到手机的盘符。 2、将手机开启USB调试模式。在手机设置中找到开发者选项,然后点击“开启USB调试模式”。 3、...
- 电脑怎么激活windows11专业版
-
win11专业版激活方法有多种,以下提供两种常用的激活方式:方法一:使用激活密钥激活。在win11桌面上右键点击“此电脑”,选择“属性”选项。进入属性页面后,点击“更改产品密钥或升级windows”。...
- 华为手机助手下载官网(华为手机助手app下载专区)
-
华为手机助手策略调整,已不支持从应用市场下载手机助手,目前华为手机助手是需要在电脑上下载或更新手机助手到最新版本,https://consumer.huawei.com/cn/support/his...
- 光纤线断了怎么接(宽带光纤线断了怎么接)
-
宽带光纤线断了可以重接,具体操作方法如下:1、光纤连接的时候要根据束管内,同色相连,同芯相连,按顺序进行连接,由大到小。一般有三种连接方法,分别是熔接、活动连接和机械连接。2、连接的时候要开剥光缆,抛...
- win7旗舰版和专业版区别(win7旗舰版跟专业版)
-
1、功能区别:Win7旗舰版比专业版多了三个功能,分别是Bitlocker、BitlockerToGo和多语言界面; 2、用途区别:旗舰版的功能是所有版本中最全最强大的,占用的系统资源,...
- 万能连接钥匙(万能wifi连接钥匙下载)
-
1、首先打开wifi万能钥匙软件,若手机没有开启WLAN,就根据软件提示打开WLAN开关;2、打开WLAN开关后,会显示附近的WiFi,如果知道密码,可点击相应WiFi后点击‘输入密码’连接;3、若不...
- 雨林木风音乐叫什么(雨林木风是啥)
-
雨林木风的创始人是陈年鑫先生。陈年鑫先生于1999年创立了雨林木风公司,其初衷是为满足中国市场对高品质、高性能电脑的需求。在陈年鑫先生的领导下,雨林木风以技术创新、产品质量和客户服务为核心价值,不断推...
- aics6序列号永久序列号(aics6破解序列号)
-
关于AICS6这个版本,虽然是比较久远的版本,但是在功能上也是十分全面和强大的,作为一名平面设计师的话,AICS6的现有的功能已经能够应付几乎所有的设计工作了……到底AICC2019的功能是不是...
- 手机可以装电脑系统吗(手机可以装电脑系统吗怎么装)
-
答题公式1:手机可以通过数据线或无线连接的方式给电脑装系统。手机安装系统需要一定的技巧和软件支持,一般需要通过数据线或无线连接的方式与电脑连接,并下载相应的软件和系统文件进行安装。对于大部分手机用户来...
- 一周热门
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
