引导

本章主要介绍下AbstractRegistry、FailbackRegistry的作用和源码。

AbstractRegistry

首先,直接引出这个类的作用,该类主要把服务提供者信息缓存本地文件上,文件目录是:当前用户目录下的/.dubbo/dubbo-registry-${application}-${hos}-${port}.cache。

在解读源码前,先阅读下AbstractRegistry类的成员变量,从成员变量中可以看到这个类是怎么完成数据的本地化存储的。

    // URL 地址分隔符
private static final char URL_SEPARATOR = ' '; //URL地址正则表达式,任何空白符
private static final String URL_SPLIT = "\\s+"; // 参数保存到本地文件的最大重试次数
private static final int MAX_RETRY_TIMES_SAVE_PROPERTIES = 3; // 需要保存的参数
private final Properties properties = new Properties(); // 保存线程,可以看出是否异步保存
private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true)); // 是否同步保存
private boolean syncSaveFile; // 上一次保存的版本,每次保存更新+1
private final AtomicLong lastCacheChanged = new AtomicLong(); // 保存重试的次数
private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger(); // 服务注册的URL保存在这里
private final Set<URL> registered = new ConcurrentHashSet<>(); // 订阅的URL,key:消费端订阅者URL,values: 通知监听器
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<>(); // 订阅的URL,key:消费端订阅者URL,values: Map ,key:服务提供者的名字(默认为providers,configurations,routers),和服务提供者URL
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>(); //当前注册URL,于指定的注册中心连接的URL
private URL registryUrl; //本地文件
private File file;

入口,构造函数

public AbstractRegistry(URL url) {
// 保存与注册中心连接的url.
setUrl(url); //判断是否需要缓存本地文件,默认需要,文件地址
if (url.getParameter(REGISTRY__LOCAL_FILE_CACHE_ENABLED, true)) {
// Start file save timer
// 是否同步保存,默认是异步
syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false); //文件名和路径一般在,当前用户目录下的/.dubbo/dubbo-registry-${application}-${hos}-${port}.cache
//例如dubbo-registry-dubbo-demo-annotation-provider-106.52.187.48-2181.cache
String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress().replaceAll(":", "-") + ".cache";
String filename = url.getParameter(FILE_KEY, defaultFilename);
File file = null;
//创建文件
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
} this.file = file;
//在启动订阅中心时,我们需要读取本地缓存文件,以便将来进行注册表容错处理。 其实就是把本地文件file的内容 放入参数properties里
loadProperties();
// 进行通知url.getBackupUrls(),第一个参数就是url 自己本身
notify(url.getBackupUrls());
}
}

上面的注释已经非常的清晰了,这里就不在描述,需要关注的是notify()这个函数,所以当每个服务注册和订阅时,首次创建注册中心都会进行notify操作。具体来看下notify方法。

protected void notify(List<URL> urls) {
// 这里是注册中心链接的url,里面包括了服务提供方的信息(key:interface等)
if (CollectionUtils.isEmpty(urls)) {
return;
}
// 这里循环所有的订阅URL
for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
URL url = entry.getKey(); // 查看订阅的url 是否是订阅当前的注册服务。不是的话,轮训下一个
if (!UrlUtils.isMatch(url, urls.get(0))) {
continue;
} // 这里订阅的URL的通知监听器
Set<NotifyListener> listeners = entry.getValue();
if (listeners != null) {
// 然后进行依次遍历通知
for (NotifyListener listener : listeners) {
try { notify(url, listener, filterEmpty(url, urls));
} catch (Throwable t) {
logger.error("Failed to notify registry event, urls: " + urls + ", cause: " + t.getMessage(), t);
}
}
}
}
}

接下来看下具体的notify(URL url, NotifyListener listener, List urls)

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((CollectionUtils.isEmpty(urls))
&& !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);
} //result,key: providers,configurators,routers ,values:urls.
Map<String, List<URL>> result = new HashMap<>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) { // 这里再一次判断,订阅URL 和服务提供者URL 是否匹配
String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
} Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList); //监听通知
listener.notify(categoryList);
// 我们将在每次通知后更新缓存文件。
// 当我们的注册表由于网络抖动而出现订阅失败时,我们至少可以返回现有的缓存URL。
saveProperties(url);
}
}

