【Dubbo 源码解析】04_Dubbo 服务注册&暴露
Dubbo 服务注册&暴露
Dubbo 服务暴露过程是通过 com.alibaba.dubbo.config.spring.ServiceBean 来实现的。Spring 容器 refresh() 完成后,会发送 ContextRefreshedEvent,ServiceBean 会接收到这个 event 然后调用 export()。
Dubbo 服务暴露过程:
获取 Invoker Invoker<?> invoker = proxyFactory.getInvoker(T proxy, Class<T> type, com.alibaba.dubbo.common.URL url); 这个 Invoker 就是 Provider 接收到服务调用的 Request 之后,最终要调用的 invoker。
通过 Protocol 调用 export() Exporter<?> exporter = protocol.export(com.alibaba.dubbo.rpc.Invoker<T> invoker); 默认会使用 DubboProtocol 做服务暴露,过程中会启动 Netty Server 监听端口。
// JavassistProxyFactory.class
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// 通过 wrapper 类去调用服务提供者的真实方法。(避免使用反射,提高效率)
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
服务注册
通过之前 Dubbo Protocol & Filter 的学习,我们知道这里的 protocol 是一个 Wrappered Protocol,所以 protocol.export() 方法会先调用 Protocol SPI 扩展中的 wrapper 类的 export() 。 其中,ProtocolFilterWrapper#export(Invoker<T> invoker) 代码如下:
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// 如果 URL 的 protocol 是注册协议的话,就执行服务注册流程
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
// 服务暴露过程中,会添加 group="provider" 的 Filter
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
通过在上述 export() 方法上打断点跟踪,我们可以发现,Dubbo 首先执行的是服务注册,在服务注册过程中,会再次调用 protocol.export(invokerDelegete) 来做服务本地暴露。
RegistryProtocol#export(Invoker<T> originInvoker) 代码如下:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
// 会再次调用 protocol.export(invokerDelegete) 来做服务本地暴露
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
URL registryUrl = getRegistryUrl(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
//to judge to delay publish whether or not
boolean register = registedProviderUrl.getParameter("register", true);
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
if (register) {
// 服务注册
register(registryUrl, registedProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
}
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
}
附: com.alibaba.dubbo.registry.support.AbstractRegistry#AbstractRegistry(URL url) 服务注册缓存文件
服务暴露
服务本地暴露,最终会调用 DubboProtocol#export(Invoker<T> invoker)
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
// 1. 创建 DubboExporter
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
......
// 2. 开启服务监听
openServer(url);
optimizeSerialization(url);
return exporter;
}
Dubbo 服务暴露时,首先会创建一个 DubboExporter,然后再通过 netty 开启服务端口监听。 DubboExporter 的作用是缓存 Invoker,方便后续操作获取 Invoker。其中最重要的操作就是: Provider 接收到 Request 请求后,获取到对应的 Invoker,然后执行 Invoker。
openServer(url) 最终会调用 createServer(URL url) 来创建 tcp server:
private ExchangeServer createServer(URL url) {
// send readonly event when server closes, it's enabled by default
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
// enable heartbeat by default
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
......
// 设置 codec = "dubbo"
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
// 创建 server,并传递 requestHandler
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}
......
return server;
}
这里的 requestHandler 非常重要,它是用来处理 consumer 端的 Request,将 Request 转化成 Invoker 调用的。
创建 tcp server 的过程:
Exchangers.bind(url, requestHandler) --> Exchanger$Adaptive#bind(URL url, ExchangeHandler handler) --> HeaderExchanger#bind(URL url, ExchangeHandler handler) --> 返回 new HeaderExchangeServer(Server server)
// HeaderExchanger.class
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
Transporters.bind(URL url, ChannelHandler... handlers) --> Transporter$Adaptive#bind(URL url, ChannelHandler handler) --> NettyTransporter#bind(URL url, ChannelHandler listener) --> 返回 new NettyServer(url, listener)
编解码最终使用的是:com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec
NettyServer 开启服务监听的代码:
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
bootstrap = new ServerBootstrap(channelFactory);
// 这里的 this 所指的 handler 就是 Exchangers.bind(url, requestHandler) 传递的 handler
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
// 编解码的 handler
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
// 业务处理的 handler
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
// bind
channel = bootstrap.bind(getBindAddress());
}
官方如是说:
暴露服务
1. 只暴露服务端口:
在没有注册中心,直接暴露提供者的情况下 [1],ServiceConfig 解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0。
基于扩展点自适应机制,通过 URL 的 dubbo:// 协议头识别,直接调用 DubboProtocol的 export() 方法,打开服务端口。
2. 向注册中心暴露服务:
在有注册中心,需要注册提供者地址的情况下 [2],ServiceConfig 解析出的 URL 的格式为: registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0"),
基于扩展点自适应机制,通过 URL 的 registry:// 协议头识别,就会调用 RegistryProtocol 的 export()方法,将 export 参数中的提供者 URL,先注册到注册中心。
再重新传给 Protocol 扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo:// 协议头识别,就会调用 DubboProtocol 的 export()方法,打开服务端口。
服务提供者暴露一个服务的详细过程


