作者:黄金

一、Dubbo简介

Dubbo是一款典型的高扩展、高性能、高可用的RPC微服务框架,用于解决微服务架构下的服务治理与通信问题。其核心模块包含 【RPC通信】【服务治理】 ,其中服务治理又分为服务注册与发现、服务容错、负载均衡、流量调度等。今天将重点介绍Dubbo的服务注册与发现

二、SPI机制

在介绍服务注册发现之前,先简单介绍一下贯穿整个Dubbo源码,也是Dubbo实现自适应扩展的核心--SPI机制,下图为Dubbo SPI实现的简单类图。

1、Dubbo SPI原理:通过读取相应的配置文件找到具体实现类,然后通过以下两种方式实例化对象:(1)通过自适应动态字节码编译技术,生成相应的动态代理类,(2)利用反射机制实现实例化。相较于Java SPI,Dubbo SPI实现了内部的IoC和Aop

2、Dubbo SPI 优点:(1)高扩展:用户可以根据实际业务需求扩展相应的实现模块,包含字节码编译技术、rpc协议、通信方式、注册方式等,(2)解耦: 通过封装SPI调用机制,架构上实现了上层应用与底层逻辑之间的解耦,为高扩展提供了支撑条件

3、Dubbo SPI 常用样例(以getExtension和getAdaptiveExtension为例)

配置文件内容
test1=com.dubbo.demo.service.TestServiceimpl
test2=com.dubbo.demo.service.TestServiceImpl2 一、通过getExtension方法生成实例
ExtensionLoader<TestService> extensionLoader = ExtensionLoader.getExtensionLoader(TestService.class);
TestService t1 = extensionLoader.getExtension("test1");
TestService t2 = extensionLoader.getExtension("test2"); 二、通过getAdaptiveExtension生成实例(方法中需要@Adaptive注解,参数会对URL校验)
TestService testService = ExtensionLoader.getExtensionLoader(TestService.class).getAdaptiveExtension();
URL url = new URL("test", "localhost", 8080, new String[]{"test.service", "test1"});
testService.sayHello("bbb", url);

调用getAdaptiveExtension方法最终会生成相应的代理类,最终生成的代理类会根据URL参数里面的protocol决定(以内部Protocol为例)

三、服务注册

1、服务注册流程

2、服务注册类图详解

3、服务注册步骤

(1)步骤一:初始化配置(类图:抽象Config与初始化配置)

首先需要实例化ServiceConfig实例,声明“注册接口、接口实例、注册中心配置”,其中“ServiceBean”是实现Spring与Dubbo整合的桥梁。然后会由DubboBootstrap调用initialize方法实现configManager和Environment的初始化,其中就包括将ServiceConfig中的配置转换成内部封装的协议(ApplicationModel、ProviderModel等)

private static void startWithExport() throws InterruptedException {
//初始化配置
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
//服务注册入口
service.export();
}
public synchronized void export() {
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
// compatible with api call.
if (null != this.getRegistry()) {
bootstrap.registries(this.getRegistries());
}
//初始化配置()
bootstrap.initialize();
}
......
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
//服务注册
doExport();
} exported();
}

(2)步骤二:组装URL

根据初始化配置组转注册接口服务的URL。其中URL也是Dubbo内部通过@Adaptive注解实现SPI的核心,通过修改URL的头部协议(如:register、dubbo、injvm等),在调用

private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
PROTOCOL.export(wrapperInvoker)

该方法的时候,会根据不同的协议切换不通的实现类,实现了Dubbo技术架构与业务逻辑的解耦。

private void doExportUrls() {
//组装后的URL格式样例
//registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&dubbo=2.0.2&pid=26212istry=zookeeper×tamp=1663049763199
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true); int protocolConfigNum = protocols.size();
for (ProtocolConfig protocolConfig : protocols) {
//组装pathKey : org.apache.dubbo.demo.DemoService
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
//保存接口服务
repository.registerService(pathKey, interfaceClass);
//服务注册
doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
}
}

(3)步骤三:Invoker封装(类图:Ref -> Invoker)

通过内置的动态字节码编译(默认javassist)生成Invoker代理类,然后通过反射机制生成Wrapper实例。其中Invoker是Dubbo的核心模型,Invoker是Dubbo中的实体域,也就是真实存在的。其他模型都向它靠拢或转换成它

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) {
......
//组装新的URL
//dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=46528&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663051456562
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
......
//Invoker封装
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
//wrapper
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //服务注册(此时URL头部协议变成了register,实际会调用RegistryProtocol)
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
exporters.add(exporter);
} # PROXY_FACTORY
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 动态代理类生成,反射生成实例
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 {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}

(4)步骤四:Exporter封装(类图:Invoker-> Exporter)

此时会依次调用RegistryProtocol 、DubboProtocol 将Invoker封装成Exporter,并将封装后的Exporter存储到本地map中(类似于spring bean)。然后会调用底层通信服务(默认netty)进行端口监听,此时会通过责任链模式封装Exchanger与Transporter,用于处理网络传输消息的编码/解码。

# RegistryProtocol : export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
......
//此时URL头部协议已变成dubbo
//dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // 此时Registry实例默认是ZookeeperRegistry
final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
//底层调用ZK,创建node节点
registry.register(registeredProviderUrl);
}
....
} # RegistryProtocol : doLocalExport
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker); return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
//此时会调用DubboProtocol进行exporter封装
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
# DubboProtocol : export
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
......
// export service.
String key = serviceKey(url);
//exporter封装
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
......
//开启服务监听
openServer(url);
optimizeSerialization(url); return exporter;
}

