dubbo zk订阅流程

86 阅读2分钟

dubbo zookeeper订阅

一般来说我们dubbo使用的注册中心一般都是zk为主。这次看一下具体的一些订阅相关的流程。首先来自org.apache.dubbo.registry.zookeeper.ZookeeperRegistry

public void doSubscribe(final URL url, final NotifyListener listener) {  
try {  
// 如果是代表*的。那么会订阅providers、 routers、 consumers、configurators这四种类型
if (ANY_VALUE.equals(url.getServiceInterface())) {  
String root = toRootPath();  
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());  
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> {  
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);  
}  
}  
});  
zkClient.create(root, false);  
List<String> services = zkClient.addChildListener(root, zkListener);  
if (CollectionUtils.isNotEmpty(services)) {  
for (String service : services) {  
service = URL.decode(service);  
anyServices.add(service);  
subscribe(url.setPath(service).addParameters(INTERFACE_KEY, service,  
Constants.CHECK_KEY, String.valueOf(false)), listener);  
}  
}  
//这里只订阅providers相关类型的
} else {  
CountDownLatch latch = new CountDownLatch(1);  
try {  
List<URL> urls = new ArrayList<>();  
for (String path : toCategoriesPath(url)) {  
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());  
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, k, latch));  
if (zkListener instanceof RegistryChildListenerImpl) {  
((RegistryChildListenerImpl) zkListener).setLatch(latch);  
}  
//在zk中创建对应的节点
zkClient.create(path, false);  
List<String> children = zkClient.addChildListener(path, zkListener);  
if (children != null) {  
urls.addAll(toUrlsWithEmpty(url, path, children));  
}  
}  
// 发送相关的同志
notify(url, listener, urls);  
} finally {  
// tells the listener to run only after the sync notification of main thread finishes.  
latch.countDown();  
}  
}  
} catch (Throwable e) {  
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);  
}  
}

继续看一下通知了什么东西。来到org.apache.dubbo.registry.support.AbstractRegistry

protected void notify(URL url, NotifyListener listener, List<URL> urls) {  
if (url == null) {  
throw new IllegalArgumentException("notify url == null");  
}  
if (listener == null) {  
throw new IllegalArgumentException("notify listener == null");  
}  
if ((CollectionUtils.isEmpty(urls))  
&& !ANY_VALUE.equals(url.getServiceInterface())) {  
logger.warn("Ignore empty notify urls for subscribe url " + url);  
return;  
}  
if (logger.isInfoEnabled()) {  
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);  
}  
// keep every provider's category.  
Map<String, List<URL>> result = new HashMap<>();  
for (URL u : urls) {  
if (UrlUtils.isMatch(url, u)) {  
String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);  
List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());  
categoryList.add(u);  
}  
}  
if (result.size() == 0) {  
return;  
}  
Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());  
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {  
String category = entry.getKey();  
List<URL> categoryList = entry.getValue();  
categoryNotified.put(category, categoryList);  
// 各种listen去通知
listener.notify(categoryList);  
// 保存url相关的配置。比如我们用户名下面的.dubbo文件里面的缓存
saveProperties(url);  
}  
}

看一下是怎么去保存我们的配置文件的

private void saveProperties(URL url) {  
if (file == null) {  
return;  
}  
  
try {  
StringBuilder buf = new StringBuilder();  
Map<String, List<URL>> categoryNotified = notified.get(url);  
if (categoryNotified != null) {  
for (List<URL> us : categoryNotified.values()) {  
for (URL u : us) {  
if (buf.length() > 0) {  
buf.append(URL_SEPARATOR);  
}  
buf.append(u.toFullString());  
}  
}  
}  
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);  
}  
}

最后就是看一下文件保存的逻辑

public void doSaveProperties(long version) {  
if (version < lastCacheChanged.get()) {  
return;  
}  
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");  
}  
// 开始保存文件
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);  
}  
    }