上图是服务提供者暴露服务的主过程:
首先 ServiceConfig 类拿到对外提供服务的实际类 ref(如:HelloWorldImpl),然后通过 ProxyFactory 类的 getInvoker 方法使用 ref 生成一个 AbstractProxyInvoker 实例,到这一步就完成具体服务到 Invoker 的转化。接下来就是 Invoker 转换到 Exporter 的过程。
Dubbo 处理服务暴露的关键就在 Invoker 转换到 Exporter 的过程,上图中的红色部分。下面我们以 Dubbo 和 RMI 这两种典型协议的实现来进行说明:
Dubbo 的实现
Dubbo 协议的 Invoker 转为 Exporter 发生在 DubboProtocol 类的 export 方法,它主要是打开 socket 侦听服务,并接收客户端发来的各种请求,通讯细节由 Dubbo 自己实现。
RMI 的实现
RMI 协议的 Invoker 转为 Exporter 发生在 RmiProtocol类的 export 方法,它通过 Spring 或 Dubbo 或 JDK 来实现 RMI 服务,通讯细节这一块由 JDK 底层来实现,这就省了不少工作量。
如果想了解更多Dubbo源码的知识,请移步 Dubbo源码解读——通向高手之路 的视频讲解:
http://edu.51cto.com/sd/2e565
【Dubbo 源码解析】04_Dubbo 服务注册&暴露的更多相关文章
- dubbo源码分析13——服务本地暴露 exportLocal(url)
dubbo服务的本地暴露,显然是针对当服务消费者和服务提供者都在同一个jvm的进程内这种场景 .通常是发生在服务之间的调用的情况下.一种情况就是A服务调用B服务的情况,如果A服务和B服务都是在一个线程 ...
- Dubbo源码解析之registry注册中心
阅读须知 dubbo版本:2.6.0 spring版本:4.3.8 文章中使用/* */注释的方法会做深入分析 正文注册中心是Dubbo的重要组成部分,主要用于服务的注册与发现,我们可以选择Redis ...
- 第零章 dubbo源码解析目录
第一章 第一个dubbo项目 第二章 dubbo内核之spi源码解析 2.1 jdk-spi的实现原理 2.2 dubbo-spi源码解析 第三章 dubbo内核之ioc源码解析 第四章 dubb ...
- dubbo源码解析-zookeeper创建节点
前言 在之前dubbo源码解析-本地暴露中的前言部分提到了两道高频的面试题,其中一道dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?在上周的dubbo源码 ...
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- dubbo源码解析-spi(4)
前言 本篇是spi的第四篇,本篇讲解的是spi中增加的AOP,还是和上一篇一样,我们先从大家熟悉的spring引出AOP. AOP是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从All in ...
- Netty 4源码解析:服务端启动
Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...
- Dubbo 源码解析四 —— 负载均衡LoadBalance
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...
- dubbo源码解析-spi(3)
前言 在上一篇的末尾,我们提到了dubbo的spi中增加了IoC和AOP的功能.那么本篇就讲一下这个增加的IoC,spi部分预计会有四篇,因为这东西实在是太重要了.温故而知新,我们先来回顾一下,我们之 ...
- dubbo源码解析-spi(一)
前言 虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理.与spring融合一样,为dubbo源码解析专题的知识预热篇. 插播面试题 你是否了解 ...
随机推荐
- Wooden Sticks [POJ1065] [DP]
Description 有N根木棍等待处理.机器在处理第一根木棍时需要准备1分钟,此后遇到长宽都不大于前一根木棍的木棍就不需要时间准备,反之则需要1分钟重新准备.比如木棍按照(3,3).(1,3).( ...
- 3ds max学习笔记-- 动画
栗子:若要使茶壶从a点运动到b点,是需要动画实现的:动画与传统意义的移动不同,与时间是存在关系的: 时间线,时间滑条: [时间配置]按钮: 弹出面板: 动画时间轴默认时间是从0帧开始100结束:总长度 ...
- vivox23幻彩版手机怎么设置双击息屏
除了使用电源键来实现快速息屏方式外,我们还能通过双击屏幕的手势来息屏,下面小编就教大家vivox23幻彩版设置双击息屏的方法教程. vivox23幻彩版怎么设置双击息屏 第一步:打开vivox23幻彩 ...
- jquery 在线视频
1. jquery 网址 自学视频 http://edu.51cto.com/center/course/lesson/index?id=19292
- CentOS下安装Jenkins(Docker/war/tomcat/java -jar)
参考官方提供的安装教程:https://jenkins.io/doc/book/installing/,可以发现官方推荐使用Docker进行安装.虽然用Docker有很多好处,但也有缺点. 下面是各种 ...
- [原创]RedisDesktopManager工具使用介绍
[原创]RedisDesktopManager工具使用介绍 1 RedisDesktopManager简介 一款能够跨平台使用的开源性redis可视化工具.redis desktop manager主 ...
- HEVC与VP9之间的对比
在streamingmedia上看到的一篇对比HEVC与VP9的文章,挺不错.另外这边文章的几个comment也是不错的. 下面是全文. The Great UHD Codec Debate: G ...
- let's encrypt申请
let's encrypt申请 https://keelii.com/2016/06/12/free-https-cert-lets-encrypt-apply-install/ https://ww ...
- echarts中tooltip提示框位置控制
关键代码: position: function(point, params, dom, rect, size) { //其中point为当前鼠标的位置,size中有两个属性:viewSize和con ...
- dubbo常见错误
1.dubbo zookeeper注册中心provider的ip地址为内网ip,导致consumer连不上 我用的阿里云的服务器,host默认配置了内网ip,注销或删除即可 vim /etc/host ...