java调用dubbo服务器_dubbo源码分析-服务端注册流程-笔记

前面,我们已经知道,基于spring这个解析入口,到发布服务的过程,接着基于DubboProtocol去发布,最终调用Netty的api创建了一个NettyServer。

那么继续沿着RegistryProtocol.export这个方法,来看看注册服务的代码:

RegistryProtocol.export

public Exporter export(final Invoker originInvoker) throws RpcException {

//export invoker

final ExporterChangeableWrapper 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);

overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

//保证每次export都返回一个新的exporter实例

return new Exporter() {

public Invoker 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);

}

}

};

}

getRegistry

这个方法是invoker的地址获取registry实例

/**

* 根据invoker的地址获取registry实例

* @param originInvoker

* @return

*/

private Registry getRegistry(final Invoker> originInvoker){

URL registryUrl = originInvoker.getUrl(); //获得registry://192.168.11.156:2181的协议地址

if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) {

//得到zookeeper的协议地址

String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY);

//registryUrl就会变成了zookeeper://192.168.11.156

registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY);

}

//registryFactory是什么?

return registryFactory.getRegistry(registryUrl);

}

registryFactory.getRegistry

这段代码很明显了,通过前面这段代码的分析,其实就是把registry的协议头改成服务提供者配置的协议地址,也就是我们配置的

然后registryFactory.getRegistry的目的,就是通过协议地址匹配到对应的注册中心。

那registryFactory是一个什么样的对象呢?我们找一下这个代码的定义

private RegistryFactory registryFactory;

public void setRegistryFactory(RegistryFactory registryFactory) {

this.registryFactory = registryFactory;

}

这个代码有点眼熟,再来看看RegistryFactory这个类的定义,我猜想一定是一个扩展点,不信,咱们看

并且,大家还要注意这里面的一个方法上,有一个@Adaptive的注解,说明什么? 这个是一个自适应扩展点。

按照我们之前看过代码,自适应扩展点加在方法层面上,表示会动态生成一个自适应的适配器。

所以这个自适应适配器应该是RegistryFactory$Adaptive

@SPI("dubbo")

public interface RegistryFactory {

/**

* 连接注册中心.

*

* 连接注册中心需处理契约:

* 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。

* 2. 支持URL上的username:password权限认证。

* 3. 支持backup=10.20.153.10备选注册中心集群地址。

* 4. 支持file=registry.cache本地磁盘文件缓存。

* 5. 支持timeout=1000请求超时设置。

* 6. 支持session=60000会话超时或过期设置。

*

* @param url 注册中心地址,不允许为空

* @return 注册中心引用,总不返回空

*/

@Adaptive({"protocol"})

Registry getRegistry(URL url);

}

RegistryFactory$Adaptive

我们拿到这个动态生成的自适应扩展点,看看这段代码里面的实现

从url中拿到协议头信息,这个时候的协议头是zookeeper://

通过ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(“zookeeper”)去获得一个指定的扩展点,而这个扩展点的配置在

dubbo-registry-zookeeper/resources/META-INF/dubbo/internal/com.alibaba.dubbo.registry.RegistryFactory。

得到一个ZookeeperRegistryFactory

public class RegistryFactory$Adaptive implements com.alibaba.dubbo.registry.RegistryFactory {

public com.alibaba.dubbo.registry.Registry getRegistry(com.alibaba.dubbo.common.URL arg0) {

if (arg0 == null) throw new IllegalArgumentException("url == null");

com.alibaba.dubbo.common.URL url = arg0;

String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());

if (extName == null)

throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.registry.RegistryFactory) " +

"name from url(" + url.toString() + ") use keys([protocol])");

com.alibaba.dubbo.registry.RegistryFactory extension =

(com.alibaba.dubbo.registry.RegistryFactory)

ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.registry.RegistryFactory.class).

getExtension(extName);

return extension.getRegistry(arg0);

}

}

ZookeeperRegistryFactory

这个方法中并没有getRegistry方法,而是在父类AbstractRegistryFactory

从缓存REGISTRIES中,根据key获得对应的Registry

如果不存在,则创建Registry

public Registry getRegistry(URL url) {

url = url.setPath(RegistryService.class.getName())

.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())

