From 8675a4b67d0778d51ffe06c942f0b7ad9b626978 Mon Sep 17 00:00:00 2001 From: AmyliaY <471816751@qq.com> Date: Tue, 21 Apr 2020 23:12:39 +0800 Subject: [PATCH] =?UTF-8?q?Dubbo=E6=B3=A8=E5=86=8C=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E7=AE=80=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../registry/Dubbo注册中心模块简析.md | 2171 +++++++++++++++++ ...egistry-zookeeper模块工程结构图.png | Bin 0 -> 15219 bytes ...bo注册中心在zookeeper中的结构.png | Bin 0 -> 98083 bytes 3 files changed, 2171 insertions(+) create mode 100644 docs/Dubbo/registry/Dubbo注册中心模块简析.md create mode 100644 images/Dubbo/dubbo-registry-zookeeper模块工程结构图.png create mode 100644 images/Dubbo/dubbo注册中心在zookeeper中的结构.png diff --git a/docs/Dubbo/registry/Dubbo注册中心模块简析.md b/docs/Dubbo/registry/Dubbo注册中心模块简析.md new file mode 100644 index 0000000..4a127d4 --- /dev/null +++ b/docs/Dubbo/registry/Dubbo注册中心模块简析.md @@ -0,0 +1,2171 @@ +## 注册中心在Dubbo中的作用 +服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者Provider 会往注册中心注册服务,而消费者Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer以及Registry之间的依赖关系 如下图所示。 + +![avatar](/images/Dubbo/Dubbo工作原理图.png) + +## dubbo-registry 模块 结构分析 +dubbo的注册中心有多种实现方案,如:zookeeper、redis、multicast等,本章先看一下 dubbo-registry 模块的核心部分 dubbo-registry-api,具体实现部分放到下章来讲。dubbo-registry模块 的结构如下图所示。 + +![avatar](/images/Dubbo/dubbo-registry模块结构图.png) + +### Registry 核心组件类图 +典型的 接口 -> 抽象类 -> 实现类 的结构设计,如下图所示。 + +![avatar](/images/Dubbo/Registry组件类图.png) + +既然有Registry组件,那么按照很多框架的套路,肯定也有一个用于获取 Registry实例的RegistryFactory,其中用到了工厂方法模式,不同的工厂类用于获取不同类型的实例。其类图结构如下。 + +![avatar](/images/Dubbo/RegistryFactory组件类图.png) + +## 源码详解 +根据上面的类图,我们开始从上往下 详解dubbo中对于注册中心的设计以及实现。 +### RegistryService 接口 +RegistryService 是注册中心模块的服务接口,定义了注册、取消注册、订阅、取消订阅以及查询符合条件的已注册数据 等方法。这里统一说明一下URL,dubbo是以总线模式来时刻传递和保存配置信息的,配置信息都被放在URL上进行传递,随时可以取得相关配置信息,而这里提到了URL有别的作用,就是作为类似于节点的作用,首先服务提供者(Provider)启动时需要提供服务,就会向注册中心写下自己的URL地址。然后消费者启动时需要去订阅该服务,则会订阅Provider注册的地址,并且消费者也会写下自己的URL。 +```java +/** + * RegistryService. (SPI, Prototype, ThreadSafe) + * + * 注册中心服务接口 + */ +public interface RegistryService { + + /** + * 注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则 等数据。 + *

