Dubbo的服务暴露是一个重要的特性,了解其机制很重要。之前有很多人写了有关的源代码分析,在本文中不再重新分析。官方文档中的一篇写的就很好,本文主要是有关内容进行补充与总结。

传送门:服务导出

为什么要服务暴露

服务暴露分为远程暴露和本地暴露。在远程服务暴露中会将服务信息上传到注册中心。这时客户端要调用某个服务时会从注册中心找到该服务的远程地址等信息。然后客户端根据这个地址进行远程调用。服务端接收到远程调用请求后会最终调用getInvoker()方法进行查找对用的invoker。在getInvoker()方法中会从一个HashMap中进行查找,如果在这个Map中查找不到就会抛出异常。在远程服务暴露中,会按照规则将实例Invoker存储在HashMap中,其中Key名包含端口、接口名、接口版本和接口分组。所以进行服务暴露很重要。

本地服务暴露是暴露在JVM中,不需要远程通信。Dubbo会默认把远程服务用injvm协议再暴露一份。

为什么会有本地服务暴露

在Dubbo中,一个服务可以即是provider,又是Consumer,因此就存在它自己调用自己服务的时候,如果再通过网络去访问,那么就是舍近求远,因此有了本地暴露服务这个设计。消费者可以直接消费同一个JVM内部的服务,避免了跨网络进行远程通信。

服务暴露起点

我们会通过XML或注解的方式来指定要暴露的服务。l例子如下:

<bean id=“xxxService” class=“com.xxx.XxxServiceImpl” />
<!-- 增加暴露远程服务配置 -->
<dubbo:service interface=“com.xxx.XxxService” ref=“xxxService” />

这时会创建出一个ServiceBean对象。

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, BeanNameAware,
ApplicationEventPublisherAware
public class ServiceConfig<T> extends ServiceConfigBase<T>
public abstract class ServiceConfigBase<T> extends AbstractServiceConfig {
private static final long serialVersionUID = 3033787999037024738L;
protected String interfaceName;
//要暴露服务类的接口类
protected Class<?> interfaceClass;
//实现类引用
protected T ref;
//服务名
protected String path;
protected ProviderConfig provider;
protected String providerIds;
protected volatile String generic;
protected ServiceMetadata serviceMetadata;

ServiceBean和Spring有关,它继承了InitializingBean和ApplicationEvent。在Bean初始化完成后会调用InitializingBean.afterPropertiesSet方法来执行服务暴露的准备工作。在Spring的context完成初始化后,会触发ApplicationEventListener事件进行服务暴露,会执行onApplicationEvent 方法。这时服务服务暴露就开始了。

public void onApplicationEvent(ContextRefreshedEvent event) {
// 是否有延迟导出 && 是否已导出 && 是不是已被取消导出
if (isDelay() && !isExported() && !isUnexported()) {
// 导出服务
export();
}
}

整体流程

Dubbo真正的服务暴露入口是ServiceConfig#doExport()方法。首先ServiceConfig类拿到对外提供服务的实际类ref,然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化。然后就是把Invoker通过具体的协议(比如Bubbo)转化成Exporter



参考了简书肥朝的一篇文章: dubbo源码解析-服务暴露原理

  • 调用ServiceConfig中的export方法,对配置进行检查与更新,例如provider是否为空,注册中心是否为空,protocols是否为空。然后检测是否应该暴露,如果不应该暴露,则直接结束;然后检测是否配置了延迟加载,如果是,则使用定时器来实现延迟加载的目的。
  • 调用expoer方法里面的doExport()方法。通过exported变量判断是否暴露,如果为true,直接返回;否则先设置为true,然后执行doExportUrls()方法。
  • 调用doExportUrls()方法,先通过loadRegistries加载注册中心链接,然后遍历ProtocolConfig集合,它是用户指定的协议集合(比如Dubbo、REST),在里面执行doExportUrlsFor1Protocol(protocolConfig, registryURLs)方法。
  • 执行doExportUrlsFor1Protocol(protocolConfig, registryURLs)方法,刚开始是组装URL。(1)它把metrices、application、module、provider、protocol等所有配置都放入到map中,(2) 通过反射获取interfaceClass的方法列表,先做签名校验,判断该服务是否有配置的方法存在,然后该方法签名是否有这个参数存在,都核对成功才将method的配置加入到map中;(3)将范化调用、版本号、method或者methods、token等信息加入到map;(4)获取服务暴露地址和端口号,利用map内数据组装成URL。
  • 然后根据url中的scope参数决定服务导出方式。scope=none,不导出服务;scope!=remote,导出到本地;scope!=local,导出到远程。
  • 执行proxyFactory.getInvoker(ref, (Class)interfaceClass, url)方法,来获取invoker,Dubbo默认的ProxyFactory实现类是JavasistProxyFacoty方法。

这时,第一大步服务转化成Invoker已经完成,然后执行第二大步Invoker转化成Exporter

先介绍本地服务的暴露机制