.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);

String key = url.toServiceString();

// 锁定注册中心获取过程,保证注册中心单一实例

LOCK.lock();

try {

Registry registry = REGISTRIES.get(key);

if (registry != null) {

return registry;

}

registry = createRegistry(url);

if (registry == null) {

throw new IllegalStateException("Can not create registry " + url);

}

REGISTRIES.put(key, registry);

return registry;

} finally {

// 释放锁

LOCK.unlock();

}

}

createRegistry

创建一个注册中心,这个是一个抽象方法,具体的实现在对应的子类实例中实现的,在ZookeeperRegistryFactory中

public Registry createRegistry(URL url) {

return new ZookeeperRegistry(url, zookeeperTransporter);

}

通过zkClient,获得一个zookeeper的连接实例

public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {

super(url);

if (url.isAnyHost()) {

throw new IllegalStateException("registry address == null");

}

String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);

if (! group.startsWith(Constants.PATH_SEPARATOR)) {

group = Constants.PATH_SEPARATOR + group;

}

this.root = group; //设置根节点

zkClient = zookeeperTransporter.connect(url);//建立连接

zkClient.addStateListener(new StateListener() {

public void stateChanged(int state) {

if (state == RECONNECTED) {

try {

recover();

} catch (Exception e) {

logger.error(e.getMessage(), e);

}

}

}

});

}

代码分析到这里,我们对于getRegistry得出了一个结论,根据当前注册中心的配置信息,获得一个匹配的注册中心,也就是ZookeeperRegistry

registry.register(registedProviderUrl);

继续往下分析,会调用registry.register去将dubbo://的协议地址注册到zookeeper上

这个方法会调用FailbackRegistry类中的register. 为什么呢?

因为ZookeeperRegistry这个类中并没有register这个方法,但是他的父类FailbackRegistry中存在register方法,而这个类又重写了AbstractRegistry类中的register方法。

所以我们可以直接定位大FailbackRegistry这个类中的register方法中

FailbackRegistry.register

FailbackRegistry,从名字上来看,是一个失败重试机制

调用父类的register方法,讲当前url添加到缓存集合中

调用doRegister方法,这个方法很明显,是一个抽象方法,会由ZookeeperRegistry子类实现。

@Override

public void register(URL url) {

super.register(url);

failedRegistered.remove(url);

failedUnregistered.remove(url);

try {

// 向服务器端发送注册请求

doRegister(url);

} catch (Exception e) {

Throwable t = e;

// 如果开启了启动时检测,则直接抛出异常

boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)

&& url.getParameter(Constants.CHECK_KEY, true)

&& ! Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());

boolean skipFailback = t instanceof SkipFailbackWrapperException;

if (check || skipFailback) {

if(skipFailback) {

t = t.getCause();

}

throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);

} else {

logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);

}

// 将失败的注册请求记录到失败列表,定时重试

failedRegistered.add(url);

}

}

ZookeeperRegistry.doRegister

终于找到你了,调用zkclient.create在zookeeper中创建一个节点。

protected void doRegister(URL url) {

try {

zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));

} catch (Throwable e) {

throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);

}

}

RegistryProtocol.export 这个方法中后续的代码就不用再分析了。

就是去对服务提供端去注册一个zookeeper监听,当监听发生变化的时候,服务端做相应的处理。

在register 方法里面,调用subscribe 方法,订阅注册中心变化

/**

* 订阅符合条件的已注册数据,当有注册数据变更时自动推送.

*

* 订阅需处理契约:

* 1. 当URL设置了check=false时,订阅失败后不报错,在后台定时重试。

* 2. 当URL设置了category=routers,只通知指定分类的数据,多个分类用逗号分隔,并允许星号通配,表示订阅所有分类数据。

* 3. 允许以interface,group,version,classifier作为条件查询,如:interface=com.alibaba.foo.BarService&version=1.0.0

* 4. 并且查询条件允许星号通配,订阅所有接口的所有分组的所有版本,或:interface=*&group=*&version=*&classifier=*

* 5. 当注册中心重启,网络抖动,需自动恢复订阅请求。

* 6. 允许URI相同但参数不同的URL并存,不能覆盖。

* 7. 必须阻塞订阅过程,等第一次通知完后再返回。

*

* @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin

* @param listener 变更事件监听器,不允许为空

*/

