Dubbo服务暴露分析
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,并调用InjvmProtocal的export()方法暴露服务。 InjvmProtocol的export方法仅创建一个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);
}
然后介绍远程服务暴露
如果有注册中心,服务暴露后需要向注册中心注册服务信息
如果没有注册中心,直接暴露服务。
有注册中心的暴露
- 调用
doLocalExport()方法暴露服务。 - 向注册中心注册服务
- 向注册中心进行订阅ovrride数据
- 创建并返回
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服务暴露分析的更多相关文章
- 《Duubo系列》-Dubbo服务暴露过程
我今天来就带大家看看 Dubbo 服务暴露过程,这个过程在 Dubbo 中其实是很核心的过程之一,关乎到你的 Provider 如何能被 Consumer 得知并调用. 今天还是会进行源码解析,毕竟我 ...
- Dubbo服务暴露源码解析②
目录 0.配置解析 1.开始export 2.组装URL 3.服务暴露 疑问解析 先放一张官网的服务暴露时序图,对我们梳理源码有很大的帮助.注:不论是暴露还是导出或者是其他翻译,都是描述expor ...
- Dubbo 服务暴露注册流程
Dubbo的应用会在启动时完成服务注册或订阅(不论是生产者,还是消费者)如下图所示. 图中小方块Protocol, Cluster, Proxy, Service, Container, Regist ...
- dubbo服务暴露
想熟悉dubbo源码,首先要知道dubbo extensionLoader,而dubbo的这种扩展机制,是根据java spi衍生而来. 这是基础,但是我放在后面说明. 一:dubbo demo pr ...
- dubbo系列四、dubbo服务暴露过程源码解析
一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...
- dubbo服务暴露过程
所谓服务暴露最终做的事情:绑定网络端口,开启serversokect服务以接收外部请求 服务暴露时序图 本地暴露 远程暴露 整体总结 dubbo服务提供者暴露服务的主过程:首先 ServiceConf ...
- dubbo服务暴露原理-远程暴露
1.与本地暴露相比,远程暴露也大同小异 我们已经到了第三个关键词Procotol我们来看看他的继承体系图 按照经典图的路线,我们下一个关键词应该就是Server了,从方法名openServer(url ...
- dubbo服务暴露原理
1.发布流程 暴露本地服务 暴露远程服务 启动netty 连接zookeeper 到zookeeper注册 监听zookeeper 2.官方文档 3.看输出日志,就会发现在暴露本地服务之前,有一句很重 ...
- (转)Dubbo服务暴露过程源码分析
参考
随机推荐
- Day17-18前端学习之路——常用语句资料库
一.var 与 let 的区别 var: 可以先初始化再声明该变量; 可以根据需要多次声明相同名称的变量 var myName = 'Chris'; var myName = 'Bob'; let m ...
- asp.net MVC项目开发之统计图echarts柱状图(一)
echarts统计图doc网址:http://echarts.baidu.com/echarts2/index.html 使用echarts,需要引用在js中,如果你已经下载echarts的js包,可 ...
- 关于simplememory theme的设置和感想
前言 首先,这是我第一次自己个性化博客的主题.如果下文所写如有不妥之处还望大佬指出 参考 这次设置多亏了GitHub上的开源代码:https://github.com/BNDong/Cnblogs-T ...
- 【HDU - 1260 】Tickets (简单dp)
Tickets 搬中文 Descriptions: 现在有n个人要买电影票,如果知道每个人单独买票花费的时间,还有和前一个人一起买花费的时间,问最少花多长时间可以全部买完票. Input 给出 N(1 ...
- P5727 【深基5.例3】冰雹猜想
链接:Miku -------------------- 欸,为什么我第一遍没过 -------------------- #include<iostream> using namespa ...
- MySQL快速回顾:数据库和表操作
前提要述:参考书籍<MySQL必知必会> 利用空闲时间快速回顾一些数据库基础. 4.1 连接 在最初安装MySQL,可能会要求你输入一个管理登录(通常为root)和一个口令(密码). 连接 ...
- 【Java】Swing实现一个简单的计算器
import javax.swing.*; import java.awt.*; /** * 计算器 * @author paul * 2019.11.25 21:43 * */ public cla ...
- Centos7下安装包方式安装MySQL
安装包下载地址:https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.27-1.el7.x86_64.rpm-bundle.tar 第一步:在 /h ...
- float浮动以及案例演示
浮动元素会影响后边的元素,但不会影响前边的元素 清除浮动: 方法一:在浮动元素后面添加一个空元素 <!DOCTYPE html> <html lang="en"& ...
- MySQL概述及入门(一)
MySql概述及入门(一) 什么是MySQL? MySQL是当今主流的关系型数据库管理系统(记录是有行有列的数据库) , 可以与Oracle 和SQL Server 竞争 , 是最好RDBMS( ...