(5)步骤五:注册服务节点

封装Exporter并开启服务端口监听后,会调用注册中心(默认Zookeeper)注册服务节点信息

# RegistryProtocol : export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
......
//此时URL头部协议已变成dubbo
//dubbo://2.0.0.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=2.0.0.1&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=56036&release=&service.name=ServiceBean:/org.apache.dubbo.demo.DemoService&side=provider×tamp=1663052353098
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// export invoker
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // 此时Registry实例默认是ZookeeperRegistry
final Registry registry = getRegistry(originInvoker); final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // decide if we need to delay publish
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
//底层调用ZK,创建node节点
registry.register(registeredProviderUrl);
}
....
}

四、总结

至此,Dubbo服务注册的整体流程已大致结束,文中如有不当或者错误观点,欢迎大家评论区指出。感兴趣的同学,可以关注后续“Dubbo架构设计与源码解析”系列的文章。

Dubbo架构设计与源码解析(二) 服务注册的更多相关文章

  1. Java生鲜电商平台-优惠券系统的架构设计与源码解析

    Java生鲜电商平台-优惠券系统的架构设计与源码解析 电商后台:实例解读促销系统 电商后台系统包括商品管理系统.采购系统.仓储系统.订单系统.促销系统.维权系统.财务系统.会员系统.权限系统等,各系统 ...

  2. Java生鲜电商平台-促销系统的架构设计与源码解析

    Java生鲜电商平台-促销系统的架构设计与源码解析 说明:本文重点讲解现在流行的促销方案以及源码解析,让大家对促销,纳新有一个深入的了解与学习过程. 促销系统是电商系统另外一个比较大,也是比较复杂的系 ...

  3. Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战

    Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台-  什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...

  4. Java生鲜电商平台-电商会员体系系统的架构设计与源码解析

    Java生鲜电商平台-电商会员体系系统的架构设计与源码解析 说明:Java生鲜电商平台中会员体系作为电商平台的基础设施,重要性不容忽视.我去年整理过生鲜电商中的会员系统,但是比较粗,现在做一个最好的整 ...

  5. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  6. RxJava2源码解析(二)

    title: RxJava2源码解析(二) categories: 源码解析 tags: 源码解析 rxJava2 前言 本篇主要解析RxJava的线程切换的原理实现 subscribeOn 首先, ...

  7. Sentinel源码解析二(Slot总览)

    写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...

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

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

  9. jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究

    终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...

  10. 第四章 dubbo内核之aop源码解析

    ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class); final P ...

随机推荐

  1. Linux实战笔记_ 如何远程访问Kali?

    注:基于2018年安装的kali版本. 启动ssh服务 /etc/init.d/ssh start 或 service ssh start #启动ssh服务 /etc/init.d/ssh statu ...

  2. .net core 读取appsettings.json 文件中文乱码的问题

    解决办法:设置高级保存选项 第一步:在工具栏找到自定义选项 第二步:添加高级保存选项Advanced save options 第三步:在Appsettings.json页面操作

  3. appium 移动端自动化测试工具

    appium 移动端自动化测试工具 appium 移动端自动化测试工具 appium 是哟个自动化测试开源工具 支持ios和安卓平台的应用 以及web应用和混合应用 appium 是一个跨平台的工具: ...

  4. xlwings 模块总结

    基本使用 在子线程中使用时,有时需要在子线程函数中加入以下.有时不需要加入,目前还不明白具体的原因 import pythoncom # 导入的库 pythoncom.CoInitialize() # ...

  5. 如何通过free看懂内存的真实使用

    之前有位同事问过Linux系统内存free命令下各参数的区别与关系,自己也没太明白,有点尴尬.今天整理一下,供了解. free命令是Liunx操作系统中对内存进行查看和监控的一个常用命令.我们可以直接 ...

  6. ios手机键盘拉起之后页面不会回退的问题

    在input输入框输入内容之后,点击完成,键盘下去了,可是页面没有回退回去,也就是页面会空出浏览器高度那一块,这个问题发现于ios手机中的微信浏览器.解决方案如下 <input type=&qu ...

  7. 重大发现,AQS加锁机制竟然跟Synchronized有惊人的相似

    在并发多线程的情况下,为了保证数据安全性,一般我们会对数据进行加锁,通常使用Synchronized或者ReentrantLock同步锁.Synchronized是基于JVM实现,而Reentrant ...

  8. 【网络】内网穿透方案&FRP内网穿透实战(基础版)

    目录 前言 方案 方案1:公网 方案2:第三方内网穿透软件 花生壳 cpolar 方案3:云服务器做反向代理 FRP简介 FRP资源 FRP原理 FRP配置教程之SSH 前期准备 服务器配置 下载FR ...

  9. el-select实现下拉框触底加载更多

    当下拉框需要展示的数据有很多时,几千甚至上万条,一次性全部请求回来再按照特定格式比如 id-name 去处理数据的话,不论是从接口还是前端,这个性能都不是很好,会造成下拉框初次打开时响应很慢,影响用户 ...

  10. PEP8语法规范解释说明

    PEP8规范解析 内容概要: 1.PEP8规范是什么? 2.PEP8相关内容 1.PEP8规范是什么 PEP是Python Enhancement Proposal的缩写,翻译为:"Pyth ...