+ * 注册需处理契约:
+ * 1. 当URL设置了check=false时,注册失败后不报错,在后台定时重试,否则抛出异常。
+ * 2. 当URL设置了dynamic=false参数,则需持久存储,否则,当注册者出现断电等情况异常退出时,需自动删除。
+ * 3. 当URL设置了category=routers时,表示分类存储,缺省类别为providers,可按分类部分通知数据。
+ * 4. 当注册中心重启,网络抖动,不能丢失数据,包括断线自动删除数据。
+ * 5. 允许URI相同但参数不同的URL并存,不能覆盖。
+ * + * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin + */ + void register(URL url); + + /** + * 取消注册. + *

+ * 取消注册需处理契约:
+ * 1. 如果是dynamic=false的持久存储数据,找不到注册数据,则抛IllegalStateException,否则忽略。
+ * 2. 按全URL匹配取消注册。
+ * + * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin + */ + void unregister(URL url); + + /** + * 订阅符合条件的已注册数据,当有注册数据变更时自动推送. + *

+ * 订阅需处理契约:
+ * 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); + + /** + * 取消订阅. + *

+ * 取消订阅需处理契约:
+ * 1. 如果没有订阅,直接忽略。
+ * 2. 按全URL匹配取消订阅。
+ * + * @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); + + /** + * 查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。 + * + * @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)}的参数。 + * @see com.alibaba.dubbo.registry.NotifyListener#notify(List) + */ + List lookup(URL url); +} +``` + +### Registry 接口 +注册中心接口,把节点Node 以及注册中心服务RegistryService 的方法整合在了这个接口里面。该接口并没有自己的方法,就是继承了Node和RegistryService接口。这里的Node是节点的接口,里面协定了关于节点的一些操作方法,源码如下。 +```java +/** + * 注册中心接口 + */ +public interface Registry extends Node, RegistryService { +} + +public interface Node { + //获得节点地址 + URL getUrl(); + //判断节点是否可用 + boolean isAvailable(); + //销毁节点 + void destroy(); +} +``` + +### AbstractRegistry 抽象类 +实现了Registry接口的抽象类。为了减轻注册中心的压力,该抽象类把本地URL缓存到了property文件中,并且实现了注册中心的注册、订阅等方法。 +```java +/** + * 实现了Registry接口的抽象类,实现了如下方法: + * + * 1、通用的注册、订阅、查询、通知等方法 + * 2、读取和持久化注册数据到文件,以 properties 格式存储 + */ +public abstract class AbstractRegistry implements Registry { + + // URL地址分隔符,用于文件缓存中,服务提供者URL分隔 + private static final char URL_SEPARATOR = ' '; + // URL地址分隔正则表达式,用于解析文件缓存中服务提供者URL列表 + private static final String URL_SPLIT = "\\s+"; + + // Log output + protected final Logger logger = LoggerFactory.getLogger(getClass()); + /** + * 本地磁盘缓存。 + * 1. 其中特殊的 key 值 .registies 记录注册中心列表 TODO 8019 芋艿,特殊的 key 是 + * 2. 其它均为 {@link #notified} 服务提供者列表 + */ + private final Properties properties = new Properties(); + /** + * 注册中心缓存写入执行器。 + * 线程数=1 + */ + // File cache timing writing + private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true)); + /** + * 是否同步保存文件 + */ + private final boolean syncSaveFile; + /** + * 数据版本号 + */ + private final AtomicLong lastCacheChanged = new AtomicLong(); + /** + * 已注册 URL 集合。 + * 注册的 URL 可以是服务提供者的,也可以是服务消费者的 + */ + private final Set registered = new ConcurrentHashSet(); + /** + * 订阅 URL 的监听器集合 + * key:订阅者的 URL ,例如消费者的 URL + */ + private final ConcurrentMap> subscribed = new ConcurrentHashMap>(); + /** + * 被通知的 URL 集合 + * key1:消费者的 URL ,例如消费者的 URL ,和 {@link #subscribed} 的键一致 + * key2:分类,例如:providers、consumers、routes、configurators。【实际无 consumers ,因为消费者不会去订阅另外的消费者的列表】 + * 在 {@link Constants} 中,以 "_CATEGORY" 结尾 + */ + private final ConcurrentMap>> notified = new ConcurrentHashMap>>(); + /** + * 注册中心 URL + */ + private URL registryUrl; + /** + * 本地磁盘缓存文件,缓存注册中心的数据 + */ + private File file; + /** + * 是否销毁 + */ + private AtomicBoolean destroyed = new AtomicBoolean(false); + + public AbstractRegistry(URL url) { + setUrl(url); + // Start file save timer + syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false); + // 获得 `file` + String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(Constants.APPLICATION_KEY) + "-" + url.getAddress() + ".cache"); + 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 store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!"); + } + } + } + this.file = file; + // 加载本地磁盘缓存文件到内存缓存 + loadProperties(); + // 通知监听器,URL 变化结果 + notify(url.getBackupUrls()); // 【TODO 8020】为什么构造方法,要通知,连监听器都没注册 + } + + protected static List filterEmpty(URL url, List urls) { + if (urls == null || urls.isEmpty()) { + List result = new ArrayList(1); + result.add(url.setProtocol(Constants.EMPTY_PROTOCOL)); + return result; + } + return urls; + } + + @Override + public URL getUrl() { + return registryUrl; + } + + protected void setUrl(URL url) { + if (url == null) { + throw new IllegalArgumentException("registry url == null"); + } + this.registryUrl = url; + } + + public Set getRegistered() { + return registered; + } + + public Map> getSubscribed() { + return subscribed; + } + + public Map>> getNotified() { + return notified; + } + + public File getCacheFile() { + return file; + } + + public Properties getCacheProperties() { + return properties; + } + + public AtomicLong getLastCacheChanged() { + return lastCacheChanged; + } + + /** + * 保存内存缓存到本地磁盘缓存文件,即 {@link #properties} => {@link #file} + * + * @param version 数据版本号 + */ + public void doSaveProperties(long version) { + if (version < lastCacheChanged.get()) { + return; + } + if (file == null) { + return; + } + // Save + try { + // 创建 .lock 文件 + File lockfile = new File(file.getAbsolutePath() + ".lock"); + if (!lockfile.exists()) { + lockfile.createNewFile(); + } + // 随机读写文件操作 + RandomAccessFile raf = new RandomAccessFile(lockfile, "rw"); + try { + FileChannel channel = raf.getChannel(); + try { + // 获得文件锁 + FileLock lock = channel.tryLock(); + // 获取失败 + if (lock == null) { + throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties"); + } + // 获取成功,进行保存 + // Save + try { + if (!file.exists()) { + file.createNewFile(); + } + FileOutputStream outputFile = new FileOutputStream(file); + try { + properties.store(outputFile, "Dubbo Registry Cache"); + } finally { + outputFile.close(); + } + // 释放文件锁 + } finally { + lock.release(); + } + // 释放文件 Channel + } finally { + channel.close(); + } + // 释放随机读写文件操作 + } finally { + raf.close(); + } + } catch (Throwable e) { + // 版本号过小,不保存 + if (version < lastCacheChanged.get()) { + return; + // 重新异步保存,一般情况下为上面的获取锁失败抛出的异常。通过这样的方式,达到保存成功。 + } else { + registryCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet())); + } + logger.warn("Failed to save registry store file, cause: " + e.getMessage(), e); + } + } + + /** + * 加载本地磁盘缓存文件到内存缓存,即 {@link #file} => {@link #properties} + */ + private void loadProperties() { + if (file != null && file.exists()) { + InputStream in = null; + try { + // 文件流 + in = new FileInputStream(file); + // 读取文件流 + properties.load(in); + if (logger.isInfoEnabled()) { + logger.info("Load registry store file " + file + ", data: " + properties); + } + } catch (Throwable e) { + logger.warn("Failed to load registry store file " + file, e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + logger.warn(e.getMessage(), e); + } + } + } + } + } + + /** + * 从 `properties` 中获得缓存的 URL 集合 + * + * @param url URL + * @return URL 集合 + */ + public List getCacheUrls(URL url) { + for (Map.Entry entry : properties.entrySet()) { + String key = (String) entry.getKey(); + String value = (String) entry.getValue(); + if (key != null && key.length() > 0 // 非空 + && key.equals(url.getServiceKey()) // 服务键匹配 + && (Character.isLetter(key.charAt(0)) || key.charAt(0) == '_') // TODO 芋艿,_ 是什么 + && value != null && value.length() > 0) { // 值非空 + String[] arr = value.trim().split(URL_SPLIT); + List urls = new ArrayList(); + for (String u : arr) { + urls.add(URL.valueOf(u)); + } + return urls; + } + } + return null; + } + + @Override + public List lookup(URL url) { + List result = new ArrayList(); + Map> notifiedUrls = getNotified().get(url); + // 有数据,遍历数据获得 + if (notifiedUrls != null && notifiedUrls.size() > 0) { + // 遍历 + for (List urls : notifiedUrls.values()) { + for (URL u : urls) { + if (!Constants.EMPTY_PROTOCOL.equals(u.getProtocol())) { + result.add(u); + } + } + } + // 无数据,通过发起订阅的方式得到数据后,遍历数据获得 + } else { + // 创建 NotifyListener 对象 + final AtomicReference> reference = new AtomicReference>(); + NotifyListener listener = new NotifyListener() { + public void notify(List urls) { + reference.set(urls); + } + }; + // 订阅获得数据 + subscribe(url, listener); // Subscribe logic guarantees the first notify to return + List urls = reference.get(); + // 遍历 + if (urls != null && !urls.isEmpty()) { + for (URL u : urls) { + if (!Constants.EMPTY_PROTOCOL.equals(u.getProtocol())) { + result.add(u); + } + } + } + } + return result; + } + + @Override + public void register(URL url) { + if (url == null) { + throw new IllegalArgumentException("register url == null"); + } + if (logger.isInfoEnabled()) { + logger.info("Register: " + url); + } + // 添加到 registered 集合 + registered.add(url); + } + + @Override + public void unregister(URL url) { + if (url == null) { + throw new IllegalArgumentException("unregister url == null"); + } + if (logger.isInfoEnabled()) { + logger.info("Unregister: " + url); + } + // 移除出 registered 集合 + registered.remove(url); + } + + @Override + public void subscribe(URL url, NotifyListener listener) { + if (url == null) { + throw new IllegalArgumentException("subscribe url == null"); + } + if (listener == null) { + throw new IllegalArgumentException("subscribe listener == null"); + } + if (logger.isInfoEnabled()) { + logger.info("Subscribe: " + url); + } + // 添加到 subscribed 集合 + Set listeners = subscribed.get(url); + if (listeners == null) { + subscribed.putIfAbsent(url, new ConcurrentHashSet()); + listeners = subscribed.get(url); + } + listeners.add(listener); + } + + @Override + public void unsubscribe(URL url, NotifyListener listener) { + if (url == null) { + throw new IllegalArgumentException("unsubscribe url == null"); + } + if (listener == null) { + throw new IllegalArgumentException("unsubscribe listener == null"); + } + if (logger.isInfoEnabled()) { + logger.info("Unsubscribe: " + url); + } + // 移除出 subscribed 集合 + Set listeners = subscribed.get(url); + if (listeners != null) { + listeners.remove(listener); + } + } + + /** + * 恢复注册和订阅 + * + * @throws Exception 发生异常 + */ + protected void recover() throws Exception { + // register 恢复注册 + Set recoverRegistered = new HashSet(getRegistered()); + if (!recoverRegistered.isEmpty()) { + if (logger.isInfoEnabled()) { + logger.info("Recover register url " + recoverRegistered); + } + for (URL url : recoverRegistered) { + register(url); + } + } + // subscribe 恢复订阅 + Map> recoverSubscribed = new HashMap>(getSubscribed()); + if (!recoverSubscribed.isEmpty()) { + if (logger.isInfoEnabled()) { + logger.info("Recover subscribe url " + recoverSubscribed.keySet()); + } + for (Map.Entry> entry : recoverSubscribed.entrySet()) { + URL url = entry.getKey(); + for (NotifyListener listener : entry.getValue()) { + subscribe(url, listener); + } + } + } + } + + /** + * 通知监听器,URL 变化结果。 + * + * @param urls 通知的 URL 变化结果(全量数据) + */ + protected void notify(List urls) { + if (urls == null || urls.isEmpty()) return; + // 循环 `subscribed` ,通知监听器们 + for (Map.Entry> entry : getSubscribed().entrySet()) { + URL url = entry.getKey(); + // 匹配 + if (!UrlUtils.isMatch(url, urls.get(0))) { + continue; + } + // 通知监听器 + Set 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); + } + } + } + } + } + + /** + * 通知监听器,URL 变化结果。 + * + * 数据流向 `urls` => {@link #notified} => {@link #properties} => {@link #file} + * + * @param url 消费者 URL + * @param listener 监听器 + * @param urls 通知的 URL 变化结果(全量数据) + */ + 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.isEmpty()) + && !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); + } + // 将 `urls` 按照 `url.parameter.category` 分类,添加到集合 + // 注意,特殊情况,使用 curator 连接 Zookeeper 时,若是服务消费者,连接断开,会出现 category=providers,configurations,routes + 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; + } + // 获得消费者 URL 对应的在 `notified` 中,通知的 URL 变化结果(全量数据) + Map> categoryNotified = notified.get(url); + if (categoryNotified == null) { + notified.putIfAbsent(url, new ConcurrentHashMap>()); + categoryNotified = notified.get(url); + } + // 【按照分类循环】处理通知的 URL 变化结果(全量数据) + for (Map.Entry> entry : result.entrySet()) { + String category = entry.getKey(); + List categoryList = entry.getValue(); + // 覆盖到 `notified` + // 当某个分类的数据为空时,会依然有 urls 。其中 `urls[0].protocol = empty` ,通过这样的方式,处理所有服务提供者为空的情况。 + categoryNotified.put(category, categoryList); + // 保存到文件 + saveProperties(url); + // 通知监听器 + listener.notify(categoryList); + } + } + + /** + * 保存单个消费者 URL 对应,在 `notified` 的数据,到文件。 + * + * @param url 消费者 URL + */ + private void saveProperties(URL url) { + if (file == null) { + return; + } + + try { + // 拼接 URL + StringBuilder buf = new StringBuilder(); + Map> categoryNotified = notified.get(url); + if (categoryNotified != null) { + for (List us : categoryNotified.values()) { + for (URL u : us) { + if (buf.length() > 0) { + buf.append(URL_SEPARATOR); + } + buf.append(u.toFullString()); + } + } + } + // 设置到 properties 中 + 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); + } + } + + /** + * 取消注册和订阅 + */ + @Override + public void destroy() { + // 已销毁,跳过 + if (!destroyed.compareAndSet(false, true)) { + return; + } + if (logger.isInfoEnabled()) { + logger.info("Destroy registry:" + getUrl()); + } + // 取消注册 + Set destroyRegistered = new HashSet(getRegistered()); + if (!destroyRegistered.isEmpty()) { + for (URL url : new HashSet(getRegistered())) { + if (url.getParameter(Constants.DYNAMIC_KEY, true)) { + try { + unregister(url); // 取消注册 + if (logger.isInfoEnabled()) { + logger.info("Destroy unregister url " + url); + } + } catch (Throwable t) { + logger.warn("Failed to unregister url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t); + } + } + } + } + // 取消订阅 + Map> destroySubscribed = new HashMap>(getSubscribed()); + if (!destroySubscribed.isEmpty()) { + for (Map.Entry> entry : destroySubscribed.entrySet()) { + URL url = entry.getKey(); + for (NotifyListener listener : entry.getValue()) { + try { + unsubscribe(url, listener); // 取消订阅 + if (logger.isInfoEnabled()) { + logger.info("Destroy unsubscribe url " + url); + } + } catch (Throwable t) { + logger.warn("Failed to unsubscribe url " + url + " to registry " + getUrl() + " on destroy, cause: " + t.getMessage(), t); + } + } + } + } + } + + public String toString() { + return getUrl().toString(); + } + + /** + * 保存配置的 Runnable任务 + */ + private class SaveProperties implements Runnable { + + /** + * 数据版本号 + */ + private long version; + + private SaveProperties(long version) { + this.version = version; + } + + public void run() { + doSaveProperties(version); + } + } +} +``` + +### FailbackRegistry 抽象类 +FailbackRegistry抽象类 继承了上面的 AbstractRegistry,AbstractRegistry中的注册、订阅等方法,实际上就是一些内存缓存的变化,而真正的注册订阅的实现逻辑在FailbackRegistry实现,并且FailbackRegistry提供了失败重试的机制。 +```java +/** + * 支持失败重试的 FailbackRegistry抽象类 + */ +public abstract class FailbackRegistry extends AbstractRegistry { + + /** + * 定时任务执行器 + */ + private final ScheduledExecutorService retryExecutor = Executors. + newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryFailedRetryTimer", true)); + + /** + * 失败重试定时器,定时检查是否有请求失败,如有,无限次重试 + */ + private final ScheduledFuture retryFuture; + /** + * 注册失败的 URL 集合 + */ + private final Set failedRegistered = new ConcurrentHashSet(); + /** + * 取消注册失败的 URL 集合 + */ + private final Set failedUnregistered = new ConcurrentHashSet(); + /** + * 订阅失败的监听器集合 + */ + private final ConcurrentMap> failedSubscribed = new ConcurrentHashMap>(); + /** + * 取消订阅失败的监听器集合 + */ + private final ConcurrentMap> failedUnsubscribed = new ConcurrentHashMap>(); + /** + * 通知失败的 URL 集合 + */ + private final ConcurrentMap>> failedNotified = new ConcurrentHashMap>>(); + + /** + * 是否销毁 + */ + private AtomicBoolean destroyed = new AtomicBoolean(false); + + public FailbackRegistry(URL url) { + super(url); + // 重试频率,单位:毫秒 + int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD); + // 创建失败重试定时器 + this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() { + public void run() { + // Check and connect to the registry + try { + retry(); + } catch (Throwable t) { // Defensive fault tolerance + logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t); + } + } + }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS); + } + + public Future getRetryFuture() { + return retryFuture; + } + + public Set getFailedRegistered() { + return failedRegistered; + } + + public Set getFailedUnregistered() { + return failedUnregistered; + } + + public Map> getFailedSubscribed() { + return failedSubscribed; + } + + public Map> getFailedUnsubscribed() { + return failedUnsubscribed; + } + + public Map>> getFailedNotified() { + return failedNotified; + } + + /** + * 添加到 `failedSubscribed` + */ + private void addFailedSubscribed(URL url, NotifyListener listener) { + Set listeners = failedSubscribed.get(url); + if (listeners == null) { + failedSubscribed.putIfAbsent(url, new ConcurrentHashSet()); + listeners = failedSubscribed.get(url); + } + listeners.add(listener); + } + + /** + * 移除出 `failedSubscribed` `failedUnsubscribed` `failedNotified` + */ + private void removeFailedSubscribed(URL url, NotifyListener listener) { + // 移除出 `failedSubscribed` + Set listeners = failedSubscribed.get(url); + if (listeners != null) { + listeners.remove(listener); + } + // 移除出 `failedUnsubscribed` + listeners = failedUnsubscribed.get(url); + if (listeners != null) { + listeners.remove(listener); + } + // 移除出 `failedNotified` + Map> notified = failedNotified.get(url); + if (notified != null) { + notified.remove(listener); + } + } + + @Override + public void register(URL url) { + // 已销毁,跳过 + if (destroyed.get()){ + return; + } + // 添加到 `registered` 变量 + super.register(url); + // 移除出 `failedRegistered` `failedUnregistered` 变量 + 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()); // 非消费者。消费者会在 `ReferenceConfig#createProxy(...)` 方法中,调用 `Invoker#avalible()` 方法,进行检查。 + 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`,定时重试 + failedRegistered.add(url); + } + } + + @Override + public void unregister(URL url) { + // 已销毁,跳过 + if (destroyed.get()){ + return; + } + // 移除出 `registered` 变量 + super.unregister(url); + // 移除出 `failedRegistered` `failedUnregistered` 变量 + failedRegistered.remove(url); + failedUnregistered.remove(url); + // 向注册中心发送取消注册请求 + try { + doUnregister(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 unregister " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t); + } else { + logger.error("Failed to uregister " + url + ", waiting for retry, cause: " + t.getMessage(), t); + } + + // 将失败的取消注册请求记录到 `failedUnregistered`,定时重试 + failedUnregistered.add(url); + } + } + + @Override + public void subscribe(URL url, NotifyListener listener) { + // 已销毁,跳过 + if (destroyed.get()){ + return; + } + // 移除出 `subscribed` 变量 + super.subscribe(url, listener); + // 移除出 `failedSubscribed` `failedUnsubscribed` `failedNotified` + removeFailedSubscribed(url, listener); + // 向注册中心发送订阅请求 + try { + doSubscribe(url, listener); + } catch (Exception e) { + Throwable t = e; + + // 如果有缓存的 URL 集合,进行通知。后续订阅成功后,会使用最新的 URL 集合,进行通知。 + List urls = getCacheUrls(url); + if (urls != null && !urls.isEmpty()) { + notify(url, listener, urls); + logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t); + } else { + // 如果开启了启动时检测,则直接抛出异常 + // If the startup detection is opened, the Exception is thrown directly. + boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) + && url.getParameter(Constants.CHECK_KEY, true); + boolean skipFailback = t instanceof SkipFailbackWrapperException; + if (check || skipFailback) { + if (skipFailback) { + t = t.getCause(); + } + throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t); + } else { + logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t); + } + } + + // 将失败的订阅请求记录到 `failedSubscribed`,定时重试 + // Record a failed registration request to a failed list, retry regularly + addFailedSubscribed(url, listener); + } + } + + @Override + public void unsubscribe(URL url, NotifyListener listener) { + // 已销毁,跳过 + if (destroyed.get()){ + return; + } + // 移除出 `unsubscribed` 变量 + super.unsubscribe(url, listener); + // 移除出 `failedSubscribed` `failedUnsubscribed` `failedNotified` + removeFailedSubscribed(url, listener); + // 向注册中心发送取消订阅请求 + try { + // Sending a canceling subscription request to the server side + doUnsubscribe(url, listener); + } catch (Exception e) { + Throwable t = e; + + // 如果开启了启动时检测,则直接抛出异常 + // If the startup detection is opened, the Exception is thrown directly. + boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) + && url.getParameter(Constants.CHECK_KEY, true); + boolean skipFailback = t instanceof SkipFailbackWrapperException; + if (check || skipFailback) { + if (skipFailback) { + t = t.getCause(); + } + throw new IllegalStateException("Failed to unsubscribe " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t); + } else { + logger.error("Failed to unsubscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t); + } + + // 将失败的订阅请求记录到 `failedUnsubscribed`,定时重试 + // Record a failed registration request to a failed list, retry regularly + Set listeners = failedUnsubscribed.get(url); + if (listeners == null) { + failedUnsubscribed.putIfAbsent(url, new ConcurrentHashSet()); + listeners = failedUnsubscribed.get(url); + } + listeners.add(listener); + } + } + + @Override + 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"); + } + // 通知监听器 + try { + doNotify(url, listener, urls); + } catch (Exception t) { + // 将失败的通知记录到 `failedNotified`,定时重试 + Map> listeners = failedNotified.get(url); + if (listeners == null) { + failedNotified.putIfAbsent(url, new ConcurrentHashMap>()); + listeners = failedNotified.get(url); + } + listeners.put(listener, urls); + logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t); + } + } + + protected void doNotify(URL url, NotifyListener listener, List urls) { + super.notify(url, listener, urls); + } + + @Override + protected void recover() throws Exception { + // register 恢复注册,添加到 `failedRegistered` ,定时重试 + Set recoverRegistered = new HashSet(getRegistered()); + if (!recoverRegistered.isEmpty()) { + if (logger.isInfoEnabled()) { + logger.info("Recover register url " + recoverRegistered); + } + for (URL url : recoverRegistered) { + failedRegistered.add(url); + } + } + // subscribe 恢复订阅,添加到 `failedSubscribed` ,定时重试 + Map> recoverSubscribed = new HashMap>(getSubscribed()); + if (!recoverSubscribed.isEmpty()) { + if (logger.isInfoEnabled()) { + logger.info("Recover subscribe url " + recoverSubscribed.keySet()); + } + for (Map.Entry> entry : recoverSubscribed.entrySet()) { + URL url = entry.getKey(); + for (NotifyListener listener : entry.getValue()) { + addFailedSubscribed(url, listener); + } + } + } + } + + /** + * 重试 + */ + protected void retry() { + // 重试执行注册 + if (!failedRegistered.isEmpty()) { + Set failed = new HashSet(failedRegistered); // 避免并发冲突 + if (failed.size() > 0) { + if (logger.isInfoEnabled()) { + logger.info("Retry register " + failed); + } + try { + for (URL url : failed) { + try { + // 执行注册 + doRegister(url); + // 移除出 `failedRegistered` + failedRegistered.remove(url); + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + // 重试执行取消注册 + if (!failedUnregistered.isEmpty()) { + Set failed = new HashSet(failedUnregistered); // 避免并发冲突 + if (!failed.isEmpty()) { + if (logger.isInfoEnabled()) { + logger.info("Retry unregister " + failed); + } + try { + for (URL url : failed) { + try { + // 执行取消注册 + doUnregister(url); + // 移除出 `failedUnregistered` + failedUnregistered.remove(url); + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry unregister " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry unregister " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + // 重试执行注册 + if (!failedSubscribed.isEmpty()) { + Map> failed = new HashMap>(failedSubscribed); // 避免并发冲突 + for (Map.Entry> entry : new HashMap>(failed).entrySet()) { + if (entry.getValue() == null || entry.getValue().size() == 0) { + failed.remove(entry.getKey()); + } + } + if (failed.size() > 0) { + if (logger.isInfoEnabled()) { + logger.info("Retry subscribe " + failed); + } + try { + for (Map.Entry> entry : failed.entrySet()) { + URL url = entry.getKey(); + Set listeners = entry.getValue(); + for (NotifyListener listener : listeners) { + try { + // 执行注册 + doSubscribe(url, listener); + // 移除出监听器 + listeners.remove(listener); + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + // 重试执行取消注册 + if (!failedUnsubscribed.isEmpty()) { + Map> failed = new HashMap>(failedUnsubscribed); + for (Map.Entry> entry : new HashMap>(failed).entrySet()) { + if (entry.getValue() == null || entry.getValue().isEmpty()) { + failed.remove(entry.getKey()); + } + } + if (failed.size() > 0) { + if (logger.isInfoEnabled()) { + logger.info("Retry unsubscribe " + failed); + } + try { + for (Map.Entry> entry : failed.entrySet()) { + URL url = entry.getKey(); + Set listeners = entry.getValue(); + for (NotifyListener listener : listeners) { + try { + // 执行取消注册 + doUnsubscribe(url, listener); + // 移除出监听器 + listeners.remove(listener); + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + // 重试执行通知监听器 + if (!failedNotified.isEmpty()) { + Map>> failed = new HashMap>>(failedNotified); + for (Map.Entry>> entry : new HashMap>>(failed).entrySet()) { + if (entry.getValue() == null || entry.getValue().size() == 0) { + failed.remove(entry.getKey()); + } + } + if (failed.size() > 0) { + if (logger.isInfoEnabled()) { + logger.info("Retry notify " + failed); + } + try { + for (Map> values : failed.values()) { + for (Map.Entry> entry : values.entrySet()) { + try { + NotifyListener listener = entry.getKey(); + List urls = entry.getValue(); + // 通知监听器 + listener.notify(urls); + // 移除出监听器 + values.remove(listener); + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry + logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t); + } + } + } + } + + @Override + public void destroy() { + // 忽略,若已经销毁 + if (!canDestroy()) { + return; + } + // 调用父方法,取消注册和订阅 + super.destroy(); + // 销毁重试任务 + try { + retryFuture.cancel(true); + } catch (Throwable t) { + logger.warn(t.getMessage(), t); + } + } + + // TODO: 2017/8/30 to abstract this method + protected boolean canDestroy(){ + return destroyed.compareAndSet(false, true); + } + + // ==== Template method ==== + + protected abstract void doRegister(URL url); + + protected abstract void doUnregister(URL url); + + protected abstract void doSubscribe(URL url, NotifyListener listener); + + protected abstract void doUnsubscribe(URL url, NotifyListener listener); +} +``` + +### RegistryFactory 和 AbstractRegistryFactory +RegistryFactory接口 是 Registry的工厂接口,用来返回 Registry实例。该接口是一个可扩展接口,可以看到该接口上有个@SPI注解,并且默认值为dubbo,也就是默认扩展的是DubboRegistryFactory。AbstractRegistryFactory 则是实现了 RegistryFactory接口 的抽象类。其源码如下。 +```java +/** + * 注册中心工厂 + */ +@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); +} + +/** + * 注册中心抽象类 + */ +public abstract class AbstractRegistryFactory implements RegistryFactory { + + // Log output + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRegistryFactory.class); + + // The lock for the acquisition process of the registry + private static final ReentrantLock LOCK = new ReentrantLock(); + + /** + * Registry 集合 + */ + private static final Map REGISTRIES = new ConcurrentHashMap(); + + /** + * Get all registries + */ + public static Collection getRegistries() { + return Collections.unmodifiableCollection(REGISTRIES.values()); + } + + /** + * 销毁所有 Registry + */ + // TODO: 2017/8/30 to move somewhere else better + 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(); + } + } + + /** + * 获得注册中心 Registry 对象 + * + * @param url 注册中心地址,不允许为空 + * @return Registry 对象 + */ + @Override + public Registry getRegistry(URL url) { + // 修改 URL + url = url.setPath(RegistryService.class.getName()) // + `path` + .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName()) // + `parameters.interface` + .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY); // - `export` + // 计算 key + String key = url.toServiceString(); + // 获得锁 + // Lock the registry access process to ensure a single instance of the registry + LOCK.lock(); + try { + // 从缓存中获得 Registry 对象 + Registry registry = REGISTRIES.get(key); + if (registry != null) { + return registry; + } + // 缓存不存在,进行创建 Registry 对象 + registry = createRegistry(url); + if (registry == null) { + throw new IllegalStateException("Can not create registry " + url); + } + // 添加到缓存 + REGISTRIES.put(key, registry); + return registry; + } finally { + // 释放锁 + // Release the lock + LOCK.unlock(); + } + } + + /** + * 创建 Registry 对象 + * + * @param url 注册中心地址 + * @return Registry 对象 + */ + protected abstract Registry createRegistry(URL url); +} +``` +### NotifyListener 和 RegistryDirectory +最后我们来看一下 dubbo-registry-api 模块下的另一个比较重要的组件,NotifyListener接口 和 RegistryDirectory抽象类。NotifyListener接口 只有一个notify方法,通知监听器。当收到服务变更通知时触发。RegistryDirectory是注册中心服务,维护着所有可用的远程Invoker或者本地的Invoker,它的Invoker集合是从注册中心获取的,另外,它实现了NotifyListener接口。比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有 服务提供方,在订阅 及 服务提供方数据有变动时,回调消费方的NotifyListener服务的notify方法,回调接口传入所有服务提供方的url地址然后将urls转化为invokers,也就是refer应用远程服务。源码如下。 +```java +/** + * 通知监听器 + */ +public interface NotifyListener { + + /** + * 当收到服务变更通知时触发。 + *

+ * 通知需处理契约:
+ * 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。
+ * 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。
+ * 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。
+ * 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。
+ * 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。
+ * + * @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。 + */ + void notify(List urls); +} + + +/** + * 基于注册中心的 Directory 实现类 + */ +public class RegistryDirectory extends AbstractDirectory implements NotifyListener { + + private static final Logger logger = LoggerFactory.getLogger(RegistryDirectory.class); + + // ========== Dubbo SPI Adaptive 对象 BEGIN ========== + + /** + * Cluster$Adaptive 对象 + */ + private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); + /** + * RouterFactory$Adaptive 对象 + */ + private static final RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension(); + /** + * ConfiguratorFactory$Adaptive 对象 + */ + private static final ConfiguratorFactory configuratorFactory = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getAdaptiveExtension(); + + // ========== 服务消费者相关 BEGIN ========== + + /** + * 服务类型,例如:com.alibaba.dubbo.demo.DemoService + */ + private final Class serviceType; // Initialization at construction time, assertion not null + /** + * Consumer URL 的配置项 Map + */ + private final Map queryMap; // Initialization at construction time, assertion not null + /** + * 服务方法数组 + */ + private final String[] serviceMethods; + /** + * 是否引用多分组 + * + * 服务分组:https://dubbo.gitbooks.io/dubbo-user-book/demos/service-group.html + */ + private final boolean multiGroup; + + // ========== 注册中心相关 BEGIN ========== + + /** + * 注册中心的 Protocol 对象 + */ + private Protocol protocol; // Initialization at the time of injection, the assertion is not null + /** + * 注册中心 + */ + private Registry registry; // Initialization at the time of injection, the assertion is not null + /** + * 注册中心的服务类,目前是 com.alibaba.dubbo.registry.RegistryService + * + * 通过 {@link #url} 的 {@link URL#getServiceKey()} 获得 + */ + private final String serviceKey; // Initialization at construction time, assertion not null + /** + * 是否禁止访问。 + * + * 有两种情况会导致: + * + * 1. 没有服务提供者 + * 2. 服务提供者被禁用 + */ + private volatile boolean forbidden = false; + + // ========== 配置规则相关 BEGIN ========== + + /** + * 原始的目录 URL + * + * 例如:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&callbacks=1000&check=false&client=netty4&cluster=failback&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello,callbackParam,save,update,say03,delete,say04,demo,say01,bye,say02,saves&payload=1000&pid=63400&qos.port=33333®ister.ip=192.168.16.23&sayHello.async=true&side=consumer&timeout=10000×tamp=1527056491064 + */ + private final URL directoryUrl; // Initialization at construction time, assertion not null, and always assign non null value + /** + * 覆写的目录 URL ,结合配置规则 + */ + private volatile URL overrideDirectoryUrl; // Initialization at construction time, assertion not null, and always assign non null value + /** + * 配置规则数组 + * + * override rules + * Priority: override>-D>consumer>provider + * Rule one: for a certain provider + * Rule two: for all providers <* ,timeout=5000> + */ + private volatile List configurators; // The initial value is null and the midway may be assigned to null, please use the local variable reference + + // ========== 服务提供者相关 BEGIN ========== + + /** + * [url]与[服务提供者 Invoker 集合]的映射缓存 + */ + // Map cache service url to invoker mapping. + private volatile Map> urlInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference + /** + * [方法名]与[服务提供者 Invoker 集合]的映射缓存 + */ + // Map cache service method to invokers mapping. + private volatile Map>> methodInvokerMap; // The initial value is null and the midway may be assigned to null, please use the local variable reference + /** + * [服务提供者 Invoker 集合]缓存 + */ + // Set cache invokeUrls to invokers mapping. + private volatile Set cachedInvokerUrls; // The initial value is null and the midway may be assigned to null, please use the local variable reference + + public RegistryDirectory(Class serviceType, URL url) { + super(url); + if (serviceType == null) { + throw new IllegalArgumentException("service type is null."); + } + if (url.getServiceKey() == null || url.getServiceKey().length() == 0) { + throw new IllegalArgumentException("registry serviceKey is null."); + } + this.serviceType = serviceType; + this.serviceKey = url.getServiceKey(); + // 获得 queryMap + this.queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); + // 获得 overrideDirectoryUrl 和 directoryUrl + this.overrideDirectoryUrl = this.directoryUrl = url.setPath(url.getServiceInterface()).clearParameters().addParameters(queryMap).removeParameter(Constants.MONITOR_KEY); + // 初始化 multiGroup + String group = directoryUrl.getParameter(Constants.GROUP_KEY, ""); + this.multiGroup = group != null && ("*".equals(group) || group.contains(",")); + // 初始化 serviceMethods + String methods = queryMap.get(Constants.METHODS_KEY); + this.serviceMethods = methods == null ? null : Constants.COMMA_SPLIT_PATTERN.split(methods); + } + + /** + * 将overrideURL 转换为 map,供重新 refer 时使用. + * 每次下发全部规则,全部重新组装计算 + * + * @param urls 契约: + *
1.override://0.0.0.0/...(或override://ip:port...?anyhost=true)¶1=value1...表示全局规则(对所有的提供者全部生效) + *
2.override://ip:port...?anyhost=false 特例规则(只针对某个提供者生效) + *
3.不支持override://规则... 需要注册中心自行计算. + *
4.不带参数的override://0.0.0.0/ 表示清除override + * + * @return Configurator 集合 + */ + public static List toConfigurators(List urls) { + // 忽略,若配置规则 URL 集合为空 + if (urls == null || urls.isEmpty()) { + return Collections.emptyList(); + } + + // 创建 Configurator 集合 + List configurators = new ArrayList(urls.size()); + for (URL url : urls) { + // 若协议为 `empty://` ,意味着清空所有配置规则,因此返回空 Configurator 集合 + if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { + configurators.clear(); + break; + } + // 对应第 4 条契约,不带参数的 override://0.0.0.0/ 表示清除 override + Map override = new HashMap(url.getParameters()); + // The anyhost parameter of override may be added automatically, it can't change the judgement of changing url + // override 上的 anyhost 可能是自动添加的,不能影响改变url判断 + override.remove(Constants.ANYHOST_KEY); + if (override.size() == 0) { + configurators.clear(); + continue; + } + // 获得 Configurator 对象,并添加到 `configurators` 中 + configurators.add(configuratorFactory.getConfigurator(url)); + } + // 排序 + Collections.sort(configurators); + return configurators; + } + + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } + + public void setRegistry(Registry registry) { + this.registry = registry; + } + + /** + * 发起订阅 + * + * @param url 消费者 URL + */ + public void subscribe(URL url) { + // 设置消费者 URL + setConsumerUrl(url); + // 向注册中心,发起订阅 + registry.subscribe(url, this); + } + + @Override + public void destroy() { + if (isDestroyed()) { + return; + } + // 取消订阅 + // unsubscribe. + try { + if (getConsumerUrl() != null && registry != null && registry.isAvailable()) { + registry.unsubscribe(getConsumerUrl(), this); + } + } catch (Throwable t) { + logger.warn("unexpeced error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t); + } + // 标记已经销毁 + super.destroy(); // must be executed after unsubscribing + // 销毁所有 Invoker + try { + destroyAllInvokers(); + } catch (Throwable t) { + logger.warn("Failed to destroy service " + serviceKey, t); + } + } + + @Override + public synchronized void notify(List urls) { + // 根据 URL 的分类或协议,分组成三个集合 。 + List invokerUrls = new ArrayList(); // 服务提供者 URL 集合 + List routerUrls = new ArrayList(); + List configuratorUrls = new ArrayList(); + for (URL url : urls) { + String protocol = url.getProtocol(); + String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); + if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) { + routerUrls.add(url); + } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) { + configuratorUrls.add(url); + } else if (Constants.PROVIDERS_CATEGORY.equals(category)) { + invokerUrls.add(url); + } else { + logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()); + } + } + // 处理配置规则 URL 集合 + // configurators + if (!configuratorUrls.isEmpty()) { + this.configurators = toConfigurators(configuratorUrls); + } + // 处理路由规则 URL 集合 + // routers + if (!routerUrls.isEmpty()) { + List routers = toRouters(routerUrls); + if (routers != null) { // null - do nothing + setRouters(routers); + } + } + // 合并配置规则,到 `directoryUrl` 中,形成 `overrideDirectoryUrl` 变量。 + List localConfigurators = this.configurators; // local reference + // merge override parameters + this.overrideDirectoryUrl = directoryUrl; + if (localConfigurators != null && !localConfigurators.isEmpty()) { + for (Configurator configurator : localConfigurators) { + this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); + } + } + // 处理服务提供者 URL 集合 + // providers + refreshInvoker(invokerUrls); + } + + /** + * 根据invokerURL列表转换为invoker列表。转换规则如下: + * + * 1.如果url已经被转换为invoker,则不在重新引用,直接从缓存中获取,注意如果url中任何一个参数变更也会重新引用 + * 2.如果传入的invoker列表不为空,则表示最新的invoker列表 + * 3.如果传入的invokerUrl列表是空,则表示只是下发的override规则或route规则,需要重新交叉对比,决定是否需要重新引用。 + * + * @param invokerUrls 传入的参数不能为null + */ + // TODO: 2017/8/31 FIXME The thread pool should be used to refresh the address, otherwise the task may be accumulated. + private void refreshInvoker(List invokerUrls) { + if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null + && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { + // 设置禁止访问 + this.forbidden = true; // Forbid to access + // methodInvokerMap 置空 + this.methodInvokerMap = null; // Set the method invoker map to null + // 销毁所有 Invoker 集合 + destroyAllInvokers(); // Close all invokers + } else { + // 设置允许访问 + this.forbidden = false; // Allow to access + // 引用老的 urlInvokerMap + Map> oldUrlInvokerMap = this.urlInvokerMap; // local reference + // 传入的 invokerUrls 为空,说明是路由规则或配置规则发生改变,此时 invokerUrls 是空的,直接使用 cachedInvokerUrls 。 + if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) { + invokerUrls.addAll(this.cachedInvokerUrls); + // 传入的 invokerUrls 非空,更新 cachedInvokerUrls 。 + } else { + this.cachedInvokerUrls = new HashSet(); + this.cachedInvokerUrls.addAll(invokerUrls); //Cached invoker urls, convenient for comparison //缓存invokerUrls列表,便于交叉对比 + } + // 忽略,若无 invokerUrls + if (invokerUrls.isEmpty()) { + return; + } + // 将传入的 invokerUrls ,转成新的 urlInvokerMap + Map> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map + // 转换出新的 methodInvokerMap + Map>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map + // state change + // If the calculation is wrong, it is not processed. 如果计算错误,则不进行处理. + if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { + logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString())); + return; + } + // 若服务引用多 group ,则按照 method + group 聚合 Invoker 集合 + this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; + this.urlInvokerMap = newUrlInvokerMap; + // 销毁不再使用的 Invoker 集合 + try { + destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker + } catch (Exception e) { + logger.warn("destroyUnusedInvokers error. ", e); + } + } + } + + /** + * 若服务引用多 group ,则按照 method + group 聚合 Invoker 集合 + */ + private Map>> toMergeMethodInvokerMap(Map>> methodMap) { + Map>> result = new HashMap>>(); + // 循环方法,按照 method + group 聚合 Invoker 集合 + for (Map.Entry>> entry : methodMap.entrySet()) { + String method = entry.getKey(); + List> invokers = entry.getValue(); + // 按照 Group 聚合 Invoker 集合的结果。其中,KEY:group VALUE:Invoker 集合。 + Map>> groupMap = new HashMap>>(); + // 循环 Invoker 集合,按照 group 聚合 Invoker 集合 + for (Invoker invoker : invokers) { + String group = invoker.getUrl().getParameter(Constants.GROUP_KEY, ""); + List> groupInvokers = groupMap.get(group); + if (groupInvokers == null) { + groupInvokers = new ArrayList>(); + groupMap.put(group, groupInvokers); + } + groupInvokers.add(invoker); + } + // 大小为 1,使用第一个 + if (groupMap.size() == 1) { + result.put(method, groupMap.values().iterator().next()); + // 大于 1,将每个 Group 的 Invoker 集合,创建成 Cluster Invoker 对象。 + } else if (groupMap.size() > 1) { + List> groupInvokers = new ArrayList>(); + for (List> groupList : groupMap.values()) { + groupInvokers.add(cluster.join(new StaticDirectory(groupList))); + } + result.put(method, groupInvokers); + // 大小为 0 ,使用原有值 + } else { + result.put(method, invokers); + } + } + return result; + } + + /** + * @param urls + * @return null : no routers ,do nothing + * else :routers list + */ + private List toRouters(List urls) { + List routers = new ArrayList(); + if (urls == null || urls.isEmpty()) { + return routers; + } + for (URL url : urls) { + // 忽略,若是 "empty://" 。一般情况下,所有路由规则被删除时,有且仅有一条协议为 "empty://" 的路由规则 URL + if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) { + continue; + } + // 获得 "router" + String routerType = url.getParameter(Constants.ROUTER_KEY); + if (routerType != null && routerType.length() > 0) { + url = url.setProtocol(routerType); + } + try { + // 创建 Router 对象 + Router router = routerFactory.getRouter(url); + // 添加到返回结果 + if (!routers.contains(router)) { + routers.add(router); + } + } catch (Throwable t) { + logger.error("convert router url to router error, url: " + url, t); + } + } + return routers; + } + + /** + * 将服务提供者 URL 集合,转成 Invoker 集合。若该服务提供者 URL 已经转换,则直接复用,不重新引用。 + * + * @param urls URL 集合 + * @return invokers + */ + private Map> toInvokers(List urls) { + // 新的 `newUrlInvokerMap` + Map> newUrlInvokerMap = new HashMap>(); + // 若为空,直接返回 + if (urls == null || urls.isEmpty()) { + return newUrlInvokerMap; + } + // 已初始化的服务器提供 URL 集合 + Set keys = new HashSet(); + // 获得引用服务的协议 + String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY); + // 循环服务提供者 URL 集合,转成 Invoker 集合 + for (URL providerUrl : urls) { + // If protocol is configured at the reference side, only the matching protocol is selected + // 如果 reference 端配置了 protocol ,则只选择匹配的 protocol + if (queryProtocols != null && queryProtocols.length() > 0) { + boolean accept = false; + String[] acceptProtocols = queryProtocols.split(","); // 可配置多个协议 + for (String acceptProtocol : acceptProtocols) { + if (providerUrl.getProtocol().equals(acceptProtocol)) { + accept = true; + break; + } + } + if (!accept) { + continue; + } + } + // 忽略,若为 `empty://` 协议 + if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) { + continue; + } + // 忽略,若应用程序不支持该协议 + if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) { + logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost() + + ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions())); + continue; + } + // 合并 URL 参数 + URL url = mergeUrl(providerUrl); + // 忽略,若已经初始化 + String key = url.toFullString(); // The parameter urls are sorted + if (keys.contains(key)) { // Repeated url + continue; + } + // 添加到 `keys` 中 + keys.add(key); + // Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again + // 如果服务端 URL 发生变化,则重新 refer 引用 + Map> localUrlInvokerMap = this.urlInvokerMap; // local reference + Invoker invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key); + if (invoker == null) { // Not in the cache, refer again 未在缓存中,重新引用 + try { + // 判断是否开启 + boolean enabled; + if (url.hasParameter(Constants.DISABLED_KEY)) { + enabled = !url.getParameter(Constants.DISABLED_KEY, false); + } else { + enabled = url.getParameter(Constants.ENABLED_KEY, true); + } + // 若开启,创建 Invoker 对象 + if (enabled) { + // 注意,引用服务 + invoker = new InvokerDelegate(protocol.refer(serviceType, url), url, providerUrl); + } + } catch (Throwable t) { + logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t); + } + // 添加到 newUrlInvokerMap 中 + if (invoker != null) { // Put new invoker in cache + newUrlInvokerMap.put(key, invoker); + } + } else { // 在缓存中,直接使用缓存的 Invoker 对象,添加到 newUrlInvokerMap 中 + newUrlInvokerMap.put(key, invoker); + } + } + // 清空 keys + keys.clear(); + return newUrlInvokerMap; + } + + /** + * Merge url parameters. the order is: override > -D >Consumer > Provider + * + * 合并 URL 参数,优先级为配置规则 > 服务消费者配置 > 服务提供者配置 + * + * @param providerUrl 服务提供者 URL + * @return 合并后的 URL + */ + private URL mergeUrl(URL providerUrl) { + // 合并消费端参数 + providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); // Merge the consumer side parameters + + // 合并配置规则 + List localConfigurators = this.configurators; // local reference + if (localConfigurators != null && !localConfigurators.isEmpty()) { + for (Configurator configurator : localConfigurators) { + providerUrl = configurator.configure(providerUrl); + } + } + + // 不检查连接是否成功,总是创建 Invoker !因为,启动检查,只有启动阶段需要。此时在检查,已经没必要了。 + providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // Do not check whether the connection is successful or not, always create Invoker! + + // The combination of directoryUrl and override is at the end of notify, which can't be handled here + // 仅合并提供者参数,因为 directoryUrl 与 override 合并是在 notify 的最后,这里不能够处理 + this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters + + // 【忽略】因为是对 1.0 版本的兼容 + if ((providerUrl.getPath() == null || providerUrl.getPath().length() == 0) + && "dubbo".equals(providerUrl.getProtocol())) { // Compatible version 1.0 + //fix by tony.chenl DUBBO-44 + String path = directoryUrl.getParameter(Constants.INTERFACE_KEY); + if (path != null) { + int i = path.indexOf('/'); + if (i >= 0) { + path = path.substring(i + 1); + } + i = path.lastIndexOf(':'); + if (i >= 0) { + path = path.substring(0, i); + } + providerUrl = providerUrl.setPath(path); + } + } + + // 返回服务提供者 URL + return providerUrl; + } + + private List> route(List> invokers, String method) { + // 创建 Invocation 对象 + Invocation invocation = new RpcInvocation(method, new Class[0], new Object[0]); + // 获得 Router 数组 + List routers = getRouters(); + // 根据路由规则,筛选 Invoker 集合 + if (routers != null) { + for (Router router : routers) { + if (router.getUrl() != null) { + invokers = router.route(invokers, getConsumerUrl(), invocation); + } + } + } + return invokers; + } + + /** + * 将invokers列表转成与方法的映射关系 + * + * @param invokersMap Invoker列表 + * @return Invoker与方法的映射关系 + */ + private Map>> toMethodInvokers(Map> invokersMap) { + // 创建新的 `methodInvokerMap` + Map>> newMethodInvokerMap = new HashMap>>(); + // 创建 Invoker 集合 + List> invokersList = new ArrayList>(); + // According to the methods classification declared by the provider URL, the methods is compatible with the registry to execute the filtered methods + // 按服务提供者 URL 所声明的 methods 分类,兼容注册中心执行路由过滤掉的 methods + if (invokersMap != null && invokersMap.size() > 0) { + // 循环每个服务提供者 Invoker + for (Invoker invoker : invokersMap.values()) { + String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY); // methods + if (parameter != null && parameter.length() > 0) { + String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter); + if (methods != null && methods.length > 0) { + // 循环每个方法,按照方法名为维度,聚合到 `methodInvokerMap` 中 + for (String method : methods) { + if (method != null && method.length() > 0 && !Constants.ANY_VALUE.equals(method)) { // 当服务提供者的方法为 "*" ,代表泛化调用 + List> methodInvokers = newMethodInvokerMap.get(method); + if (methodInvokers == null) { + methodInvokers = new ArrayList>(); + newMethodInvokerMap.put(method, methodInvokers); + } + methodInvokers.add(invoker); + } + } + } + } + // 添加到 `invokersList` 中 + invokersList.add(invoker); + } + } + // 路由全 `invokersList` ,匹配合适的 Invoker 集合 + List> newInvokersList = route(invokersList, null); + // 添加 `newInvokersList` 到 `newMethodInvokerMap` 中,表示该服务提供者的全量 Invoker 集合 + newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList); + // 循环,基于每个方法路由,匹配合适的 Invoker 集合 + if (serviceMethods != null && serviceMethods.length > 0) { + for (String method : serviceMethods) { + List> methodInvokers = newMethodInvokerMap.get(method); + if (methodInvokers == null || methodInvokers.isEmpty()) { + methodInvokers = newInvokersList; + } + newMethodInvokerMap.put(method, route(methodInvokers, method)); + } + } + // 循环排序每个方法的 Invoker 集合,并设置为不可变 + // sort and unmodifiable + for (String method : new HashSet(newMethodInvokerMap.keySet())) { + List> methodInvokers = newMethodInvokerMap.get(method); + Collections.sort(methodInvokers, InvokerComparator.getComparator()); + newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers)); + } + return Collections.unmodifiableMap(newMethodInvokerMap); + } + + /** + * Close all invokers + */ + private void destroyAllInvokers() { + Map> localUrlInvokerMap = this.urlInvokerMap; // local reference 本地引用,避免并发问题 + if (localUrlInvokerMap != null) { + // 循环 urlInvokerMap ,销毁所有服务提供者 Invoker + for (Invoker invoker : new ArrayList>(localUrlInvokerMap.values())) { + try { + invoker.destroy(); + } catch (Throwable t) { + logger.warn("Failed to destroy service " + serviceKey + " to provider " + invoker.getUrl(), t); + } + } + // urlInvokerMap 清空 + localUrlInvokerMap.clear(); + } + // methodInvokerMap 置空 + methodInvokerMap = null; + } + + /** + * Check whether the invoker in the cache needs to be destroyed + * If set attribute of url: refer.autodestroy=false, the invokers will only increase without decreasing,there may be a refer leak + * + * @param oldUrlInvokerMap + * @param newUrlInvokerMap + */ + private void destroyUnusedInvokers(Map> oldUrlInvokerMap, Map> newUrlInvokerMap) { + // 防御性编程,目前不存在这个情况 + if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { + // 销毁所有服务提供者 Invoker + destroyAllInvokers(); + return; + } + // check deleted invoker + // 对比新老集合,计算需要销毁的 Invoker 集合 + List deleted = null; + if (oldUrlInvokerMap != null) { + Collection> newInvokers = newUrlInvokerMap.values(); + for (Map.Entry> entry : oldUrlInvokerMap.entrySet()) { + // 若不存在,添加到 `deleted` 中 + if (!newInvokers.contains(entry.getValue())) { + if (deleted == null) { + deleted = new ArrayList(); + } + deleted.add(entry.getKey()); + } + } + } + + // 若有需要销毁的 Invoker ,则进行销毁 + if (deleted != null) { + for (String url : deleted) { + if (url != null) { + // 移除出 `urlInvokerMap` + Invoker invoker = oldUrlInvokerMap.remove(url); + if (invoker != null) { + try { + // 销毁 Invoker + invoker.destroy(); + if (logger.isDebugEnabled()) { + logger.debug("destroy invoker[" + invoker.getUrl() + "] success. "); + } + } catch (Exception e) { + logger.warn("destroy invoker[" + invoker.getUrl() + "] failed. " + e.getMessage(), e); + } + } + } + } + } + } + + @Override + public List> doList(Invocation invocation) { + if (forbidden) { + // 1. No service provider 2. Service providers are disabled + throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, + "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() + + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); + } + List> invokers = null; + Map>> localMethodInvokerMap = this.methodInvokerMap; // local reference + // 获得 Invoker 集合 + if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) { + // 获得方法名、方法参数 + String methodName = RpcUtils.getMethodName(invocation); + Object[] args = RpcUtils.getArguments(invocation); + // 【第一】可根据第一个参数枚举路由 + if (args != null && args.length > 0 && args[0] != null + && (args[0] instanceof String || args[0].getClass().isEnum())) { +// invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter + invokers = localMethodInvokerMap.get(methodName + args[0]); // The routing can be enumerated according to the first parameter + } + // 【第二】根据方法名获得 Invoker 集合 + if (invokers == null) { + invokers = localMethodInvokerMap.get(methodName); + } + // 【第三】使用全量 Invoker 集合。例如,`#$echo(name)` ,回声方法 + if (invokers == null) { + invokers = localMethodInvokerMap.get(Constants.ANY_VALUE); + } + // 【第四】使用 `methodInvokerMap` 第一个 Invoker 集合。防御性编程。 + if (invokers == null) { + Iterator>> iterator = localMethodInvokerMap.values().iterator(); + if (iterator.hasNext()) { + invokers = iterator.next(); + } + } + } + return invokers == null ? new ArrayList>(0) : invokers; + } + + @Override + public Class getInterface() { + return serviceType; + } + + @Override + public URL getUrl() { + return this.overrideDirectoryUrl; + } + + @Override + public boolean isAvailable() { + // 若已销毁,返回不可用 + if (isDestroyed()) { + return false; + } + // 任意一个 Invoker 可用,则返回可用 + Map> localUrlInvokerMap = urlInvokerMap; + if (localUrlInvokerMap != null && localUrlInvokerMap.size() > 0) { + for (Invoker invoker : new ArrayList>(localUrlInvokerMap.values())) { + if (invoker.isAvailable()) { + return true; + } + } + } + return false; + } + + /** + * Haomin: added for test purpose + */ + public Map> getUrlInvokerMap() { + return urlInvokerMap; + } + + /** + * Haomin: added for test purpose + */ + public Map>> getMethodInvokerMap() { + return methodInvokerMap; + } + + /** + * Invoker 排序器,根据 URL 升序 + */ + private static class InvokerComparator implements Comparator> { + + /** + * 单例 + */ + private static final InvokerComparator comparator = new InvokerComparator(); + + private InvokerComparator() { + } + + public static InvokerComparator getComparator() { + return comparator; + } + + @Override + public int compare(Invoker o1, Invoker o2) { + return o1.getUrl().toString().compareTo(o2.getUrl().toString()); + } + + } + + /** + * + * Invoker 代理类,主要用于存储注册中心下发的 url 地址,用于重新重新 refer 时能够根据 providerURL queryMap overrideMap 重新组装 + * + * @param + */ + private static class InvokerDelegate extends InvokerWrapper { + + /** + * 服务提供者 URL + * + * 未经过配置合并 + */ + private URL providerUrl; + + public InvokerDelegate(Invoker invoker, URL url, URL providerUrl) { + super(invoker, url); + this.providerUrl = providerUrl; + } + + public URL getProviderUrl() { + return providerUrl; + } + } +} +``` \ No newline at end of file diff --git a/images/Dubbo/dubbo-registry-zookeeper模块工程结构图.png b/images/Dubbo/dubbo-registry-zookeeper模块工程结构图.png new file mode 100644 index 0000000000000000000000000000000000000000..4eb49c8468e0578c0370b28b75862f0f417f4d0b GIT binary patch literal 15219 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 Ml#=9kaYO(A2V$0o5C8xG literal 0 HcmV?d00001 diff --git a/images/Dubbo/dubbo注册中心在zookeeper中的结构.png b/images/Dubbo/dubbo注册中心在zookeeper中的结构.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe0688da936337bb4ff81d073b83e37965988f5 GIT binary patch literal 98083 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 z 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 yZC8Y<-n++EngyS~2G;c)6@e;Qs(1XBt!h literal 0 HcmV?d00001