void subscribe(URL url, NotifyListener listener);

subscribe ->doSubscribe ->notify ->

protected void notify(URL url, NotifyListener listener, List urls) {

if (url == null) {

throw new IllegalArgumentException("notify url == null");

}

if (listener == null) {

throw new IllegalArgumentException("notify listener == null");

}

if ((urls == null || urls.size() == 0)

&& ! Constants.ANY_VALUE.equals(url.getServiceInterface())) {

logger.warn("Ignore empty notify urls for subscribe url " + url);

return;

}

if (logger.isInfoEnabled()) {

logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);

}

Map> result = new HashMap>();

for (URL u : urls) {

if (UrlUtils.isMatch(url, u)) {

String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);

List categoryList = result.get(category);

if (categoryList == null) {

categoryList = new ArrayList();

result.put(category, categoryList);

}

categoryList.add(u);

}

}

if (result.size() == 0) {

return;

}

Map> categoryNotified = notified.get(url);

if (categoryNotified == null) {

notified.putIfAbsent(url, new ConcurrentHashMap>());

categoryNotified = notified.get(url);

}

// 第一次主动调用 notify

// 对 /router /providers /configerations 路径下的变更 进行notify

//后续(zookeeper watcher 机制)

for (Map.Entry> entry : result.entrySet()) {

String category = entry.getKey();

List categoryList = entry.getValue();

categoryNotified.put(category, categoryList);

saveProperties(url);

listener.notify(categoryList);

}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/417799.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React - S1

资料: 1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript 进度: 教程 - 高级内容remaining; 参考remaining js 与 的区别[转] 1、对于string,number等基础类型,和是有区别的 1)不同类型间比较,之比较“转…

java错位_java – 如何保护自己免受参数错位的影响

假设我有这个重要的方法:int generateId(int clientCode, int dataVersion) {return clientCode * 2 dataVersion % 2;}这两个参数都是int,因此使用错误的参数调用此方法非常容易,例如generateId(dataVersion,clientCode).它将被成功编译和执行.但生成的id将完全错…

(csc)Visual C# 2010 编译器选项.

Visual C# 2010 编译器选项 - 输出文件 -/out:<文件> 指定输出文件名(默认值: 包含主类的文件或第一个文件的基名称)/target:exe 生成控制台可执行文件(默认) (缩写: /t:exe)/target:winexe …

力扣Java解数独_LeetCode 力扣 37. 解数独

题目描述(困难难度)给定一个数独棋盘&#xff0c;输出它的一个解。解法一 回溯法从上到下&#xff0c;从左到右遍历每个空位置。在第一个位置&#xff0c;随便填一个可以填的数字&#xff0c;再在第二个位置填一个可以填的数字&#xff0c;一直执行下去直到最后一个位置。期间如…

T-SQL像数组一样处理字符串、分割字符串,遍历数组

T-SQL对字符串的处理能力比较弱&#xff0c;比如我要循环遍历象1,2,3,4,5这样的字符串&#xff0c;如果用数组的话&#xff0c;遍历很简单&#xff0c;但是T-SQL不支持数组&#xff0c;所以处理下来比较麻烦。下边的函数&#xff0c;实现了象数组一样去处理字符串。 一、按指定…

jquery 获取鼠标和元素的坐标点