接着看下saveProperties,

private void saveProperties(URL url) {
if (file == null) {
return;
} try {
StringBuilder buf = new StringBuilder();
// 得到该订阅URL 的所有服务提供者URLS,并放入buf中
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified != null) {
for (List<URL> us : categoryNotified.values()) {
for (URL u : us) {
if (buf.length() > 0) {
buf.append(URL_SEPARATOR);
}
buf.append(u.toFullString());
}
}
}
// key 服务接口,value :提供者URL.
properties.setProperty(url.getServiceKey(), buf.toString());
long version = lastCacheChanged.incrementAndGet(); // 新增一个版本
if (syncSaveFile) { //同步保存
doSaveProperties(version);
} else { // 异步保存
registryCacheExecutor.execute(new SaveProperties(version));
}
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}

从上面可以知道,把消费端的订阅的服务信息存入了file文件中,doSaveProperties就是文件操作,不进行分析。再一次强调下,消费端订阅时,会订阅某个具体服务下3个节点(providers,configurations,routers)。

FailbackRegistry

接着,FailbackRegistry继承自AbstractRegistry。

其构造函数如下,可以得知除了调用AbstractRegistry构造方法外,并且创建一个HashedWheelTimer类型的定时器。

public FailbackRegistry(URL url) {
super(url);
this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD); // since the retry task will not be very much. 128 ticks is enough.
//集运时间轮转的重试线程器
retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer", true), retryPeriod, TimeUnit.MILLISECONDS, 128);
}

并且FailbackRegistry 成员记录一组注册失败和订阅失败的集合,然后通过retryTimer定式扫描这些失败集合,重新发起订阅和注册。之后会单独拿一章节来讲解这个时间轮算法。

下面是失败集合:

// 这里是注册失败的urls
private final ConcurrentMap<URL, FailedRegisteredTask> failedRegistered = new ConcurrentHashMap<URL, FailedRegisteredTask>(); // 这里是取消注册失败的urls
private final ConcurrentMap<URL, FailedUnregisteredTask> failedUnregistered = new ConcurrentHashMap<URL, FailedUnregisteredTask>(); // 这里是订阅失败的urls
private final ConcurrentMap<Holder, FailedSubscribedTask> failedSubscribed = new ConcurrentHashMap<Holder, FailedSubscribedTask>(); // 这里是取消订阅失败的urls
private final ConcurrentMap<Holder, FailedUnsubscribedTask> failedUnsubscribed = new ConcurrentHashMap<Holder, FailedUnsubscribedTask>(); // 这里是通知notify()方法失败异常时的url集合,会进行重新通知
private final ConcurrentMap<Holder, FailedNotifiedTask> failedNotified = new ConcurrentHashMap<Holder, FailedNotifiedTask>();

本章的内容比较简单,主要是接上一章节Dubbo系列之 (二)Registry注册中心-注册(1)的内容,使其完整。目前我们已经完成大部分dubbo是如何与注册中心交互的,接下来的章节我们讲继续分享dubbo服务的导出和订阅等内容。

