前言:
第一次发表技术文章,勿吐槽哈,给点面子,谢谢
本文从dubbo中的registry分包来讲解,dubbo内部设计和实现细节,registry分包在com.alibaba.dubbo.registry包中,我参照的是2.5.2的较新版本。
为了方便代码阅读,我删除了很多的关于版权的注释信息。
注册表服务提供以下功能:
注册项为原子节点,原子节点在dubbo框架中定义为扩展自定义URL。
请自行维护URL格式定义。
信息异步通告的方式为时间模型,即需要订阅原子节点主题。
主题能够保证原子节点信息变化后,信息异步通告,接收者必须实现NotifyListener 接口。
在一些系统启动和信息同步性要求很高的场景,该方法会被调用,请自行确保线程安全性
package com.alibaba.dubbo.registry;
import java.util.List;
import com.alibaba.dubbo.common.URL;
/**
* RegistryService. (SPI, Prototype, ThreadSafe)
*
* @see com.alibaba.dubbo.registry.Registry
* @see com.alibaba.dubbo.registry.RegistryFactory#getRegistry(URL)
* @author william.liangf
*/
public interface RegistryService {
/**
* 注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则,等数据。
*
* 注册需处理契约:<br>
* 1. 当URL设置了check=false时,注册失败后不报错,在后台定时重试,否则抛出异常。<br>
* 2. 当URL设置了dynamic=false参数,则需持久存储,否则,当注册者出现断电等情况异常退出时,需自动删除。<br>
* 3. 当URL设置了category=routers时,表示分类存储,缺省类别为providers,可按分类部分通知数据。<br>
* 4. 当注册中心重启,网络抖动,不能丢失数据,包括断线自动删除数据。<br>
* 5. 允许URI相同但参数不同的URL并存,不能覆盖。<br>
*
* @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
*/
void register(URL url);
/**
* 取消注册.
*
* 取消注册需处理契约:<br>
* 1. 如果是dynamic=false的持久存储数据,找不到注册数据,则抛IllegalStateException,否则忽略。<br>
* 2. 按全URL匹配取消注册。<br>
*
* @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
*/
void unregister(URL url);
/**
* 订阅符合条件的已注册数据,当有注册数据变更时自动推送.
*
* 订阅需处理契约:<br>
* 1. 当URL设置了check=false时,订阅失败后不报错,在后台定时重试。<br>
* 2. 当URL设置了category=routers,只通知指定分类的数据,多个分类用逗号分隔,并允许星号通配,表示订阅所有分类数据。<br>
* 3. 允许以interface,group,version,classifier作为条件查询,如:interface=com.alibaba.foo.BarService&version=1.0.0<br>
* 4. 并且查询条件允许星号通配,订阅所有接口的所有分组的所有版本,或:interface=*&group=*&version=*&classifier=*<br>
* 5. 当注册中心重启,网络抖动,需自动恢复订阅请求。<br>
* 6. 允许URI相同但参数不同的URL并存,不能覆盖。<br>
* 7. 必须阻塞订阅过程,等第一次通知完后再返回。<br>
*
* @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);
/**
* 取消订阅.
*
* 取消订阅需处理契约:<br>
* 1. 如果没有订阅,直接忽略。<br>
* 2. 按全URL匹配取消订阅。<br>
*
* @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @param listener 变更事件监听器,不允许为空
*/
void unsubscribe(URL url, NotifyListener listener);
/**
* 查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
*
* @see com.alibaba.dubbo.registry.NotifyListener#notify(List)
* @param url 查询条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
* @return 已注册信息列表,可能为空,含义同{@link com.alibaba.dubbo.registry.NotifyListener#notify(List<URL>)}的参数。
*/
List<URL> lookup(URL url);
}
真正实现的注册表registry实现自Node和registryService,代码如下
package com.alibaba.dubbo.registry;
import com.alibaba.dubbo.common.Node;
import com.alibaba.dubbo.common.URL;
public interface Registry extends Node, RegistryService {
}
节点在dubbo中定义如下:
package com.alibaba.dubbo.common;
import com.alibaba.dubbo.common.URL;
/**
* Node. (API/SPI, Prototype, ThreadSafe)
*
* @author william.liangf
*/
public interface Node {
URL getUrl();
boolean isAvailable();
void destroy();
}
之后所用到的设计模式为:
简单工厂
动机:
为了针对redis、zookeeper、multicast、simple-registry等不用应用场景创建创建不同的注册表,但又方便扩展新的注册表应用。
优点:
针对不同的应用场景,可以扩展不同的应用以投入稳定环境中使用,而又不影响原有的体系框架。
角色:
抽象工厂 RegistryFactory(实际上是AbstractRegistryFactory)
抽象产品 Registry
类图:
源码:
抽象工厂:
/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.registry.support;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.registry.Registry;
import com.alibaba.dubbo.registry.RegistryFactory;
import com.alibaba.dubbo.registry.RegistryService;
/**
* AbstractRegistryFactory. (SPI, Singleton, ThreadSafe)
*
* @see com.alibaba.dubbo.registry.RegistryFactory
* @author william.liangf
*/
public abstract class AbstractRegistryFactory implements RegistryFactory {
// 日志输出
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRegistryFactory.class);
// 注册中心获取过程锁
private static final ReentrantLock LOCK = new ReentrantLock();
// 注册中心集合 Map<RegistryAddress, Registry>
private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();
/**
* 获取所有注册中心
*
* @return 所有注册中心
*/
public static Collection<Registry> getRegistries() {
return Collections.unmodifiableCollection(REGISTRIES.values());
}
/**
* 关闭所有已创建注册中心
*/
public static void destroyAll() {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Close all registries " + getRegistries());
}
// 锁定注册中心关闭过程
LOCK.lock();
try {
for (Registry registry : getRegistries()) {
try {
registry.destroy();
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
}
REGISTRIES.clear();
} finally {
// 释放锁
LOCK.unlock();
}
}
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();
}
}
protected abstract Registry createRegistry(URL url);
}
设计技巧:
定义注册中心管理模式
private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();
该属性可以使得注册表分级管理,类同与window操作系统中注册表的概念(分为Host、Machine级等),同时给给每一个注册表打标签
提供获取所以注册字典入口
public static Collection<Registry> getRegistries() ;
注意到返回的是集合对象,方便数据字典迭代查看相关信息
提供创建字典方法
protected abstract Registry createRegistry(URL url);
该方法是抽象方法,具体创建的字典是什么样的,有具体工厂来制定。
解决线程锁问题
使用ReentrantLock类而非sychonized机制,既保证线程并发的公平性又有效的解决线程争用过程中死锁问题。
模板方法
注意到在AbstractRegistryFactory 中有抽象方法
protected abstract Registry createRegistry(URL url);
该方法的具体实现延迟到子类中,父类定义的操作的具体算法骨架。
<!--EndFragment-->
分享到:
相关推荐
本文将深入分析"Dubbo源码分析-2(注册表AbstractRegistry设计技巧讲解)"这一主题,探讨`AbstractRegistry`的设计理念和实现细节。 `AbstractRegistry`是Dubbo中的抽象注册表类,它是所有具体注册表实现(如...
雷神对dubbo2.x版本的源码进行了刨析,这意味着对于学习Dubbo源码来说,可以通过阅读相关的源码部分来深入了解其内部工作机制。这通常包括了解服务暴露的具体实现细节,以及服务引用、网络通信、序列化机制、服务...
五、源码分析 理解分布式服务访问框架的源码有助于我们深入定制和优化框架。以Dubbo为例,其源码包含了服务注册、服务发现、负载均衡、服务过滤器、监控统计等多个模块。通过阅读源码,我们可以了解其内部设计模式,...
本资源提供了“从零开始写分布式服务框架”的最新版源码,对于想要深入理解分布式系统设计和服务化架构的开发者来说,是一个宝贵的实践学习材料。 分布式框架的核心目标是实现服务的解耦、负载均衡、容错处理以及...
简介 笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级、中级、高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情、执着,对IT的憧憬、向往!...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...