Dubbo 服务注册&暴露

Dubbo 服务暴露过程是通过 com.alibaba.dubbo.config.spring.ServiceBean 来实现的。Spring 容器 refresh() 完成后,会发送 ContextRefreshedEvent,ServiceBean 会接收到这个 event 然后调用 export()。

Dubbo 服务暴露过程:

  1. 获取 Invoker Invoker<?> invoker = proxyFactory.getInvoker(T proxy, Class<T> type, com.alibaba.dubbo.common.URL url); 这个 Invoker 就是 Provider 接收到服务调用的 Request 之后,最终要调用的 invoker。

  2. 通过 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:// 协议头识别,直接调用 DubboProtocolexport() 方法,打开服务端口。

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:// 协议头识别,就会调用 RegistryProtocolexport()方法,将 export 参数中的提供者 URL,先注册到注册中心。

再重新传给 Protocol 扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo:// 协议头识别,就会调用 DubboProtocolexport()方法,打开服务端口。

服务提供者暴露一个服务的详细过程


上图是服务提供者暴露服务的主过程:

首先 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 服务注册&暴露的更多相关文章

  1. dubbo源码分析13——服务本地暴露 exportLocal(url)

    dubbo服务的本地暴露,显然是针对当服务消费者和服务提供者都在同一个jvm的进程内这种场景 .通常是发生在服务之间的调用的情况下.一种情况就是A服务调用B服务的情况,如果A服务和B服务都是在一个线程 ...

  2. Dubbo源码解析之registry注册中心

    阅读须知 dubbo版本:2.6.0 spring版本:4.3.8 文章中使用/* */注释的方法会做深入分析 正文注册中心是Dubbo的重要组成部分,主要用于服务的注册与发现,我们可以选择Redis ...

  3. 第零章 dubbo源码解析目录

    第一章 第一个dubbo项目 第二章  dubbo内核之spi源码解析 2.1  jdk-spi的实现原理 2.2 dubbo-spi源码解析 第三章 dubbo内核之ioc源码解析 第四章 dubb ...

  4. dubbo源码解析-zookeeper创建节点

    前言 在之前dubbo源码解析-本地暴露中的前言部分提到了两道高频的面试题,其中一道dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?在上周的dubbo源码 ...

  5. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

  6. dubbo源码解析-spi(4)

    前言 本篇是spi的第四篇,本篇讲解的是spi中增加的AOP,还是和上一篇一样,我们先从大家熟悉的spring引出AOP. AOP是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从All in ...

  7. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  8. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

  9. dubbo源码解析-spi(3)

    前言 在上一篇的末尾,我们提到了dubbo的spi中增加了IoC和AOP的功能.那么本篇就讲一下这个增加的IoC,spi部分预计会有四篇,因为这东西实在是太重要了.温故而知新,我们先来回顾一下,我们之 ...

  10. dubbo源码解析-spi(一)

    前言 虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理.与spring融合一样,为dubbo源码解析专题的知识预热篇. 插播面试题 你是否了解 ...

随机推荐

  1. redis:aof恢复与rdb服务器间迁移

    1. aof恢复与rdb服务器间迁移: 1.1. Aof恢复: 如果不小心执行了flushdb或flushall了怎么办? (1)立马执行命令:shutdown nosave 关闭服务器,为了防止其他 ...

  2. nginx -s reload时出现open() "/run/nginx.pid" failed (2: No such file or directory)错误

    解决办法: 找到你的nginx.conf的文件夹目录,比如我的为/etc/nginx/nginx.conf,然后运行这个  nginx -c /etc/nginx/nginx.conf命令,  再运行 ...

  3. js中函数对象创建的总结

    在JavaScript的函数对象创建方法中,可以分为三种情况: 1:第一种是使用function语句定义函数 <script type="text/javascript"&g ...

  4. javascript对内容的操作

    我在这里介绍innerHTML.innerText.innerContent 一,innerHTML(可以识别标签): 案例1:替换掉整个标签 <!--innerHTML--> <p ...

  5. flask之SQLAlchemy

    本篇导航: 介绍 使用 SQLAlchemy-Utils 一. 介绍 SQLAlchemy是一个基于Python实现的ORM框架.该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之 ...

  6. WPF之几何图形Geometry

    在WPF的DrawingContext对象中,提供了基本的绘制椭圆和矩形的API:DrawEllipse和DrawRectangle.但是,这些是远远不够用的,我们在日常应用中,更多的是使用DrawG ...

  7. An entry point cannot be marked with the 'async' modifier

    I copied below code from this link.But when I am compiling this code I am getting an entry point can ...

  8. .net core日志记录

    .net core日志记录 日志是必须的,目前采用log4net进行日志记录. 定义通用的日志记录方法 public static class Log4NetFunc { private static ...

  9. 根据javabean转换为mysql建表语句与mapper内容

    原文地址:  https://www.cnblogs.com/Jeffscnblog/p/10072483.html 一般上,我们会使用数据库表转换为javabean.dao.或是mapper,就叫逆 ...

  10. DBS:TestSys

    ylbtech-DBS:TestSys 1.返回顶部 1. -- ============================================= -- 测试系统 -- 2018-4-12 ...