Dubbo系列之 (二)Registry注册中心-注册(2)的更多相关文章

  1. Dubbo系列之 (二)Registry注册中心-注册(1)

    引导 dubbo的服务的注册与发现,需要通过第三方注册中心来协助完成,目前dubbo支持的注册中心包括 zookeeper,consul,etcd3,eureka,nacas,redis,sofa.这 ...

  2. spring cloud系列教程第八篇-修改服务名称及获取注册中心注册者的信息

    spring cloud系列教程第八篇-修改服务名称及获取注册中心注册者的信息 本文主要内容: 1:管理页面主机名及访问ip信息提示修改 2:获取当前注册中心的服务列表及每个服务对于的服务提供者列表 ...

  3. dubbo-admin与多注册中心(注册中心集群)

    在使用dubbo时,注册中心是一个必要的架构组成成员.当我们的注册中心没有采取集群时,如何在dubbo-admin中配置,我们可以根据dubbo官方文档,很快找到我们的答案. 但是当注册中心集群之后怎 ...

  4. Dubbo系列(二)dubbo的环境搭建

    dubbo是一个分布式服务框架,提供一个SOA的解决方案.简单的说,dubbo就像在生产者和消费者中间架起了一座桥梁,使之能透明交互.本文旨在搭建一个可供使用和测试的dubbo环境,使用了spring ...

  5. 【SpringCloud】consul注册中心注册的服务为内网(局域网)IP

    一.前因 最近在做公司的一个微服务项目,技术架构为spring cloud + consul + SSM. 当我写完一个功能要在本地测试时,发现服务运行成功,但是前后端联调报500错误. 当时的第一个 ...

  6. Dubbo源代码实现三:注册中心Registry

    我们知道,对于服务治理框架来说,服务通信(RPC)和服务管理两部分必不可少,而服务管理又分为服务注册.服务发现和服务人工介入,我们来看看Dubbo框架的结构图(来源网络): 图中可以看出,服务提供者P ...

  7. Dubbo源码学习总结系列七---注册中心

    Dubbo注册中心是框架的核心模块,提供了服务注册发现(包括服务提供者.消费者.路由策略.覆盖规则)的功能,该功能集中体现了服务治理的特性.该模块结合Cluster模块实现了集群服务.Dubbo管理控 ...

  8. dubbo在idea下的使用创建 服务者,消费者 注册中心

    1.基于windows 下  spring 下的dubbo  需要书写配置文件 (1).创建带有web工程的项目 创建一个服务者 package cn.edu.aynu.bean; import lo ...

  9. Dubbo中多注册中心问题与服务分组

    一:注册中心 1.场景 Dubbo 支持同一服务向多注册中心同时注册, 或者不同服务分别注册到不同的注册中心上去, 甚至可以同时引用注册在不同注册中心上的同名服务. 2.多注册中心注册 中文站有些服务 ...

随机推荐

  1. 在ASP.NET中,<%= %>和<%# %>有什么区别

    asp.net中<%#%>出现在repeater gridview等控件中.用以绑定控件的datasource asp.net中<%%>的意思是 上运行c#或者vb代码,比如: ...

  2. Java线程的6种状态及切换

    Java中线程的状态分为6种. 1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法.2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running) ...

  3. git问题解决

    1.如果系统中有一些配置文件在服务器上做了配置修改,然后后续开发又新添加一些配置项的时候, 在发布这个配置文件的时候,会发生代码冲突: error: Your local changes to the ...

  4. 如何在CSDN博客开头处加上版权声明?

    1.首先在CSDN账号头像处打开"管理博客"选项. 2.然后在管理博客界面左侧找到设置下面的"博客设置"选项. 3.将博客设置里的"版权声明" ...

  5. link小图标以及表格的用法基础

    一.网页小图标的实现 实例: 实现方式: 效果: 二.表格基础 1.表格的组合标签 常用: table tr td caption ①table属性 border  边框 width  宽度 默认按照 ...

  6. layui常用插件(一) 轮播图

    轮播图 <html lang="en"> <head> <meta charset="UTF-8"> <meta ht ...

  7. 使用Spring Validation优雅地校验参数

    写得好的没我写得全,写得全的没我写得好 引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断? public String add(Use ...

  8. 5-Pandas之常用的描述性统计函数、汇总函数

    一.常用的描述性统计函数  函数 作用 函数 作用 count 非缺失样本的数量 sum 求和 mean 均值 mad 平均绝对偏差(Mean absolute deviation) median 中 ...

  9. Python 字典(Dictionary) clear()方法

    Python 字典(Dictionary) clear()方法 描述 Python 字典(Dictionary) clear() 函数用于删除字典内所有元素.高佣联盟 www.cgewang.com ...

  10. PDOStatement::closeCursor

    PDOStatement::closeCursor — 关闭游标,使语句能再次被执行.(PHP 5 >= 5.1.0, PECL pdo >= 0.9.0) 说明 语法 bool PDOS ...