  • 调用exportLocal(url)。首先判断URL的协议头如果等于injvm,说明已经导出到本地了,无需再次导出。如果不是,则创建一个新的URL并将协议头设置为injvm,并另外设置主机名和端口。然后创建invoker,并调用InjvmProtocalexport()方法暴露服务。
  • InjvmProtocolexport方法仅创建一个InjvmExporter,无其他逻辑。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
exporterMap.put(key, this);
}

然后介绍远程服务暴露

如果有注册中心,服务暴露后需要向注册中心注册服务信息

如果没有注册中心,直接暴露服务。

有注册中心的暴露

  1. 调用doLocalExport()方法暴露服务。
  2. 向注册中心注册服务
  3. 向注册中心进行订阅ovrride数据
  4. 创建并返回DestroyableExporter

    调用doLocalExport()方法暴露服务
  • 首先根据invoker得到key,从bounds缓存变量中尝试获取exporter,如果获取不到,则调用Protocal的export方法,默认的协议是dubbo,所以调用的DubboProtocol的export方法。
  • 调用DubboProtocol的export方法,首先从invoker中获取对用的URL,然后根据服务组名、服务名、服务版本号和端口组成key,然后创建一个DubboExporter。
// demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
String key = serviceKey(url);
// 创建 DubboExporter
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// 将 <key, exporter> 键值对放入缓存中
exporterMap.put(key, exporter);
  • 调用openServer(url)方法。首先根据url获取host:port,用于标识当前的服务器实例。在同一台机器上,同一个端口仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时调用reset方法重置一些服务器的配置;如果没有则调用createServer(url)方法创建一个服务器实例。
  • 调用createServer(url)方法,有三个核心的逻辑,首先检测是否存在server参数所代表的Transporter拓展,即网络传输方式(如Netty, Mina),如果没有,则抛出异常;然后通过Exchangers.bind(url, requestHandler)方法创建服务器实例;最后是检测是否支持client参数所表示的Transporter拓展,不存在也是抛出异常。
  • 调用Exchangers.bind(url, requestHandler)方法,里面又调用了下面几个方法。只保留了主要逻辑部分。
getExchanger(url).bind(url, handler);

public static Exchanger getExchanger(URL url) {
String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
return getExchanger(type);
}
public static Exchanger getExchanger(String type) {
return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
}

Exchange层是为了封装请求/响应模式,例如:把同步请求转化为异步请求。默认的扩展点实现类是HeaderExchanger

  • 调用HeaderExchanger的bind方法
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
// 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器
handler = new ChannelHandlerDispatcher(handlers);
}
// 获取自适应 Transporter 实例,并调用实例方法
return getTransporter().bind(url, handler);
}
  • 根据自适应扩展机制动态创建一个Transporter,Dubbo默认是NettyTransporter。
  • 调用NettyTransporter.bind(URL, ChannelHandler)方法。创建一个NettyServer实例。
  • 调用NettyServer.doOPen()方法,服务器被开启,服务也被暴露出来了。

