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. 【重新整理】log4j 2的使用

    一 概述 1.1 日志框架 日志接口(slf4j) slf4j是对所有日志框架制定的一种规范.标准.接口,并不是一个框架的具体的实现,因为接口并不能独立使用,需要和具体的日志框架实现配合使用(如log ...

  2. redis教程-基础数据结构

    需要整套redis缓存高可用集群教学视频的加qq:1324981084,本套视频从安装到集群的搭建和源码的解析,从零基础讲解. 一.Redis 有 5 种基础数据结构,分别为:string (字符串) ...

  3. leetcode--js--Median of Two Sorted Arrays

     问题描述: There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of ...

  4. C# 多线程的阻塞和继续-ManaulResetEvent的使用

    在工作中,会遇到需要多线程处理相应的业务需求,最典型的包括Socket的通信. 多线程处理里,就会考虑到,哪个线程先运行,哪个线程后运行的情况. 这里我介绍一下,使用ManualResetEvent类 ...

  5. navicat连接异常 authentication plugin 'caching_sha2_password' 问题解决

    mysql 8.0 默认使用 caching_sha2_password 身份验证机制 -- 从原来的 mysql_native_password 更改为 caching_sha2_password. ...

  6. 批量unzip一大堆压缩文件进行文件查询的办法.

    1. 公司里面开发提交的补丁存在问题. 需要找出来 哪些文件有问题 最简单的办法, 想将一对文件 转移到一个目录里面去 然后创建一个 shell 脚本执行解压缩的操作 for i in `ls *.g ...

  7. window10 cmd 常见命令

    AT 计划在计算机上运行的命令和程序. ATTRIB 显示或更改文件属性. BREAK 设置或清除扩展式 CTRL+C 检查. CACLS 显示或修改文件的访问控制列表(ACLs). CALL 从另一 ...

  8. Visual Studio 2019新建Web项目

    选择创建新项目 选择ASP.NET Web 应用程序,下一步 填好相关信息,位置是保存项目的位置,点击创建 创建你需要的项目项,我们这里选择空项目,点击创建 添加文件 右击项目名 -> 添加 - ...

  9. 工作中遇到的js跨域问题总结

    起因:之前在做一个项目的时候有这样一个问题,127.0.0.1域名上的一个页面A.html,需要访问127.0.0.2域名上B.html页面中的一个方法.这就涉及到JS跨域访问了,通过查阅资料,得以解 ...

  10. C#开发OPC客户端

    第一个随笔,使用了OPEN Live Write,作为客户端.最近使用c#开发一个小软件,主要功能是OPC客户端.以后会开发各类别的协议,作为,协议的转发栈. 因为我本人是搞自动化的,所以搞自动化小伙 ...