文章目錄
本文主要參考自Dubbo官方文檔、Dubbo項目源碼以及網絡文章和相關書籍,並附上自身的一些理解,如有遺漏或錯誤,還望海涵並指出。謝謝!
------本文基於Dubbo-2.6.1版本
一、Zookeeper在Dubbo中的作用
Dubbo源碼(3)-基礎抽象註冊中心源碼解析已經講了註冊中心抽象API的實現。
可以看到FailbackRegistry
基於AbstractRegistry
已經實現了失敗重試的邏輯,最後和註冊中心交互、寫入、刪除、查詢的邏輯都使用模板方法模式將指定的方法下放給具體註冊中心實現類來完成,這也是本章要說的重點。本章是對Zookeeper註冊中心的解析,當然了,整個Dubbo註冊中心體系下共有4個註冊中心:
1.1、什麼是Zookeeper
首先簡單介紹一下,什麼是Zookeeper?
我認爲Zookeeper可以理解爲一個分佈式的數據管理系統,每個zk都採用類似於Linux的樹狀結構來管理文件,基於此可以構建出類似於服務註冊、狀態管理、集羣管理等相關的應用來。
爲什麼選擇zk作爲註冊中心呢,我認爲有以下好處:
- 集羣化配置方便,具備良好高可用性
- 根據ZAB算法做Leader選舉,實時性高
- 整個zk集羣能保證對外數據一致性
- 整個zk集羣能保證數據的集羣原子性
當然了,也可以使用Redis作爲註冊中心,並且Redis能夠做到更快、讀寫效率更高;以CAP模型來看,Redis保證的是AP模型,也就是先保證可用性,但不保證數據的強一致性(只保證最終一致性),更加適合於作高速讀寫緩存;而zk保證的是CP模型,也就是先保證一致性,網絡分區發生時有可能不對外提供服務。
具體選擇Redis還是zk作爲註冊中心,還是要看具體情境,不過大部分情況下選擇zk都是合適的。
1.2、Dubbo的Zookeeper註冊中心
-
若Provider提供了
com.foo.BarService
這個接口的服務,那麼在Provider啓動時,會向 zk的/dubbo/com.foo.BarService/providers
目錄下寫入接口暴露出的URL地址10.20.153.10:20880
-
當Consumer啓動時,會訂閱zk的
/dubbo/com.foo.BarService/providers
目錄下的Provider暴露的URL地址。並向/dubbo/com.foo.BarService/consumers
目錄下寫入自己的URL地址 -
當Monitor啓動時,會訂閱zk
/dubbo/com.foo.BarService
目錄下的所有Provider和Consumer的URL地址。
以上就是以zk作爲註冊中心的使用,具體的使用細節以及配置參考官方文檔:
https://dubbo.apache.org/zh-cn/docs/user/references/registry/zookeeper.html
二、Zookeeper註冊中心源碼解析
現在就是Zookeeper註冊中心的源碼解析環節了,先來看看大致的情況:
Zookeeper註冊中心的實現位於dubbo-registry
模塊下的dubbo-registry-zookeeper
子模塊:
查看SPI文件,可知zookeeper
是RegistryFactory
的默認拓展實現:
zookeeper=com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory
代碼量統計:
2.1、FailbackRegistry
這裏就不貼具體代碼了,主要是想提一下這裏的模板方法模式:
public void register(URL url) {
// ...
try {
// !!doXxxx方法交由子類實現!!
doRegister(url);
} catch (Exception e) {
// ...
}
}
2.2、ZookeeperRegistryFactory
com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory
實現了AbstractRegistryFactory
抽象類,是ZookeeperRegistry
的工廠。
- ZookeeperTransporter
// 默認以curator作爲zk客戶端
@SPI("curator")
public interface ZookeeperTransporter {
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
ZookeeperClient connect(URL url);
}
- ZookeeperRegistryFactory
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
// zk客戶端
private ZookeeperTransporter zookeeperTransporter;
// 設置zk客戶端
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
// 創建ZookeeperRegistry
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}
2.3、ZookeeperRegistry
接下來就是本章的重點解析對象了,com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry
也就是真正的zk註冊中心實現類,繼承於FailbackRegistry
。從屬性及構造方法、重點方法這兩個維度來看源碼:
2.3.1、屬性及構造方法
// logger
private final static Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);
// 默認zk端口號
private final static int DEFAULT_ZOOKEEPER_PORT = 2181;
// 默認zk根節點
private final static String DEFAULT_ROOT = "dubbo";
// zk根節點
private final String root;
// Service接口全名集合
private final Set<String> anyServices = new ConcurrentHashSet<String>();
//監聽器集合
private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners = new ConcurrentHashMap<URL, ConcurrentMap<NotifyListener, ChildListener>>();
// zk客戶端
private final ZookeeperClient zkClient;
/*
* URL:當前Registry的URL
* ZookeeperTransporter:指定zk客戶端(默認爲curator)
*/
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
// 獲取到zk根結點
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(Constants.PATH_SEPARATOR)) {
// 設置爲自定義zk根結點
group = Constants.PATH_SEPARATOR + group;
}
// 設置爲自定義zk根結點
this.root = group;
// 連接到指定的zk
zkClient = zookeeperTransporter.connect(url);
zkClient.addStateListener(new StateListener() {
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
// 斷線重連zk時恢復自身數據
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}
2.3.2、重點方法
2.3.2.1、doRegister
- doRegister
/*
* 註冊url到zk中
*/
protected void doRegister(URL url) {
try {
// 創建Root+Service+Type+Url的zk節點
// toUrlPath:獲取到Root + Service + Type + URL
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
- toUrlPath
/*
* 獲取到Root + Service + Type + URL
*/
private String toUrlPath(URL url) {
return toCategoryPath(url) + Constants.PATH_SEPARATOR + URL.encode(url.toFullString());
}
- toCategoryPath
/*
* 獲取到Root + Service + Type
*/
private String toCategoryPath(URL url) {
return toServicePath(url) + Constants.PATH_SEPARATOR + url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
}
- toServicePath
/*
* 獲取到Root + Service
*/
private String toServicePath(URL url) {
String name = url.getServiceInterface();
if (Constants.ANY_VALUE.equals(name)) {
return toRootPath();
}
return toRootDir() + URL.encode(name);
}
- toRootDir
/*
* 獲取到Root
*/
private String toRootDir() {
if (root.equals(Constants.PATH_SEPARATOR)) {
return root;
}
return root + Constants.PATH_SEPARATOR;
}
private String toRootPath() {
return root;
}
2.3.2.2、doUnregister
/*
* 取消註冊,刪除該Root+Service+Type+Url的zk節點
*/
protected 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);
}
}
2.3.2.3、doSubscribe
@Override
protected void doSubscribe(final URL url, final NotifyListener listener) {
try {
// 處理所有Service層的發起訂閱,例如監控中心的訂閱
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
String root = toRootPath();
// 獲得url對應的監聽器集合
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) { // 不存在,進行創建
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);// 獲得 ChildListener ChildListener zkListener = listeners.get(listener)if (zkListener == null) { // 不存在ChildListener 對象,進行創建ChildListener對象
listeners.putIfAbsent(listener, new ChildListener() public void childChanged(String parentPath, List<String> currentChilds) { for (String child : currentChilds) {
child = URL.decode(child);
if (!anyServices.contains(child)) {
anyServices.add(child);
subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
}
});
zkListener = listeners.get(listener);
}
// 創建 Service 節點。該節點爲持久節點。
zkClient.create(root, false);
// 向 Zookeeper ,Service 節點,發起訂閱
List<String> services = zkClient.addChildListener(root, zkListener);
// 首次全量數據獲取完成時,循環 Service 接口全名數組,發起該 Service 層的訂閱
if (services != null && !services.isEmpty()) {
for (String service : services) {
service = URL.decode(service);
anyServices.add(service);
subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
// 處理指定 Service 層的發起訂閱,例如服務消費者的訂閱
} else {
// 子節點數據數組
List<URL> urls = new ArrayList<URL>();
// 循環分類數組
for (String path : toCategoriesPath(url)) {
// 獲得 url 對應的監聽器集合
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) { // 不存在,進行創建
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
// 獲得ChildListener對象
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) { // 不存在 ChildListener 對象,進行創建ChildListener對象
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List<String> currentChilds) {
// 出現變更,回調NotifyListener
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
zkListener = listeners.get(listener);
}
// 創建Type節點。該節點爲持久節點。
zkClient.create(path, false);
// 向Zookeeper ,PATH節點,發起訂閱
List<String> children = zkClient.addChildListener(path, zkListener);
// 添加到urls 中
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
// 首次全量數據獲取完成時,調用notify方法,回調 NotifyListener
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
2.3.2.4、doUnsubscribe
@Override
protected void doUnsubscribe(URL url, NotifyListener listener) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners != null) {
ChildListener zkListener = listeners.get(listener);
if (zkListener != null) {
// 通知zk ,移除訂閱
zkClient.removeChildListener(toUrlPath(url), zkListener);
}
}
}