Dubbo服务暴露分析的更多相关文章

  1. 《Duubo系列》-Dubbo服务暴露过程

    我今天来就带大家看看 Dubbo 服务暴露过程,这个过程在 Dubbo 中其实是很核心的过程之一,关乎到你的 Provider 如何能被 Consumer 得知并调用. 今天还是会进行源码解析,毕竟我 ...

  2. Dubbo服务暴露源码解析②

    目录 0.配置解析 1.开始export 2.组装URL 3.服务暴露 疑问解析 ​ 先放一张官网的服务暴露时序图,对我们梳理源码有很大的帮助.注:不论是暴露还是导出或者是其他翻译,都是描述expor ...

  3. Dubbo 服务暴露注册流程

    Dubbo的应用会在启动时完成服务注册或订阅(不论是生产者,还是消费者)如下图所示. 图中小方块Protocol, Cluster, Proxy, Service, Container, Regist ...

  4. dubbo服务暴露

    想熟悉dubbo源码,首先要知道dubbo extensionLoader,而dubbo的这种扩展机制,是根据java spi衍生而来. 这是基础,但是我放在后面说明. 一:dubbo demo pr ...

  5. dubbo系列四、dubbo服务暴露过程源码解析

    一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...

  6. dubbo服务暴露过程

    所谓服务暴露最终做的事情:绑定网络端口,开启serversokect服务以接收外部请求 服务暴露时序图 本地暴露 远程暴露 整体总结 dubbo服务提供者暴露服务的主过程:首先 ServiceConf ...

  7. dubbo服务暴露原理-远程暴露

    1.与本地暴露相比,远程暴露也大同小异 我们已经到了第三个关键词Procotol我们来看看他的继承体系图 按照经典图的路线,我们下一个关键词应该就是Server了,从方法名openServer(url ...

  8. dubbo服务暴露原理

    1.发布流程 暴露本地服务 暴露远程服务 启动netty 连接zookeeper 到zookeeper注册 监听zookeeper 2.官方文档 3.看输出日志,就会发现在暴露本地服务之前,有一句很重 ...

  9. (转)Dubbo服务暴露过程源码分析

    参考

随机推荐

  1. 解决github图片不显示的问题

    修改hosts C:\Windows\System32\drivers\etc\hosts 在文件末尾添加: # GitHub Start 192.30.253.112 Build software ...

  2. 【转载】Linux设备树(Device Tree)机制

    转:Linux设备树(Device Tree)机制   目录 1. 设备树(Device Tree)基本概念及作用2. 设备树的组成和使用 2.1. DTS和DTSI 2.2. DTC 2.3. DT ...

  3. [WPF 自定义控件]自定义一个“传统”的 Validation.ErrorTemplate

    1. 什么是Validaion.ErrorTemplate 数据绑定模型允许您将与您Binding的对象相关联ValidationRules. 如果用户输入的值无效,你可能希望在应用程序 用户界面 ( ...

  4. debian 10安装英伟达独显驱动

    我的显卡是GTX1050TI,刚安装好Debian 10的时候启动会黑屏,无法进入系统,解决办法是在grub界面,按e修改启动参数,在启动参数那一行(一般会包含quiet)后面加上 nouveau.m ...

  5. STT-MRAM万能存储器芯片

    传统存储器的技术局限以及不断缩小的制造尺寸所带来的巨大挑战促使科研人员开始寻找新一代存储器件,它应具有接近静态存储器的纳秒级读写速度,具有动态存储器甚至闪存级别的集成密度和类似Flash的非易失性存储 ...

  6. 如何优雅的封装requests

    搭建接口自动化测试框架,一般都要对post/get请求做封装. 一般的封装过程是, class MyRequest: def my_post(): """do somet ...

  7. 从零开始一个个人博客 by asp.net core and angular(一)

    这是一个个人叙述自己建设博客的帖子,既然是第一篇那肯定是不牵扯代码了,主要讲一下大体的东西,微软最新的web框架应该就数asp.net core 3.1了这是一个长期支持版,而且是跨平台又开源版本,所 ...

  8. python学习----文件的操作(2)

    1.文件指针的操作 f=open("yesterday","r",encoding="utf-8") #文件句柄 #文件内指针的操作 pri ...

  9. vue中this在回调函数中的使用

    this在各类回调中使用: 如果是普通函数是没法使用的 所以需要先将this.变量赋值给新的变量,然后才能在回调函数中使用 例如: refund: function (id) { if (!this. ...

  10. Android中通过Fragment进行简单的页面切换

    首先是activity中的布局 <?xml version="1.0" encoding="utf-8"?> <androidx.constr ...