欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
1. 注册中心api
1.1 注册中心module预览
在v3版本,注册中心模块是以子项目的形式存在的,我们先来看看dubbo-registry 子项目的目录:
其中 dubbo-registry-api模块主要是定义了注册中心的接口与注册中心Factory,再就是RegistryDirectory与RegistryProtocol类还有一些周边support。
1.2 核心接口介绍
1.2.1 RegistryService
public interface RegistryService {
void register(URL url);
void unregister(URL url);
void subscribe(URL url, NotifyListener listener);
void unsubscribe(URL url, NotifyListener listener);
List<URL> lookup(URL url);
}
我们可以看到RegistryService 注册中心服务接口主要是抽象了注册,下线,订阅,取消订阅方法
,再就是还有个lookup方法,这个方法会根据提供的url获得所有注册的url
1.2.2 RegistryFactory
Registry工厂接口,可以基于dubbo spi 扩展技术动态查找其子类,默认实现是DubboRegistryFactory。
@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
有一个获取Registry注册中心的抽象方法,这个方法可以根据url的protocol属性值来自适应RegistryFactory的实现类。
1.2.3 Registry
这个Registry就是注册中心的接口类,我们可以看下它的继承关系
public interface Registry extends Node, RegistryService {
default int getDelay() {
return getUrl().getParameter(REGISTRY_DELAY_NOTIFICATION_KEY, 5000);
}
default void reExportRegister(URL url) {
register(url);
}
default void reExportUnregister(URL url) {
unregister(url);
}
}
继承Node ,实现RegistryService ,这RegistryService我们上面小节说过,它定义了订阅,取消订阅,注册,下线等功能,而这个Node其实节点的意思,我们可以从它的定义看出来还是比较简单的,获取url,是否可用,销毁这个三个功能。
public interface Node {
/**
* 获取url
* @return url.
*/
URL getUrl();
/**
* 是否可用
* @return available.
*/
boolean isAvailable();
/**
* 销毁
*/
void destroy();
}
1.2.4 NotifyListener
public interface NotifyListener {
void notify(List<URL> urls);
default void addServiceListener(ServiceInstancesChangedListener instanceListener) {
}
default URL getConsumerUrl() {
return null;
}
}
NotifyListener 是一个通知接口,抽象了notify通知方法,当我们向注册中心订阅某个url的时候,需要带上NotifyListener 的实现类,一旦订阅的url发生了变化就会调用NotifyListener的notify进行通知。之前我们遇到过的RegistryDirectory类有个subscribe方法,同时他自己也实现了NotifyListener接口,重写notify方法,向注册中心订阅,然后那个NotifyListener 就是它自己。
1.3 在服务暴露与服务引用是怎样使用
1.3.1 服务暴露时使用
之前我们在将服务远程暴露的时候,走的export方法,然后在RegistryProtocol的Protocol实现类中,服务提供者向注册中心进行注册
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
...
// url to registry todo 获取注册中心实例
final Registry registry = getRegistry(registryUrl);
// decide if we need to delay publish
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 注册到注册中心
register(registry, registeredProviderUrl);
}
// register stated url on provider model
registerStatedUrl(registryUrl, registeredProviderUrl, register);
}
再看一下register方法
private void register(Registry registry, URL registeredProviderUrl) {
registry.register(registeredProviderUrl);
}
register是从getRegistry(registryUrl)获取的:
protected Registry getRegistry(final URL registryUrl) {
return registryFactory.getRegistry(registryUrl);
}
RegistryFactory注册中心工厂获取对应的Registry注册中心,然后调用register进行调用。
1.3.2 服务引用时使用
我们服务引用在Protocol层是走的refer方法,看下RegistryProtocol的refer方法是怎样用的
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = getRegistryUrl(url);
// 获取registry对象
Registry registry = registryFactory.getRegistry(url);
...
// 获取cluster属性,即集群容错策略,默认failover策略
Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY));
return doRefer(cluster, registry, type, url, qs); // 继续订阅
}
先是使用注册中心工厂获取对应的注册中心Registry对象,接着调用了doRefer方法,然后这块调用链路很长,可以参考服务订阅(本地和远程引用)源码分析,直接来到: org.apache.dubbo.registry.integration.RegistryProtocol#doCreateInvoker
protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
URL urlToRegistry = new ServiceConfigURL(
parameters.get(PROTOCOL_KEY) == null ? DUBBO : parameters.get(PROTOCOL_KEY),
parameters.remove(REGISTER_IP_KEY), 0, getPath(parameters, type), parameters);
if (directory.isShouldRegister()) {
directory.setRegisteredConsumerUrl(urlToRegistry);
// 将当前consumer注册到注册中心
registry.register(directory.getRegisteredConsumerUrl());
}
// 将所有RouterFactory激活扩展类创建的router添加到directory
directory.buildRouterChain(urlToRegistry);
// 服务订阅
directory.subscribe(toSubscribeUrl(urlToRegistry));
// 将多个invoker伪装为一个具有复合功能的invoker
return (ClusterInvoker<T>) cluster.join(directory);
}
我们可以看到,先向注册中心注册,后又调用subscribe订阅了某个url。
2. 核心抽象类
2.1 AbstractRegistryFactory
AbstractRegistryFactory 是注册中心工厂RegistryFactory接口的抽象实现,首先看下它的class定义:
public abstract class AbstractRegistryFactory implements RegistryFactory {...}
这个没啥好说的实现RegistryFactory接口,接下来看下静态成员与成员变量
// The lock for the acquisition process of the registry
// 在创建 注册中心对象或者 销毁注册中心对象的时候会用到 锁
protected static final ReentrantLock LOCK = new ReentrantLock();
// Registry Collection Map<RegistryAddress, Registry>
// 缓存 注册中心对象使用 key就是注册中心url的一个toServiceString, value是注册中心对象
protected static final Map<String, Registry> REGISTRIES = new HashMap<>();
private static final AtomicBoolean destroyed = new AtomicBoolean(false);
接下来看下getRegistry方法实现:
@Override
public Registry getRegistry(URL url) {
Registry defaultNopRegistry = getDefaultNopRegistryIfDestroyed();
if (null != defaultNopRegistry) {
return defaultNopRegistry;
}
url = URLBuilder.from(url)
.setPath(RegistryService.class.getName())
.addParameter(INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(EXPORT_KEY, REFER_KEY, TIMESTAMP_KEY)
.build();
String key = createRegistryCacheKey(url);
// Lock the registry access process to ensure a single instance of the registry
LOCK.lock();
try {
// double check
// fix https://github.com/apache/dubbo/issues/7265.
defaultNopRegistry = getDefaultNopRegistryIfDestroyed();
if (null != defaultNopRegistry) {
return defaultNopRegistry;
}
// 从缓存获取当前服务注册中心URL对应的实例。
// key为zookeeper://zookeeperOS:2181/org.apache.dubbo.registry.RegistryService
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
//create registry by spi/ioc
// 创建注册中心实例 创建方法由子类实现 模板方法设计模式
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
//将 registry添加到缓存中去
REGISTRIES.put(key, registry);
return registry;
} finally {
// Release the lock
LOCK.unlock();
}
}
在getRegistry方法中,前几行操作url的代码主要是为了生成一个key,类似zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService
,接着获取锁,先从缓存REGISTRIES中获取该key 的value(注册中心对象),如果能获取到就返回,如果获取不到就调用抽象方法createRegistry来创建注册中心对象,这个createRegistry 方法是个抽象方法,需要子类来具体实现,先来看下createRegistry方法定义:
// 由子类实现的 创建方法, 模板方法设计模式
protected abstract Registry createRegistry(URL url);
创建完的注册中心还是空的话抛出异常,不是空就缓存到REGISTRIES map中,最后就是释放锁了。 接下来再看下AbstractRegistryFactory其他方法:
/**
* Get all registries
* 获取所有的registry
* @return all registries
*/
public static Collection<Registry> getRegistries() {
return Collections.unmodifiableCollection(REGISTRIES.values());
}
这个getRegistries方法没什么好说的,就是获取缓存中的所有注册中心
public static void destroyAll() {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Close all registries " + getRegistries());
}
// 循环注销各个registry , 最后再清空registry缓存
LOCK.lock();
try {
for (Registry registry : getRegistries()) {
try {
registry.destroy();
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
}
REGISTRIES.clear();
} finally {
// Release the lock
LOCK.unlock();
}
}
再来看下destroyAll这个静态方法,先是获取锁,遍历缓存中的所有注册中心对象,调用对应注册中心对象的destroy方法来销毁,最后释放锁。
2.2 AbstractRegistry
AbstractRegistry类是一个抽象类,是Registry 接口的抽象实现,我们先来看下class定义:
public abstract class AbstractRegistry implements Registry {...}
接下来再来看下它的构造:
public AbstractRegistry(URL url) {
// 设置 注册 url
setUrl(url);
localCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
if (localCacheEnabled) {
// Start file save timer 是否同步保存到文件中
syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getApplication() + "-" + 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;
// When starting the subscription center,
// we need to read the local cache file for future Registry fault tolerance processing.
// todo
loadProperties();
// todo
notify(url.getBackupUrls());
}
}
先是调用setUrl方法设置注册中心url,接着就是获取save.file参数值,该参数值表示是否同步到文件中,缺省是false, 再往下就是获取file属性值,这个属性值就是注册中心缓存文件的位置,缺省的话就是在用户家目录/.dubbo文件夹下面 “dubbo-registry-应用名(也就application)-注册中心ip:port.cache ” 文件名,例如:
/Users/mac/.dubbo/dubbo-registry-dubbo-consumer-127.0.0.1:2181.cache
我们可以看下具体内容是什么
接着就是判断这个文件是否存在了,创建file,将file赋值给成员变量的file。
在往后调用loadProperties();加载这个文件里面的键值对了。我们看下这个方法
// 将cache 文件流load到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("Loaded registry cache file " + file);
}
} catch (Throwable e) {
logger.warn("Failed to load registry cache file " + file, e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
}
}
}
可以看到就是把刚才那个缓存文件解析成properties对象,这个很简单。
接着构造方法往后看,notify(url.getBackupUrls());这个就是从注册中心url中找到backup属性值,这个backup属性值就是需要恢复的一些服务提供者url,再调用notify进行通知,将那些恢复的url通知给订阅方,我们看下notify方法
protected void notify(List<URL> urls) {
if (CollectionUtils.isEmpty(urls)) {
return;
}
// getSubscribed() 获取所有服务订阅者
for (Map.Entry<URL, Set<NotifyListener>> entry : getSubscribed().entrySet()) {
URL url = entry.getKey();
if (!UrlUtils.isMatch(url, urls.get(0))) {
continue;
}
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);
}
}
}
}
}
到这咱们的构造方法就解析完成了
接下来看下它的成员有哪些:
// Local disk cache, where the special key value.registries records the list of registry centers, and the others are the list of notified service providers
private final Properties properties = new Properties();
// File cache timing writing
private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveRegistryCache", true));
// Is it synchronized to save the file 是否同步保存到file中
private boolean syncSaveFile;
private final AtomicLong lastCacheChanged = new AtomicLong();
private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger();
// 已经注册的
private final Set<URL> registered = new ConcurrentHashSet<>();
// 已经订阅的
private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<>();
// 已经通知的
private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>();
// 注册中心url
private URL registryUrl;
// Local disk cache file
private File file;
private boolean localCacheEnabled;
- properties这个成员就是咱们loadProperties方法那个存储注册中心缓存的。
- registryCacheExecutor这个成员是进行注册中心缓存的线程。
- syncSaveFile 是否同步保存到文件。
- lastCacheChanged 记录最后一次缓存的版本
- registered 存放已经注册的url
- subscribed 存放订阅的信息,key是订阅的url,然后value就是那些订阅者,一旦key这个url有变化,就通知
value这堆订阅者
- notified存放已经通知的信息
接下来看下register注册方法:
@Override
public void register(URL url) {
if (url == null) {
throw new IllegalArgumentException("register url == null");
}
if (url.getPort() != 0) {
if (logger.isInfoEnabled()) {
logger.info("Register: " + url);
}
}
registered.add(url);
}
可以看到就是检查注册url,然后添加到registered 这个set里面缓存起来。
接着看下unregister取消注册方法:
@Override
public void unregister(URL url) {
if (url == null) {
throw new IllegalArgumentException("unregister url == null");
}
if (url.getPort() != 0) {
if (logger.isInfoEnabled()) {
logger.info("Unregister: " + url);
}
}
registered.remove(url);
}
就是检查注册url是否是空,然后从registered 这个set移除掉。
接着看下subscribe 订阅方法的实现:
@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);
}
// 缓存订阅的url和Listener
Set<NotifyListener> listeners = subscribed.computeIfAbsent(url, n -> new ConcurrentHashSet<>());
listeners.add(listener);
}
可以看到一开始检查订阅url与listener,接着就是根据订阅url获取subscribed 中的监听集合,如果没有就新建塞进去,最后将listener添加到监听集合中。
接着看下unsubscribe取消订阅的实现:
@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);
}
// 移除缓存中某个url的listener
Set<NotifyListener> listeners = subscribed.get(url);
if (listeners != null) {
listeners.remove(listener);
}
// do not forget remove notified
notified.remove(url);
}
就是从监听集合移除这个listener
接着我们再来看下其他方法:
先来看下recover 恢复方法(该方法在子类重连接的时候会被用到)
protected void recover() throws Exception {
// register
Set<URL> recoverRegistered = new HashSet<>(getRegistered());
if (!recoverRegistered.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover register url " + recoverRegistered);
}
for (URL url : recoverRegistered) {
register(url);
}
}
// subscribe
Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<>(getSubscribed());
if (!recoverSubscribed.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover subscribe url " + recoverSubscribed.keySet());
}
for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
URL url = entry.getKey();
for (NotifyListener listener : entry.getValue()) {
subscribe(url, listener);
}
}
}
}
我把recover 方法分成类两部分,先是恢复register 注册的,在就是恢复那些订阅者。
先看下恢复注册的,就是获取缓存在registered的url们,然后不是空的话就遍历注册。再看下恢复订阅信息,跟恢复注册的差不多,获取缓存在subscribed的订阅信息,遍历订阅。 我们再看下另外一个notify 方法,它与上面说的那个notify 方法是有差距的,上面那个方法是通知备份url的。
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 + ", url size: " + urls.size());
}
// keep every provider's category.
Map<String, List<URL>> result = new HashMap<>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getCategory(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);
// 主动调用各分类节点的notify(),更新到本地
listener.notify(categoryList);
// We will update our cache file after each notification.
// When our Registry has a subscribe failure due to network jitter, we can return at least the existing cache URL.
if (localCacheEnabled) {
// todo 保存到properties中
saveProperties(url);
}
}
}
上面那一堆就是验证是否为空的,接着遍历urls,根据category 来分类,然后从notified 已经通知的缓存中获取分类url信息,获取的结果是个map,Map<String, List<URL>> key是分组 ,value就是在该组下面的url
。接着就是遍历上面分组出来的那个result,塞到从缓存中获取的那个map中,接着调用saveProperties 方法。最后进行通知。
我们看下这个saveProperties 方法,这个方法其实就是进行缓存文件同步的:
private void saveProperties(URL url) {
if (file == null) {
return;
}
try {
StringBuilder buf = new StringBuilder();
// 根据url从 已经通知集合中获取 分类 urls
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified != null) {
// 遍历拼接 url
for (List<URL> us : categoryNotified.values()) {
for (URL u : us) {
if (buf.length() > 0) {
// 使用空字符串隔开
buf.append(URL_SEPARATOR);
}
buf.append(u.toFullString());
}
}
}
// key 接口全类名 value urls.toFullString
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);
}
}
先是根据url从已经通知缓存中获取对应的分类url集合,然后遍历拼接url.toFullString(),中间用空字符串隔开,然后将拼接的字符串塞进properties中,获取版本信息,判断syncSaveFile变量同步保存还是异步保存,同步的话就直接调用doSaveProperties方法进行保存,异步的话使用registryCacheExecutor线程池,提交任务的方式。先看下这个异步 SaveProperties类。
private class SaveProperties implements Runnable {
private long version;
private SaveProperties(long version) {
this.version = version;
}
@Override
public void run() {
doSaveProperties(version);
}
}
这里也是调用doSaveProperties方法,我们看下这个方法:
public void doSaveProperties(long version) {
// 判断版本
if (version < lastCacheChanged.get()) {
return;
}
// 判断file
if (file == null) {
return;
}
// Save
try {
//创建锁文件。
File lockfile = new File(file.getAbsolutePath() + ".lock");
// 如果不存在的话就创建
if (!lockfile.exists()) {
lockfile.createNewFile();
}
try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");
FileChannel channel = raf.getChannel()) {
//获取锁
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();
}
try (FileOutputStream outputFile = new FileOutputStream(file)) {
properties.store(outputFile, "Dubbo Registry Cache");
}
} finally {
// 释放锁
lock.release();
}
}
} catch (Throwable e) {
savePropertiesRetryTimes.incrementAndGet();
if (savePropertiesRetryTimes.get() >= MAX_RETRY_TIMES_SAVE_PROPERTIES) {
logger.warn("Failed to save registry cache file after retrying " + MAX_RETRY_TIMES_SAVE_PROPERTIES + " times, cause: " + e.getMessage(), e);
savePropertiesRetryTimes.set(0);
return;
}
if (version < lastCacheChanged.get()) {
savePropertiesRetryTimes.set(0);
return;
} else {
//出现异常 就异步保存
registryCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet()));
}
logger.warn("Failed to save registry cache file, will retry, cause: " + e.getMessage(), e);
}
}
doSaveProperties这个方法中,也是比较简单,先是获取锁文件,就是在缓存文件名后面加.lock这个文件,接着就是获取FileLock 文件锁,将properties写入到对应注册中心缓存文件中,最后调用lock.release()释放文件锁,如果出现异常,就是用异步保存。 接下来我们再来看下lookup 方法,该方法就是根据url查找对应url集合的:
@Override
public List<URL> lookup(URL url) {
List<URL> result = new ArrayList<>();
//从已经通知 缓存中获取某个url对应的分组url集合
Map<String, List<URL>> notifiedUrls = getNotified().get(url);
// protocol不是空的话
if (CollectionUtils.isNotEmptyMap(notifiedUrls)) {
for (List<URL> urls : notifiedUrls.values()) {
for (URL u : urls) {
if (!EMPTY_PROTOCOL.equals(u.getProtocol())) {
result.add(u);
}
}
}
} else {
//使用listener订阅的方式获取 urls
final AtomicReference<List<URL>> reference = new AtomicReference<>();
NotifyListener listener = reference::set;
subscribe(url, listener); // Subscribe logic guarantees the first notify to return
List<URL> urls = reference.get();
if (CollectionUtils.isNotEmpty(urls)) {
for (URL u : urls) {
if (!EMPTY_PROTOCOL.equals(u.getProtocol())) {
result.add(u);
}
}
}
}
return result;
}
lookup方法中先从已经通知notified 缓存集合中获取分组url的map集合,如果分组url的map集合 不是空然后遍历该集合,如果protocol不是empty的就添加到结果集合中返回。如果这个分组url的map集合是空,就实现一个listener,然后订阅该url ,如果有变动就会通知该listener的notify方法,该方法中就把 urls设置到reference中,调用reference获取urls,遍历urls,procotol不是empty的就添加到结果集合中返回。
2.3 FailbackRegistry
FailbackRegistry也是个抽象类,继承AbstractRegistry 抽象类,它主要是实现了失败重试的功能,下面我们就来看下它的实现。 先来看下它的class 定义
public abstract class FailbackRegistry extends AbstractRegistry {...}
接下来看下它的成员变量:
// 注册失败
private final ConcurrentMap<URL, FailedRegisteredTask> failedRegistered = new ConcurrentHashMap<URL, FailedRegisteredTask>();
// 注销失败
private final ConcurrentMap<URL, FailedUnregisteredTask> failedUnregistered = new ConcurrentHashMap<URL, FailedUnregisteredTask>();
// 订阅失败
private final ConcurrentMap<Holder, FailedSubscribedTask> failedSubscribed = new ConcurrentHashMap<Holder, FailedSubscribedTask>();
// 取消订阅失败
private final ConcurrentMap<Holder, FailedUnsubscribedTask> failedUnsubscribed = new ConcurrentHashMap<Holder, FailedUnsubscribedTask>();
/**
* The time in milliseconds the retryExecutor will wait
*/
// 重试时间间隔
private final int retryPeriod;
// Timer for failure retry, regular check if there is a request for failure, and if there is, an unlimited retry
// 重试线程池
private final HashedWheelTimer retryTimer;
挨个解释下是干嘛的:
- retryTimer :调度线程池,他这边是1个线程,主要是用来定时重试的。
- failedRegistered:用来存放缓存注册失败url
- failedUnregistered:用来存放缓存下线失败的url
- failedSubscribed:用来存放缓存取消订阅的,主要是url与对应的listener
- failedUnsubscribed:用来存放取消订阅失败信息,主要是url与对应的listener
- retryPeriod:重试的时间间隔,默认是5秒
接下来看下构造方法:
public FailbackRegistry(URL url) {
super(url);
// 重试的间隔时间 默认是5秒
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);
}
可以看出来先是调用父类的单参构造,这里就是调用AbstractRegistry抽象类的构造,接着获取retry.period 参数值,这个表示重试的时间间隔,缺省的话是5 * 1000ms也就是5秒,再往下就是生成调度任务的,执行时间+每隔5秒 后执行一次。这个是采用时间轮机制
进行调度,大家感兴趣 可以从网上找相关文章进行学习。
接下来看下register 注册方法的实现:
// 注册,这个要注册的url就是业务url
@Override
public void register(URL url) {
if (!acceptable(url)) {
logger.info("URL " + url + " will not be registered to Registry. Registry " + url + " does not accept service of this protocol type.");
return;
}
// 检查缓存
super.register(url);
// 移除失败的缓存
removeFailedRegistered(url);
removeFailedUnregistered(url);
try {
// Sending a registration request to the server side
doRegister(url); // todo 继续注册
} 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)
&& !(url.getPort() == 0);
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);
}
// Record a failed registration request to a failed list, retry regularly
// 将url添加到failedRegistered 中
addFailedRegistered(url);
}
}
可以看到先是调用父类的register方法进行注册,接着就是从 注册失败集合与下线失败集合中移除掉这个url,调用抽象方法doRegister(url);进行注册,该方法是个抽象方法,需要子类具体实现,protected abstract void doRegister(URL url);,如果出现异常,先是是否需要检查,需要跳过失败重试,如果是就直接抛出异常,不是的话就是打印error日志,将需要注册的url添加到failedRegistered这个缓存失败集合中。
接下来看下unregister下线方法:
@Override
public void unregister(URL url) {
super.unregister(url);
removeFailedRegistered(url);
removeFailedUnregistered(url);
try {
// Sending a cancellation request to the server side
// todo
doUnregister(url);
} 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)
&& !(url.getPort() == 0);
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 unregister " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
// Record a failed registration request to a failed list, retry regularly
addFailedUnregistered(url);
}
}
这个方法跟注册方法register 差不多,先是调用父类的unregister方法进行下线,接着就是从 注册失败集合与下线失败集合中移除掉这个url,调用doUnregister(url)方法进行下线,doUnregister也是个抽象方法,需要具体的子类来实现,protected abstract void doUnregister(URL url); 出现异常判断是否检查与跳过失败重试,最后将url添加到failedUnregistered下线失败的集合中
接着再来看下subscribe订阅方法的实现:
@Override
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
removeFailedSubscribed(url, listener);
try {
// Sending a subscription request to the server side
// todo
doSubscribe(url, listener); // 继续订阅
} catch (Exception e) {
Throwable t = e;
List<URL> urls = getCacheUrls(url);
if (CollectionUtils.isNotEmpty(urls)) {
notify(url, listener, urls);
logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(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);
}
}
// Record a failed registration request to a failed list, retry regularly
// 添加到 订阅失败中
addFailedSubscribed(url, listener);
}
}
先是调用父类的订阅方法subscribe,接着调用removeFailedSubscribed方法移除失败订阅的缓存。再往后调用抽象方法doSubscribe(url, listener)进行订阅,protected abstract void doSubscribe(URL url, NotifyListener listener);该方法需要其子类具体实现,如果出现异常,现获取缓存的url集合,如果urls不是空就进行通知,如果是空的话需要是否检查与跳过失败重试,最后调用addFailedSubscribed(url, listener)方法将订阅失败的信息添加到failedSubscribed这个缓存中。这里removeFailedSubscribed方法与addFailedSubscribed需要看下。
public void removeFailedSubscribed(URL url, NotifyListener listener) {
Holder h = new Holder(url, listener);
// 移除 失败的订阅
FailedSubscribedTask f = failedSubscribed.remove(h);
if (f != null) {
f.cancel();
}
// 移除 失败的取消订阅
removeFailedUnsubscribed(url, listener);
}
在移除失败订阅信息removeFailedSubscribed方法中,先是从failedSubscribed 存放失败订阅的map中移除对应的listener,然后又从failedUnsubscribed取消订阅失败的集合中移除对应的url的listener,最后是从通知失败的集合failedNotified中移除对应的listener。
protected void addFailedSubscribed(URL url, NotifyListener listener) {
Holder h = new Holder(url, listener);
FailedSubscribedTask oldOne = failedSubscribed.get(h);
if (oldOne != null) {
return;
}
FailedSubscribedTask newTask = new FailedSubscribedTask(url, this, listener);
oldOne = failedSubscribed.putIfAbsent(h, newTask);
if (oldOne == null) {
// never has a retry task. then start a new task for retry.
retryTimer.newTimeout(newTask, retryPeriod, TimeUnit.MILLISECONDS);
}
}
在添加订阅失败addFailedSubscribed方法中就是根据url获取对应的集合,如果没有对应的集合就创建,然后将listener添加到集合中。
接下来看下取消订阅方法unsubscribe实现
@Override
public void unsubscribe(URL url, NotifyListener listener) {
// 交给父类 缓存
super.unsubscribe(url, listener);
// 移除 失败的订阅
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);
}
// Record a failed registration request to a failed list, retry regularly
addFailedUnsubscribed(url, listener);
}
}
也是先调用父类的unsubscribe方法,接着调用removeFailedSubscribed方法移除一些缓存信息,再往后就是调用抽象方法doUnsubscribe(url, listener)进行取消订阅,该方法也是需要子类实现,protected abstract void doUnsubscribe(URL url, NotifyListener listener);如果出现异常需要判断是否检查与跳过失败重试,如果需要就抛出异常,如果不需要只是打印error日志,接着就是往failedUnsubscribed 存放取消订阅失败集合中添加这个取消信息了。
接着在看下notify通知方法实现:
@Override
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");
}
try {
// todo
doNotify(url, listener, urls);
} catch (Exception t) {
// Record a failed registration request to a failed list
logger.error("Failed to notify addresses for subscribe " + url + ", cause: " + t.getMessage(), t);
}
}
先是检查url与listener,接着调用doNotify(url, listener, urls);方法进行通知:
protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
super.notify(url, listener, urls);
}
可以看到doNotify方法就是调用父类notify方法通知。如果出现异常,就将通知信息添加到failedNotified 这个存放通知失败的集合中。
接下来再来看下这个recover恢复方法:
@Override
protected void recover() throws Exception {
// register
// 获取已经注册过的
Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
if (!recoverRegistered.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover register url " + recoverRegistered);
}
for (URL url : recoverRegistered) {
// remove fail registry or unRegistry task first.
removeFailedRegistered(url);
removeFailedUnregistered(url);
// 添加到失败注册中
addFailedRegistered(url);
}
}
// subscribe
// 获取已经订阅过的
Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
if (!recoverSubscribed.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info("Recover subscribe url " + recoverSubscribed.keySet());
}
for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
URL url = entry.getKey();
for (NotifyListener listener : entry.getValue()) {
// First remove other tasks to ensure that addFailedSubscribed can succeed.
removeFailedSubscribed(url, listener);
// 添加到失败订阅中
addFailedSubscribed(url, listener);
}
}
}
}
可以看到有两部分,先是调用父类getRegistered方法获取需要恢复注册的url,然后循环添加到注册失败的集合failedRegistered中,这里这么做的原因是有失败重试的定时任务会进行重新注册。下面那部分是调用getSubscribed方法获取已经订阅的信息,然后遍历添加到失败的订阅的集合failedSubscribed 中,定时任务也会给他重新订阅。
最后我们再来看下 时间轮机制中失败重试的
方法,该方法是失败重试定时任务调用的,用来失败重试的。抽象类,
public abstract class AbstractRetryTask implements TimerTask {
...
@Override
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled() || timeout.timer().isStop() || isCancel()) {
// other thread cancel this timeout or stop the timer.
return;
}
if (times > retryTimes) {
// reach the most times of retry.
logger.warn("Final failed to execute task " + taskName + ", url: " + url + ", retry " + retryTimes + " times.");
return;
}
if (logger.isInfoEnabled()) {
logger.info(taskName + " : " + url);
}
try {
doRetry(url, registry, timeout);
} catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
logger.warn("Failed to execute task " + taskName + ", url: " + url + ", waiting for again, cause:" + t.getMessage(), t);
// reput this task when catch exception.
reput(timeout, retryPeriod);
}
}
// 具体子类实现
protected abstract void doRetry(URL url, FailbackRegistry registry, Timeout timeout);
}
-
注册失败的重试,遍历注册失败集合,然后调用 doRegister(url);进行重新注册,接着就是从failedRegistered移除该url。
public final class FailedRegisteredTask extends AbstractRetryTask { private static final String NAME = "retry register"; public FailedRegisteredTask(URL url, FailbackRegistry registry) { super(url, registry, NAME); } @Override protected void doRetry(URL url, FailbackRegistry registry, Timeout timeout) { registry.doRegister(url); registry.removeFailedRegisteredTask(url); } }
-
下线失败的重试,也是遍历下线失败集合,然后调用doUnregister(url); 方法进行下线,从failedUnregistered移除该url。
public final class FailedUnregisteredTask extends AbstractRetryTask { private static final String NAME = "retry unregister"; public FailedUnregisteredTask(URL url, FailbackRegistry registry) { super(url, registry, NAME); } @Override protected void doRetry(URL url, FailbackRegistry registry, Timeout timeout) { registry.doUnregister(url); registry.removeFailedUnregisteredTask(url); } }
-
订阅失败的重试,也是一样的,遍历调用doSubscribe 方法进行重新订阅,然后从集合中移除对应的listener。
public final class FailedSubscribedTask extends AbstractRetryTask { private static final String NAME = "retry subscribe"; private final NotifyListener listener; public FailedSubscribedTask(URL url, FailbackRegistry registry, NotifyListener listener) { super(url, registry, NAME); if (listener == null) { throw new IllegalArgumentException(); } this.listener = listener; } @Override protected void doRetry(URL url, FailbackRegistry registry, Timeout timeout) { registry.doSubscribe(url, listener); registry.removeFailedSubscribedTask(url, listener); } }
-
取消订阅的失败重试,也是遍历调用doUnsubscribe(url, listener)方法进行重新取消订阅,然后从集合中移除对应的listener。
public final class FailedUnsubscribedTask extends AbstractRetryTask { private static final String NAME = "retry unsubscribe"; private final NotifyListener listener; public FailedUnsubscribedTask(URL url, FailbackRegistry registry, NotifyListener listener) { super(url, registry, NAME); if (listener == null) { throw new IllegalArgumentException(); } this.listener = listener; } @Override protected void doRetry(URL url, FailbackRegistry registry, Timeout timeout) { registry.unsubscribe(url, listener); registry.removeFailedUnsubscribedTask(url, listener); } }
2.4 总结
注册中心抽象实现就解析完成了,主要是解析了注册中心工厂的抽象实现AbstractRegistryFactory,注册中心抽象AbstractRegistry与FailbackRegistry,在AbstractRegistryFactory 中,主要是处理了对注册中心对象的缓存。AbstractRegistry中主要是处理了对注册,订阅,取消注册,取消订阅,已经通知的缓存,并且提供了注册中心本地缓存与恢复。FailbackRegistry 中主要是处理了对注册失败,取消注册失败,订阅失败,取消订阅失败,通知失败的缓存,并且进行重试工作。
3. 注册中心zookeeper实现
3.1 ZookeeperRegistryFactory
ZookeeperRegistryFactory是注册中心zookeeper工厂的实现,代码比较简单,我们直接来看下。
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
private ZookeeperTransporter zookeeperTransporter;
public ZookeeperRegistryFactory() {
this.zookeeperTransporter = ZookeeperTransporter.getExtension();
}
@DisableInject
// 由dubbo spi自动注入 zk客户端
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
@Override
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}
可以看到ZookeeperRegistryFactory继承AbstractRegistryFactory抽象类,需要实现createRegistry方法,在createRegistry方法中,创建了一个zookeeper注册中心对象,参数分别是注册中心url与zookeeperTransporter (这个是dubbo spi 自动注入的,zookeeperTransporter能够获取zk客户端对象,这个文章后面会有说到)
3.2 ZookeeperRegistry
ZookeeperRegistry注册中心zk的实现,我们来看看它的源码实现
3.2.1 class与成员变量
public class ZookeeperRegistry extends FailbackRegistry {...}
可以看出ZookeeperRegistry继承FailbackRegistry失败重试抽象类,ZookeeperRegistry就需要实现doRegister,doUnregister,doSubscribe,doUnsubscribe 这几个方法(这个我们后面会一一解析) 接下来我们来看成员变量
private final static String DEFAULT_ROOT = "dubbo";
// 根路径
private final String root;
private final Set<String> anyServices = new ConcurrentHashSet<>();
// 缓存的订阅listener
private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<>();
// zk客户端
private final ZookeeperClient zkClient;
3.2.2 构造
接下来看下构造方法:
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
// group 默认dubbo 获取根路径
String group = url.getGroup(DEFAULT_ROOT);
if (!group.startsWith(PATH_SEPARATOR)) {
group = PATH_SEPARATOR + group;
}
this.root = group;
// 创建zk客户端,与zk进行连接 默认使用curator
zkClient = zookeeperTransporter.connect(url);
zkClient.addStateListener((state) -> {
// 状态是 已连接的 时候
if (state == StateListener.RECONNECTED) {
logger.warn("Trying to fetch the latest urls, in case there're provider changes during connection loss.\n" +
" Since ephemeral ZNode will not get deleted for a connection lose, " +
"there's no need to re-register url of this instance.");
ZookeeperRegistry.this.fetchLatestAddresses();
} else if (state == StateListener.NEW_SESSION_CREATED) {
logger.warn("Trying to re-register urls and re-subscribe listeners of this instance to registry...");
try {
// 恢复 注册 与订阅
ZookeeperRegistry.this.recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
} else if (state == StateListener.SESSION_LOST) {
logger.warn("Url of this instance will be deleted from registry soon. " +
"Dubbo client will try to re-register once a new session is created.");
} else if (state == StateListener.SUSPENDED) {
} else if (state == StateListener.CONNECTED) {
}
});
}
在构造方法中,先是调用父类的构造,接着判断注册中心url的host是不是0.0.0.0(也就是任意host),下面就是获取group参数值,缺省是dubbo,如果不是"/“开头的就在开头拼接个”/",将group 赋值给root属性(其实这里很重要的,这个root就是在zk里面的根路径,如果你配置了组信息,将以你那个组信息值开头),接着就是使用zookeeperTransporter获取zk客户端对象,给zk客户端添加一个重连监听器,如果状态是重连状态,就调用recover方法来进行恢复,这里使用zookeeperTransporter获取zk客户端对象需要说下,zk的java客户端有两种,一个是curator,一个是zkclient,同时dubbo将这两种客户端一系列操作抽象了一个接口ZookeeperClient
,curator与zkclient分别实现ZookeeperClient,这样做的好处就是统一了操作,方便开发,同时也增加了灵活性扩展性,使用zookeeperTransporter就是为了获取用户配置的ZookeeperClient实现,这里先看下ZookeeperClient实现关系:
再来看下ZookeeperClient 抽象了哪些操作:
public interface ZookeeperClient {
// 创建节点
void create(String path, boolean ephemeral);
// 删除节点
void delete(String path);
// 获取某个节点的子节点
List<String> getChildren(String path);
//为某个节点添加子节点监听器
List<String> addChildListener(String path, ChildListener listener);
// 移除某个节点的某个子节点监听器
void removeChildListener(String path, ChildListener listener);
// 添加 连接状态监听器
void addStateListener(StateListener listener);
// 移除 连接状态监听器
void removeStateListener(StateListener listener);
// 是否连接
boolean isConnected();
// 关闭
void close();
// 获取url
URL getUrl();
}
zk客户端具体实现我们会后面说下。
3.2.3 doRegister
接着我们看下ZookeeperRegistry的doRegister进行注册方法实现:
@Override
public void doRegister(URL url) {
try { // 创建一个临时节点
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
我们可以看到使用zk客户端创建一个节点,这个节点的路径是toUrlPath(url) , 然后持久还是动态是由url中配置的dynamic参数值决定的,缺省是使用动态(所谓的动态就是会话级别的,断开连接,这个路径就没了) 这里我们更关心toUrlPath(url) 这个方法的实现:
// 将url转成 urlPath
private String toUrlPath(URL url) {
// "/dubbo/xxx.xxx.xxx.Xxxx/gategory(providers)/urlfullString"
return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString());
}
可以看到是toCategoryPath生成的一个path 拼接上 “/” ,再拼接上url的fullstring信息。接着在看下toCategoryPath方法
private String toCategoryPath(URL url) {
// "/dubbo/xxx.xxx.xxx.Xxxx/gategory(providers)"
return toServicePath(url) + Constants.PATH_SEPARATOR + url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
}
toCategoryPath方法是 toServicePath(url) 获取的path路径拼接上"/" ,在拼接上url的category分类信息,默认是providers
private String toServicePath(URL url) {
String name = url.getServiceInterface();// 接口全类名
if (Constants.ANY_VALUE.equals(name)) {
return toRootPath();
}
// "/dubbo/xxx.xxx.xxx.Xxxx"
return toRootDir() + URL.encode(name);
}
toServicePath 中,先是获取接口全类名,如果接口全类名是*, 直接返回 root的值,也就是/dubbo 或者/你设置的group, 如果不是 ,就返回/dubbo/接口全类名
这个toUrlPath 方法全部拼接出来就是:
/dubbo(或者你设置的group属性值)/接口全类名(如果你接口全类名是`*` ,就没有这部分了)/ providers(也可以是consumers,routers,configurators 这个是根据你的category 属性值来的)/url的fullstring(这里其实就是url的 protocol, host,port,service,parameter等等)
这个就是你某个serivce在zk中的存在结构,我这里举个例子
/dubbo
/com.hsf.dubbo.provider.IHelloProviderService
/providers
/dubbo%3A%2F%2F192.162.0.174%3A18109%2Fcom.xuzhaocai.dubbo.provider.IHelloProviderService%3Faccesslog%3Dtrue%26anyhost%3Dtrue%26application%3Ddubbo-consumer%26bean.name%3DServiceBean%3Acom.hsf.dubbo.provider.IHelloProviderService%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.xuzhaocai.dubbo.provider.IHelloProviderService%26methods%3DgetName%26pid%3D39753%26side%3Dprovider%26timestamp%3D1598241007805
在zk中大概就是下图这样一个结构(树结构):
3.2.4 doUnregister
接着我们再来看下doUnregister 下线方法实现:
@Override
public void doUnregister(URL url) {
try {
zkClient.delete(toUrlPath(url));
} catch (Throwable e) {
throw new RpcException("Failed to unregister " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
这里就是调用zk客户端的delete方法删除url对应path的节点。
3.2.5 doSubscribe
接下来再来看下doSubscribe 进行订阅的方法(由于方法比较长,分为2部分): 第一部分是订阅所有
当url的service interface 是 * 的时候,表示要订阅所有,先是获取root,订阅所有,其实就是订阅根节点下面的所有子节点,接着从缓存中获取url对应的listener map ,这里为啥是map呢,其实一个url可以多个订阅者(也就可以有多个监听器),每个监听器又对应一个具体的子节点监听器。如果缓存中没有存在,就创建。如果没有获取到子节点监听器,也创建ChildListener对象,我们可以看看它里面的实现:
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> {
// 遍历child节点
for (String child : currentChilds) {
child = URL.decode(child);
if (!anyServices.contains(child)) {
anyServices.add(child);
// 订阅
subscribe(url.setPath(child).addParameters(INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), k);
}
}
});
当订阅节点下面的子节点发生变化的时候,就会调用childChanged 方法,parentPath 表示父节点,也就是该节点下面的子节点发生了变化,currentChilds表示当前parentPath 节点下面所有的子节点集合。在childChanged方法中,对子节点集合进行遍历,然后如果这个子节点没有在anyServices 集合中,就添加进去(anyServices 集合 就是存的所有的接口全类名,也就是根节点下的所有子节点),接着就是订阅这个子节点。
doSubscribe 方法接着往下看,使用zk客户端创建root节点,对root节点添加监听器listener,获取到root节点(也就是根节点 比如/dubbo)子节点集合,遍历集合添加到anyServices 集合中,然后订阅这堆子节点。
接着再看看第二部分,订阅某个接口的时候。
if(){
....
} else { // 处理<dubbo:reference/>的interface属性为普通值的情况
CountDownLatch latch = new CountDownLatch(1);
List<URL> urls = new ArrayList<>();
// 遍历configurators、routers与providers三个路径
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, path, k, latch));
if (zkListener instanceof RegistryChildListenerImpl) {
((RegistryChildListenerImpl) zkListener).setLatch(latch);
}
// 创建持久节点
zkClient.create(path, false);
// 为创建的节点添加子节点列表变更的watcher监听
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
// toUrlsWithEmpty
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
// 主动调用notify更新本地invoker
notify(url, listener, urls);
// tells the listener to run only after the sync notification of main thread finishes.
latch.countDown();
}
该部分中,先是通过toCategoriesPath获取paths,这个paths的个数其实是由url的category属性决定的 这个path就是上面说的这个,需要注意的是,这里path是没有url.tofullstring 那部分的,因为我们这订阅的是它的父节点,当下面子节点发生变动的时候就会通知。
/dubbo(或者你设置的group属性值)/接口全类名(如果你接口全类名是`*` ,就没有这部分了)/ providers(也可以是consumers,routers,configurators 这个是根据你的category 属性值来的)
如果你的category属性值是* 它就创建4个,分别是providers,routers,consumers,configurators,不是* 就去你设置的那个,缺省是providers。接着就是根据url从zkListeners缓存中获取,如果没有的话就创建,跟上面一样,但是它这里创建的子节点监视器ChildListener 对象与上面那部分不一样(这里很重要)
public RegistryChildListenerImpl(URL consumerUrl, String path, NotifyListener listener, CountDownLatch latch) {
this.latch = latch;
notifier = new RegistryNotifier(ZookeeperRegistry.this.getDelay()) {
@Override
public void notify(Object rawAddresses) {
long delayTime = getDelayTime();
if (delayTime <= 0) {
this.doNotify(rawAddresses);
} else {
long interval = delayTime - (System.currentTimeMillis() - lastExecuteTime);
if (interval > 0) {
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
// ignore
}
}
lastExecuteTime = System.currentTimeMillis();
this.doNotify(rawAddresses);
}
}
@Override
protected void doNotify(Object rawAddresses) {
ZookeeperRegistry.this.notify(consumerUrl, listener, ZookeeperRegistry.this.toUrlsWithEmpty(consumerUrl, path, (List<String>) rawAddresses));
}
};
}
当订阅的节点子类发生变化的时候,就会调用监听器的childChanged 方法,在childChanged 方法 中调用了notify方法进行通知。
再往下看,就是创建这个path,在path上面添加监听器,能够获取现在path下面的子节点,然后把这些子节点经过过滤后添加到list中。
最后遍历完成后,调用notify通知(这里这样的做的原因是第一次订阅的时候,如果不通知,需要子节点变动的时候才能通知,才能获取到服务提供者列表),这里面有个toUrlsWithEmpty(url, path, children) 方法,该方法将 字符串子节点转成url集合,其实这个方法中有个比较重要的点就是过滤,需要与consumerUrl的某些条件匹配上。我们来看下:
protected List<URL> toUrlsWithEmpty(URL consumer, String path, Collection<String> providers) {
List<URL> urls = new ArrayList<>(1);
// 判断当前path是否以providers结尾
boolean isProviderPath = path.endsWith(PROVIDERS_CATEGORY);
if (isProviderPath) {
// 处理providers节点情况
if (CollectionUtils.isNotEmpty(providers)) {
// 将providers节点的所有子节点变为url
urls = toUrlsWithoutEmpty(consumer, providers);
} else {
// clear cache on empty notification: unsubscribe or provider offline
evictURLCache(consumer);
}
} else {
// 处理configurators与routers节点情况
if (CollectionUtils.isNotEmpty(providers)) {
// 将节点的所有子节点变为url
urls = toConfiguratorsWithoutEmpty(consumer, providers);
}
}
// 为没有子节点的节点创建一个 empty://...的URL
if (urls.isEmpty()) {
int i = path.lastIndexOf(PATH_SEPARATOR);
String category = i < 0 ? path : path.substring(i + 1);
URL empty = URLBuilder.from(consumer)
.setProtocol(EMPTY_PROTOCOL)
.addParameter(CATEGORY_KEY, category)
.build();
urls.add(empty);
}
return urls;
}
开始调用toUrlsWithoutEmpty(consumer, providers); 方法将字符串子节点转成url集合,我们来看下这个方法:
protected List<URL> toUrlsWithoutEmpty(URL consumer, Collection<String> providers) {
// keep old urls
Map<String, ServiceAddressURL> oldURLs = stringUrls.get(consumer);
// create new urls
Map<String, ServiceAddressURL> newURLs;
URL copyOfConsumer = removeParamsFromConsumer(consumer);
if (oldURLs == null) {
newURLs = new HashMap<>();
for (String rawProvider : providers) {
rawProvider = stripOffVariableKeys(rawProvider);
// todo
ServiceAddressURL cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
if (cachedURL == null) {
logger.warn("Invalid address, failed to parse into URL " + rawProvider);
continue;
}
newURLs.put(rawProvider, cachedURL);
}
} else {
newURLs = new HashMap<>((int) (oldURLs.size() / .75 + 1));
// maybe only default , or "env" + default
for (String rawProvider : providers) {
rawProvider = stripOffVariableKeys(rawProvider);
ServiceAddressURL cachedURL = oldURLs.remove(rawProvider);
if (cachedURL == null) {
cachedURL = createURL(rawProvider, copyOfConsumer, getExtraParameters());
if (cachedURL == null) {
logger.warn("Invalid address, failed to parse into URL " + rawProvider);
continue;
}
}
newURLs.put(rawProvider, cachedURL);
}
}
evictURLCache(consumer);
stringUrls.put(consumer, newURLs);
return new ArrayList<>(newURLs.values());
}
接着就是调用createURL() -> isMatch(consumerURL, cachedURL)
这里就是遍历providers,然后需要两个条件才能添加到结果集合中,一个是判断 里面有没有:// 这个东西就是放在协议protocol后面的,这个有之后 ,将字符串就转成了url,然后调用UrlUtils.isMatch(consumer, url)方法进行匹配,匹配的上才能添加到结果集合中,这个isMatch 方法主要比对双方的 接口,category,enable,group,version,classifier 这些信息:
public static boolean isMatch(URL consumerUrl, URL providerUrl) {
// 获取接口
String consumerInterface = consumerUrl.getServiceInterface();
// 获取接口
String providerInterface = providerUrl.getServiceInterface();
//FIXME accept providerUrl with '*' as interface name, after carefully thought about all possible scenarios I think it's ok to add this condition.
// conusmerInterface 不是 * 或者consumerInterface 与providerInterface 不相等
if (!(ANY_VALUE.equals(consumerInterface)
|| ANY_VALUE.equals(providerInterface)
|| StringUtils.isEquals(consumerInterface, providerInterface))) {
return false;
}
// category 是符合要求的
if (!isMatchCategory(providerUrl.getCategory(DEFAULT_CATEGORY),
consumerUrl.getCategory(DEFAULT_CATEGORY))) {
return false;
}
// provider enable 是false && consumer enable不是*
if (!providerUrl.getParameter(ENABLED_KEY, true)
&& !ANY_VALUE.equals(consumerUrl.getParameter(ENABLED_KEY))) {
return false;
}
// group
String consumerGroup = consumerUrl.getGroup();
// version
String consumerVersion = consumerUrl.getVersion();
// Classifier 默认是*
String consumerClassifier = consumerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
String providerGroup = providerUrl.getGroup();
String providerVersion = providerUrl.getVersion();
String providerClassifier = providerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
// consumer category 是* 或者 category 相等 或者 consumer category 中包含着provider的category
return (ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup))
// consumer的version 是* 或者 两个version相等
&& (ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))
// consumerClassifier 是null 或者consumerClassifier 是* 或者相等
&& (consumerClassifier == null || ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier));
}
再回到toUrlsWithEmpty 方法中,转换成urls之后,如果是空的,就生成一个protocol 是empty的url塞进去返回,为什么要生成一个empty的url(这玩意就是代表着注册中心没有该consumer的实现了,本地缓存也就需要清空),原因是listener实现类notify方法中,看到就这一条empty url的时候就会清空对应的category下的url缓存。
到这我们的执行订阅doSubscribe 方法就讲解完成了
3.2.6 doUnsubscribe
接下来我们看下doUnsubscribe 执行取消订阅的方法的实现:
@Override
public void doUnsubscribe(URL url, NotifyListener listener) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners != null) {
ChildListener zkListener = listeners.remove(listener);
if (zkListener != null) {
if (ANY_VALUE.equals(url.getServiceInterface())) {
String root = toRootPath();
zkClient.removeChildListener(root, zkListener);
} else {
for (String path : toCategoriesPath(url)) {
zkClient.removeChildListener(path, zkListener);
}
}
}
if(listeners.isEmpty()){
zkListeners.remove(url);
}
}
}
这个比较简单,就是从缓存zkListeners 中获取对应的listener map集合,然后再获取listener对应的ChildListener监听器,如果不是空的话,判断接口是不是*,如果是,就调用zk客户端的removeChildListener方法移除根节点下的对应listener。
如果不是,就循环移除分组path的对应listener。
3.3 总结
本节主要讲解了 注册中心zk工厂的实现 ZookeeperRegistryFactory 与 注册中心zk 的实现ZookeeperRegistry ,其中注册中心zk 实现ZookeeperRegistry 是非常重要的,在这个类中并不是直接操作的zk,而是dubbo抽象了zk客户端 curator与 zkClient两种实现的一些操作,通过dubbo spi 来根据配置获得具体的实现(我们会单独解析zk客户端实现的),ZookeeperRegistry 中主要是注册到zk里面的树结构,订阅方法doSubscribe是很重要的,尤其是一些细节,比如说创建的节点监听 ,当子节点变动的时候,就会调用notify方法进行通知。
4. zk客户端实现
4.1 ZookeeperTransporter
ZookeeperTransporter其实就是使用dubbo spi获取zk客户端的接口
@SPI
public interface ZookeeperTransporter {
String CURATOR_5 = "curator5";
String CURATOR = "curator";
ZookeeperClient connect(URL url);
static ZookeeperTransporter getExtension() {
ExtensionLoader<ZookeeperTransporter> extensionLoader = getExtensionLoader(ZookeeperTransporter.class);
boolean isHighVersion = isHighVersionCurator();
if (isHighVersion) {
return extensionLoader.getExtension(CURATOR_5);
}
return extensionLoader.getExtension(CURATOR);
}
static boolean isHighVersionCurator() {
try {
Class.forName("org.apache.curator.framework.recipes.cache.CuratorCache");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
我们看下它的2个实现类CuratorZookeeperTransporter 与 Curator5ZookeeperTransporter
Curator5ZookeeperTransporter:
public class Curator5ZookeeperTransporter extends AbstractZookeeperTransporter {
@Override
public ZookeeperClient createZookeeperClient(URL url) {
return new Curator5ZookeeperClient(url);
}
我们可以看到connect方法实现就是创建Curator5ZookeeperTransporter 对象
CuratorZookeeperTransporter:
public class CuratorZookeeperTransporter extends AbstractZookeeperTransporter {
@Override
public ZookeeperClient createZookeeperClient(URL url) {
return new CuratorZookeeperClient(url);
}
}
CuratorZookeeperTransporter的connect实现就是创建CuratorZookeeperClient 对象。
在这个根据isHighVersionCurator()
返回使用那种对象。
4.2 ZookeeperClient
ZookeeperClient接口是dubbo 对zk操作的抽象,这样子做的原因是,java版本的zk客户端有curator与zkClient种,为了能够自由切换,就需要抽象出来一系列操作,分别由不同的客户端来进行实现。这样能够保证系统的灵活性,扩展性,所谓灵活性就是可以根据用户喜好自由配置,扩展性就是以后出现另一种客户端,只需要实现ZookeeperClient接口就可以了。在V3.0版本中,只有curator实现了。
public interface ZookeeperClient {
// 创建节点
void create(String path, boolean ephemeral);
// 删除节点
void delete(String path);
// 获取某个节点的子节点
List<String> getChildren(String path);
//为某个节点添加子节点监听器
List<String> addChildListener(String path, ChildListener listener);
// 移除某个节点的某个子节点监听器
void removeChildListener(String path, ChildListener listener);
// 添加 连接状态监听器
void addStateListener(StateListener listener);
// 移除 连接状态监听器
void removeStateListener(StateListener listener);
// 是否连接
boolean isConnected();
// 关闭
void close();
// 获取url
URL getUrl();
}
上面就是dubbo对zk封装的一系列操作。我们来看下它的继承关系图
可以看到还是很明显的,接下来的任务就是一一解析了。
4.3 AbstractZookeeperClient
AbstractZookeeperClient是zk客户端的抽象实现。它做了一些基本的操作,具体的操作还是由子类来实现的。
public abstract class AbstractZookeeperClient<TargetChildListener> implements ZookeeperClient {...}
可以看到AbstractZookeeperClient实现ZookeeperClient 接口,其中TargetChildListener 是个泛型,就跟我们平常写的T一个意思,只不过它这里为了好看使用了TargetChildListener,从字面意思上这个泛型是个childListener监听器。
再来看下它的成员变量
private final URL url; // 注册中心的url
// 状态listener 列表 CopyOnWriteArraySet:线程安全
private final Set<StateListener> stateListeners = new CopyOnWriteArraySet<StateListener>();
// 缓存着 path与listener的关系
private final ConcurrentMap<String, ConcurrentMap<ChildListener, TargetChildListener>> childListeners = new ConcurrentHashMap<String, ConcurrentMap<ChildListener, TargetChildListener>>();
private volatile boolean closed = false;// 关闭标识 volatile修饰
接下来看下构造,就是设置 注册中心url
public AbstractZookeeperClient(URL url) {
this.url = url;
}
接下来就是create方法,创建某个节点
@Override
public void create(String path, boolean ephemeral) {
if (!ephemeral) {
if (persistentExistNodePath.contains(path)) {
return;
}
if (checkExists(path)) {
persistentExistNodePath.add(path);
return;
}
}
int i = path.lastIndexOf('/');
if (i > 0) {
create(path.substring(0, i), false);
}
if (ephemeral) {
createEphemeral(path);
} else {
createPersistent(path);
persistentExistNodePath.add(path);
}
}
需要说明的是create 这个方法是一个递归,比如说你一个path是这个样子的(这里为了方便观察我回车换行了)
/dubbo
/com.xuzhaocai.dubbo.provider.IHelloProviderService
/providers
/dubbo%3A%2F%2F192.162.0.174%3A18109%2Fcom.xuzhaocai.dubbo.provider.IHelloProviderService%3Faccesslog%3Dtrue%26anyhost%3Dtrue%26application%3Ddubbo-consumer%26bean.name%3DServiceBean%3Acom.xuzhaocai.dubbo.provider.IHelloProviderService%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.xuzhaocai.dubbo.provider.IHelloProviderService%26methods%3DgetName%26pid%3D47110%26side%3Dprovider%26timestamp%3D1598321369244
它是这样一个执行顺序的,先是判断path是否是个永久节点(ephemeral这个参数表示 是创建永久节点还是临时节点,true的话就是临时节点,false就是永久节点),如果是永久节点的话就需要调用checkExists(path)方法检查这个path是否存在,checkExists是个抽象方法,需要子类来具体实现protected abstract boolean checkExists(String path);
,如果存在了就不需要创建了,接着就是从后面查找/这个分割符,如果是存在这个也就是i>0,就会调用create方法,这时候传入path的0位置到i位置那段,而且是个永久节点(注意这就进入递归了),上面那个path第一次 是
也就是全部,但是创建它需要创建下图这个
但是要想创建上图这个path就需要创建
就这样子最后要需要创建dubbo 这个节点(这个步骤里面存在就会返回)
创建的是这个样子的判断是临时节点还是永久节点,临时节点的话就调用createEphemeral(path)创建,永久节点的话就调用createPersistent(path)创建,需要注意的是这两个方法都是抽象方法,需要子类具体实现。
// 创建永久
protected abstract void createPersistent(String path);
// 创建临时
protected abstract void createEphemeral(String path);
接下来再来看下stateListener的两个操作:
// 添加StateListener
@Override
public void addStateListener(StateListener listener) {
stateListeners.add(listener);
}
// 移除StateListener
@Override
public void removeStateListener(StateListener listener) {
stateListeners.remove(listener);
}
可以看到很简单,就是操作stateListeners缓存集合进行添加跟移除。
接下来再来看下添加子监听器addChildListener 方法的实现
@Override
public List<String> addChildListener(String path, final ChildListener listener) {
// 根据path获取 childListener 与 具体节点监听器的对应关系
ConcurrentMap<ChildListener, TargetChildListener> listeners = childListeners.computeIfAbsent(path, k -> new ConcurrentHashMap<>());
// 根据childListener 获取对应的 具体节点监听器
TargetChildListener targetListener = listeners.computeIfAbsent(listener, k -> createTargetChildListener(path, k));
// 进行添加
return addTargetChildListener(path, targetListener);
}
在方法中,首先是根据path从childListeners缓存map中获取对应childListener 与 具体节点监听器的对应关系,如果不存在就创建,并且塞进去,接着又是根据childListener获取对应的具体节点监听器,如果不存在就调用createTargetChildListener(path, listener)方法进行创建然后塞到缓存中,最后是调用addTargetChildListener 方法进行添加。其中addTargetChildListener 与 createTargetChildListener 方法都是抽象方法,这里父类只是定义一套执行模版,定义了一套流程。 接着来看看removeChildListener移除方法:
@Override
public void removeChildListener(String path, ChildListener listener) {
ConcurrentMap<ChildListener, TargetChildListener> listeners = childListeners.get(path);
if (listeners != null) {
TargetChildListener targetListener = listeners.remove(listener);
if (targetListener != null) {
removeTargetChildListener(path, targetListener);
}
}
}
可以看出就是移除对应path 在缓存中的对应ChildListener ,最后调用子类方法removeTargetChildListener 移除具体节点监听器。removeTargetChildListener这个方法也是抽象方法。
protected abstract TargetChildListener createTargetChildListener(String path, ChildListener listener);
protected abstract List<String> addTargetChildListener(String path, TargetChildListener listener);
protected abstract void removeTargetChildListener(String path, TargetChildListener listener);
stateChanged 方法会通知stateListeners集合中所有StateListener状态发生了改变(就是调用循环内对应StateListener 对象的stateChanged方法)
// 状态改变的时候, 进行通知
protected void stateChanged(int state) {
for (StateListener sessionListener : getSessionListeners()) {
sessionListener.stateChanged(state);
}
}
4.4 CuratorZookeeperClient
CuratorZookeeperClient是对curator操作zk的封装,也是dubbo操作zk客户端默认选择,curator里面其实封装了好多东西,我曾用它做过分布式id,其中提供的api也很好用,是现在操作zk的主流。
public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorWatcher> {...}
CuratorZookeeperClient 继承AbstractZookeeperClient抽象类,然后它的节点监视器类型是CuratorWatcher。
看下构造方法实现:
public CuratorZookeeperClient(URL url) {
super(url);
try {
// 创建builder
int timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_CONNECTION_TIMEOUT_MS);
int sessionExpireMs = url.getParameter(ZK_SESSION_EXPIRE_KEY, DEFAULT_SESSION_TIMEOUT_MS);
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(url.getBackupAddress())
.retryPolicy(new RetryNTimes(1, 1000))
.connectionTimeoutMs(timeout)
.sessionTimeoutMs(sessionExpireMs);
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
builder = builder.authorization("digest", authority.getBytes());
}
// 创建client
client = builder.build();
// 添加状态监听器
client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
client.start();
boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
if (!connected) {
throw new IllegalStateException("zookeeper not connected");
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
在构造中,先是创建一个builder,设置了连接信息(ip+port),超时时间是5s,重试策略。接着又是权限信息的设置,然后通过builder的build方法获取client(这里使用了建造者模式),接着就是添加状态监视器,当状态变动的时候就会调用stateChanged方法,在stateChanged方法实现中,其实就是做了curator状态与dubbo zk 状态的转变,然后调用CuratorZookeeperClient的stateChanged方法进行状态变动通知,最后调用start方法启动。 接下来看下添加节点与删除节点操作:
// 创建永久节点
@Override
public void createPersistent(String path) {
try {
client.create().forPath(path);
} catch (NodeExistsException e) {
logger.warn("ZNode " + path + " already exists.", e);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
// 创建临时节点
@Override
public void createEphemeral(String path) {
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (NodeExistsException e) {
logger.warn("ZNode " + path + " already exists, since we will only try to recreate a node on a session expiration" +
", this duplication might be caused by a delete delay from the zk server, which means the old expired session" +
" may still holds this ZNode and the server just hasn't got time to do the deletion. In this case, " +
"we can just try to delete and create again.", e);
deletePath(path);
createEphemeral(path);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
@Override
protected void createPersistent(String path, String data) {
byte[] dataBytes = data.getBytes(CHARSET);
try {
client.create().forPath(path, dataBytes);
} catch (NodeExistsException e) {
try {
client.setData().forPath(path, dataBytes);
} catch (Exception e1) {
throw new IllegalStateException(e.getMessage(), e1);
}
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
@Override
protected void createEphemeral(String path, String data) {
byte[] dataBytes = data.getBytes(CHARSET);
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes);
} catch (NodeExistsException e) {
logger.warn("ZNode " + path + " already exists, since we will only try to recreate a node on a session expiration" +
", this duplication might be caused by a delete delay from the zk server, which means the old expired session" +
" may still holds this ZNode and the server just hasn't got time to do the deletion. In this case, " +
"we can just try to delete and create again.", e);
deletePath(path);
createEphemeral(path, data);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
// 删除节点
@Override
protected void deletePath(String path) {
try {
client.delete().deletingChildrenIfNeeded().forPath(path);
} catch (NoNodeException ignored) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
都是直接调用curator客户端的api操作。
接下来看下createTargetChildListener创建节点监听器方法实现:
@Override
public CuratorWatcher createTargetChildListener(String path, ChildListener listener) {
return new CuratorWatcherImpl(listener);
}
可以看到创建了CuratorWatcherImpl对象返回,这个CuratorWatcherImpl其实是CuratorWatcher的实现。
// 实现了CuratorWatcher接口
static class CuratorWatcherImpl implements CuratorWatcher {
private CuratorFramework client;
private volatile ChildListener childListener;
private String path;
public CuratorWatcherImpl(CuratorFramework client, ChildListener listener, String path) {
this.client = client;
this.childListener = listener;
this.path = path;
}
protected CuratorWatcherImpl() {
}
public void unwatch() {
this.childListener = null;
}
@Override
public void process(WatchedEvent event) throws Exception {
// if client connect or disconnect to server, zookeeper will queue
// watched event(Watcher.Event.EventType.None, .., path = null).
if (event.getType() == Watcher.Event.EventType.None) {
return;
}
// 获取当前监听器节点的子节点
if (childListener != null) {
childListener.childChanged(path, client.getChildren().usingWatcher(this).forPath(path));
}
}
}
当节点监听器监听的那个节点的子节点发生变化的时候,就会调用对应的process 方法,先是判断listener是否是null,获取监听的path,然后调用listener的childChanged方法进行变动通知。
再来看下addTargetChildListener添加节点监听器方法实现:
@Override
public List<String> addTargetChildListener(String path, CuratorWatcherImpl listener) {
try {
return client.getChildren().usingWatcher(listener).forPath(path);
} catch (NoNodeException e) {
return null;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
removeTargetChildListener移除节点监听器方法:
@Override
public void removeTargetChildListener(String path, CuratorWatcherImpl listener) {
listener.unwatch();
}
这里直接调用了节点监听器的unwatch方法,这个unwatch其实就是把ChildListener的链接制成null,这样变动的时候就不会调用ChildListener的childChanged方法进行通知了,而没有直接像zkClient那样remove
参考文章
Dubbo3.0源码注释github地址
深度剖析Apache Dubbo核心技术内幕
dubbo源码系列
dubbo源码分析专栏