获取当前鼠标相对img元素的坐标[javascript] view plaincopy $(img).mousemove(function(e) { varpositionXe.pageX-$(this).offset().left; //获取当前鼠标相对img的X坐标 varpositionYe.pageY-$(this).offset().top; //获取当前鼠标相对img的Y坐标 console…

java上转型对象特点_Java 浅析三大特性之一继承

上文Java 浅析三大特性之一封装我们说到Java是一个注重编写类&#xff0c;注重于代码和功能复用的语言。Java实现代码复用的方式有很多&#xff0c;这里介绍一个重要的复用方式——继承。在介绍继承之前&#xff0c;我们要明确一点&#xff0c;继承是一个比较复杂的编写类的方式…

并口学习之一

1.由于windows并不是一个实时控制系统,通过并口只能最大输出100Khz的脉冲频率.这对于普通电机来说已经足够了. 但是如果对可支持200Khz及以上的电机来说,这真是个不好的消息.USB口等输出脉冲的最大速度可无限提高了.这变由外部接口卡的性能决定了.因此有些USB口卡支持最大脉冲输…

MSSQL优化之————探索MSSQL执行计划

原文链接&#xff1a;http://blog.csdn.net/no_mIss/archive/2006/11/09/1374978.aspx 作者&#xff1a;no_mIss QQ:34813284 时间&#xff1a;2006.11.07 23:30:00 环境&#xff1a;win2003 mssql2005 最近总想整理下对MSSQL的一些理解与感悟&#xff0c;却一直没有心思和…

java web的动静分离_Apache结合Tomcat实现动静分离的方法

实验环境Apache和Tomcat均安装在IP地址为192.168.153.136的主机上 主机操作系统为centos7 实验之前关闭防火墙 ,命令&#xff1a; systemctl stop firewalld 关闭seliunx &#xff0c;命令&#xff1a;setenforce 0 实验效果为Apache处理html静态资源&#xff0c;Tomcat处理jsp…

jquery调用WCF

1.添加新项&#xff1a;启用了Ajax的WCF。2.[ServiceContract(Namespace "")] [AspNetCompatibilityRequirements(RequirementsMode AspNetCompatibilityRequirementsMode.Allowed)] public class Service { [OperationContract] public st…

java 数组地图绘画_Java将地图转换为数组[Snippet]

让我们编写一个将Map值转换为String数组的Java程序。将映射值转换为数组示例包 网。javaguides。corejava ;导入 java。util。数组 ;导入 java。util。收藏 ;导入 java。util。HashMap ;导入 java。util。地图 ;公共 类 MapToArrayExample {public String [] mapValuesToArray…

【2016.11.16】HTML学习笔记

先是学习了思维导图的使用方法&#xff0c;然后自学了HTML 下面是自学的笔记 转载于:https://www.cnblogs.com/shan01/p/6071081.html

使用SharpZipLib.dll压缩zip

/// <summary> /// zip压缩 /// </summary> /// <param name"path">源文件夹路径</param> /// <param name"topath">目标文件路径</param> /// <returns>-1文件不存在,0未知…

php 日期时间 取日期,从PHP中的文本中提取日期,时间和日期范围

我正在构建一个本地事件日历,它采用RSS提要和网站抓取并从中提取事件日期.我之前已经问过如何从PHP here中的文本中提取日期,并在MarcDefiant时获得了一个很好的答案&#xff1a;function parse_date_tokens($tokens) {# only try to extract a date if we have 2 or more toke…

不同文件类型输出及ContentType表

//输出Response.Clear(); Response.BufferOutput false; Response.ContentEncoding System.Text.Encoding.UTF8; Response.AddHeader("Content-Disposition", "attachment;filename" HttpUtility.UrlEnco…

【2016.11.17】HTML学习笔记第二天

今天是自习 下面是我的自学内容 转载于:https://www.cnblogs.com/shan01/p/6074683.html

php 安装php soap.dll,php_soap.dll下载

php_soap.dll原因说明当你的系统出现&#xff1a;php_soap.dll缺失&#xff0c;php_soap.dll故障&#xff0c;php_soap.dll删除&#xff0c;开机php_soap.dll报错&#xff0c;php_soap.dll源码缺失&#xff0c;无法加载php_soap.dll&#xff0c;计算机丢失php_soap.dll&#xf…

firefox input file宽度失效

file样式设置.upload_bg{position:relative; text-align:center; } .upload_bg input {position:absolute; left:0px;*left:-10px; top:0px; height:19px; width:100%;*width:1px; filter:alpha(opacity0);opacity:0.0;} <input type"file" size1 style"w…

1.0 C++远征:数据的封装

4-1数据的封装 1.如何进行数据封装 ​ 未进行数据的封装&#xff0c;成员变量容易发生数据的泄露&#xff1a; ​ 进行数据的封装&#xff08;这是面向对象的思想&#xff09;&#xff0c;成员变量设为private属性&#xff0c;只能通过set和get方法来赋值和取值&#xff0c;提高…