Dubbo服务发布的整体流程一文中,只是分析了服务发布的整体流程,具体的细节还没有进一步分析。本节将继续分析服务暴露的过程。在ServiceConfig中通过一句话即可暴露服务,如下:

Exporter<?> exporter = protocol.export(invoker);

此时Invoker对象携带的URL信息中定义的是"registry",则此处"protocol"加载的是RegistryProtocol对象。也即调用RegistryProtocol的export方法处理Invoker。

RegistryProtocol实现了Protocol接口。Protocol接口是一个顶级接口,定义内容比较简单,如下:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI; @SPI("dubbo")
public interface Protocol { /**
* 获取缺省端口,当用户没有配置端口时使用。
*
* @return 缺省端口
*/
int getDefaultPort(); /**
* 暴露远程服务:<br>
* 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
* 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
* 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
*
* @param <T> 服务的类型
* @param invoker 服务的执行体
* @return exporter 暴露服务的引用,用于取消暴露
* @throws RpcException 当暴露服务出错时抛出,比如端口已占用
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException; /**
* 引用远程服务:<br>
* 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
* 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
* 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>
*
* @param <T> 服务的类型
* @param type 服务的类型
* @param url 远程服务的URL地址
* @return invoker 服务的本地代理
* @throws RpcException 当连接服务提供方失败时抛出
*/
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; /**
* 释放协议:<br>
* 1. 取消该协议所有已经暴露和引用的服务。<br>
* 2. 释放协议所占用的所有资源,比如连接和端口。<br>
* 3. 协议在释放后,依然能暴露和引用新的服务。<br>
*/
void destroy(); }

通过中文注释,很容易理解接口中定义的四个方法的功能。通过SPI注解,设置默认的实现类为DubboProtocol。实现类RegistryProtocol中方法比较多,但大多数都是private方法,public方法主要是一些setter和getter方法,还有就是Protocol接口中定义的方法实现。

在这里主要看下RegistryProtocol中export方法:

    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
//export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
//registry provider
final Registry registry = getRegistry(originInvoker);
final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
registry.register(registedProviderUrl);
// 订阅override数据
// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
//保证每次export都返回一个新的exporter实例
return new Exporter<T>() {
public Invoker<T> getInvoker() {
return exporter.getInvoker();
} public void unexport() {
try {
exporter.unexport();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
registry.unregister(registedProviderUrl);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
try {
overrideListeners.remove(overrideSubscribeUrl);
registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
};
}

export方法的入参是Invoker实例,并且定义了final类型,表明在方法内部,Invoker实例不能被再修改。方法中首先对传入的Invoker进行暴露,具体方法如下:

    private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);
bounds.put(key, exporter);
}
}
}
return exporter;
}

doLocalExport方法中先检查服务是否已经暴露过,如果已经暴露则不再重复暴露,如果没有暴露,则执行暴露过程。

exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker);

执行暴露的protocol是DubboProtocol实例。和RegistryProtocol一样,DubboProtocol也实现了Protocol接口,大多数方法也是private方法。不一样的是RegistryProtocol直接实现Protocol接口,而DubboProtocol是继承AbstractProtocol抽象类,AbstractProtocol实现Protocol接口。如下所示:

public class RegistryProtocol implements Protocol{}

public class DubboProtocol extends AbstractProtocol{}

public abstract class AbstractProtocol implements Protocol{}

直接来看DubboProtocol中export方法,源码如下:

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl(); // export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter); //export an stub service for dispatching event
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
} openServer(url); return exporter;
}

在发布服务之前,先根据要发布的服务产生与服务相对应的key值。key是一个字符串类型,格式为“group/ServiceName:version:port”。具体生成方法在ProtocolUtils工具类中定义了,如下所示:

    public static String serviceKey(int port, String serviceName, String serviceVersion, String serviceGroup) {
StringBuilder buf = new StringBuilder();
if (serviceGroup != null && serviceGroup.length() > 0) {
buf.append(serviceGroup);
buf.append("/");
}
buf.append(serviceName);
if (serviceVersion != null && serviceVersion.length() > 0 && !"0.0.0".equals(serviceVersion)) {
buf.append(":");
buf.append(serviceVersion);
}
buf.append(":");
buf.append(port);
return buf.toString();
}

生成key值之后,结合Invoker和exportMap生成服务暴露,然后将生成的服务暴露作为value值放入map中,从而实现服务发布。服务暴露是用DubboExporter封装的,DubboExporter类比较简单,源码如下:

package com.alibaba.dubbo.rpc.protocol.dubbo;

import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.protocol.AbstractExporter; import java.util.Map; public class DubboExporter<T> extends AbstractExporter<T> { private final String key; private final Map<String, Exporter<?>> exporterMap; public DubboExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
} @Override
public void unexport() {
super.unexport();
exporterMap.remove(key);
} }

DubboExporter引入了泛型,继承了AbstractExporter抽象类,AbstractExporter又实现了Exporter接口。抽象类AbstractExporter和接口Exporter都比较简单,这里就不再叙述,请自行查阅。

DubboProtocol的export方法中需要重点看下exporterMap属性。exporterMap是在AbstractProtocol抽象类中定义的,如下:

    protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>();

