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. [RHEL8]安装Docker Problem: package docker-ce-3:19.03.6-3.el7.x86_64 requires containerd.io

    系统环境 # cat /etc/redhat-release Red Hat Enterprise Linux release 8.0 (Ootpa) 安装依赖 # yum install -y yu ...

  2. asp.net MVC项目开发之统计图的使用(前言)

    接触这个项目,是项目组长已经完成了多数需求,并且有2个项目需要完工的情况下,让我加入,给了我2个表格,让我去设计出统计图.      第一次做统计图,可以说没有任何经验,不知道该如何下手,表格的数据量 ...

  3. 仅需60秒,使用k3s创建一个多节点K8S集群!

    作者: Dawid Ziolkowski丨Container Solution云原生工程师 最近,我一直在Kubernetes上进行各种测试和部署.因此,我不得不一次又一次创建和销毁Kubernete ...

  4. jps jmap 的使用

    第一部分: 源代码: public class ObjectText { int a=0; public static void main(String[] args) { ObjectText ot ...

  5. 「Flink」使用Java lambda表达式实现Flink WordCount

    本篇我们将使用Java语言来实现Flink的单词统计. 代码开发 环境准备 导入Flink 1.9 pom依赖 <dependencies> <dependency> < ...

  6. 使用QT显示OpenCV读取的图片

    目录 1. 概述 2. 实现 2.1. 代码 2.2. 解析 3. 结果 1. 概述 OpenCV自带了一部分常用的GUI功能,但是更多的图像处理功能需要其他GUI框架来辅助实现,这里通过QT来显示O ...

  7. OpenCV检测Marker位姿

    Marker检测采用小觅相机,可以实时检测Marker的位置和姿态,效果如下: 参考代码如下: #include "pch.h" #include <Eigen/Dense& ...

  8. P2746 [USACO5.3]校园网Network of Schools [tarjan缩点]

    题目描述 一些学校连入一个电脑网络.那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”).注意即使 BB 在 AA 学校的分发列表中,AA 也不一定在 BB 学校的列表中. ...

  9. linux 下生成 ssh 公私钥

    生成命令 ssh-keygen -t rsa -C "763941715@qq.com" 密匙目录 cd ~/.ssh 公钥 id_rsa.pub 私钥 id_rsa

  10. CSS操作

    CSS 与 JavaScript 是两个有着明确分工的领域,前者负责页面的视觉效果,后者负责与用户的行为互动.但是,它们毕竟同属网页开发的前端,因此不可避免有着交叉和互相配合. 1. 使用JavaSc ...