exporterMap是Map类型,精确的说,是ConcurrentHashMap类型。ConcurrentHashMap是一个高并发的HashMap容器。发布的服务就存储在这里,通过key值可以得到服务发布DubboExporter,再根据DubboExporter的getInvoker方法得到服务调用,从而调用服务。具体的服务调用细节将在后续文章中分析,敬请期待。

以上就是Dubbo发布服务的整个过程,过程中各种封装、调用、继承、多态应用得淋漓尽致。大体的流程是:i. 应用Spring Schema机制读入provider配置文件信息;ii. 将读入后的信息组装成URL对象;iii. 根据组装后的URL对象创建服务调用Invoker;iv. 根据服务调用Invoker创建服务发布Exporter及相应key值;v. 将key值和Exporter装入ConcurrentHashMap中,实现发布服务功能。在这个过程中,Dubbo自定义的URL对象发挥着重要作用,将provider配置文件、Invoker、Exporter串联了起来。

分析完了Dubbo发布服务的过程,下文将继续分析Dubbo服务的注册过程


如果对您有帮助,不妨点个赞、关注一波

Dubbo源码学习--服务发布(DubboProtocol、Exporter)的更多相关文章

  1. Dubbo源码学习--服务发布(ServiceBean、ServiceConfig)

    前面讲过Dubbo SPI拓展机制,通过ExtensionLoader实现可插拔加载拓展,本节将接着分析Dubbo的服务发布过程. 以源码中dubbo-demo模块作为切入口一步步走进Dubbo源码. ...

  2. Dubbo源码学习--服务发布(ProxyFactory、Invoker)

    上文分析了Dubbo服务发布的整体流程,但服务代理生成的具体细节介绍得还不是很详细.下面将会接着上文继续分析.上文介绍了服务代理生成的切入点,如下: Invoker<?> invoker ...

  3. Dubbo源码学习--服务是如何发布的

    相关文章: Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 ServiceBean ServiceBean 实现ApplicationListener接口监听Conte ...

  4. Dubbo源码学习--服务是如何引用的

    ReferenceBean 跟服务引用一样,Dubbo的reference配置会被转成ReferenceBean类,ReferenceBean实现了InitializingBean接口,直接看afte ...

  5. dubbo源码之服务发布与注册

    服务端发布流程: dubbo 是基于 spring 配置来实现服务的发布的,对于dubbo 配置文件中看到的<dubbo:service>等标签都是服务发布的重要配置 ,对于这些提供可配置 ...

  6. dubbo源码之四——服务发布二

    dubbo版本:2.5.4 2. 服务提供者暴露一个服务的详细过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl ...

  7. 2、Dubbo源码解析--服务发布原理(Netty服务暴露)

    一.服务发布 - 原理: 首先看Dubbo日志,截取重要部分: 1)暴露本地服务 Export dubbo service com.alibaba.dubbo.demo.DemoService to ...

  8. Dubbo源码学习文章目录

    目录 Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 Dubbo源码学习--注册中心分析 Dubbo源码学习--集群负载均衡算法的实现

  9. dubbo源码学习(四):暴露服务的过程

    dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点, ...

随机推荐

  1. sqoop1.4.6从mysql导入hdfs\hive\hbase实例

    //验证sqoop是否连接到mysql数据库sqoop list-tables --connect 'jdbc:mysql://n1/guizhou_test?useUnicode=true& ...

  2. linux学习(二)linux配置网卡以及常见网络问题排查

    实验环境环境:mac,vmware fusion 一.常用的虚拟机网络连接模式. NAT:推荐方式.它可以使你在切换网络环境(比如在工作中和家里)时,不需要修改虚拟主机的配置,而维持正常的上网功能. ...

  3. PAT-甲级-1003

    一.看题,https://www.patest.cn/contests/pat-a-practise/1007 其实,也是一顿暴力,但是最后一个测试点会运行超时,最开始,计算一段区间的值的总和的时候, ...

  4. ruby 安装 mysql2 命令

    sudo apt-get install libmysql-ruby libmyclient-dev

  5. oracle导入TYPE对象报错ORA-02304

    Type是我们经常使用的数据库对象结构.我们在实际中,可以单独定义type类型,之后在PL/SQL代码或者数据表中使用. 在一个偶然的机会让笔者发现使用Type类型在数据exp/imp中的麻烦.当我们 ...

  6. 5. 监视和ZooKeeper操作

    ZooKeeper中的写入(write)操作是原子性和持久性的. 写入到大多数ZooKeeper服务器上的持久性存储中,可以保证写操作成功. 无论如何,ZooKeeper的最终一致性模型允许读取(re ...

  7. C#执行批处理命令

    using System.Diagnostics ; using System.IO; private void btnRun_Click(object sender, EventArgs e)    ...

  8. [转载] Java并发编程:Callable、Future和FutureTask

    转载自http://www.cnblogs.com/dolphin0520/p/3949310.html 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Run ...

  9. Zuul(SpringCloud学习笔记一)

    路由是微服务架构中必须(integral )的一部分,比如,"/" 可能映射到你的WEB程序上,"/api/users "可能映射到你的用户服务上," ...

  10. 基于MyBatis的数据服务接口

    背景 作为软件系统开发,数据操作是系统开发不可避免的一个重要组成部分.因为其重要性围绕着数据操作也出现了众多框架.成熟框架是为了普适众多数据操作要求的,因此为了更好的实现技术落地,需要对框架进行丰富和 ...