上一篇文章中,講到 Dubbo 初始化一個 代理對象時,會執 ReferenceConfig
的 init
方法,而後執行其
ref = createProxy(map);
而在這一步中,會進行以下幾步:
- 判斷是否是
Jvm
類型應用 - 組裝url
- 代理生成
invoker
- 最後 執行
(T) PROXY_FACTORY.getProxy(invoker);
返回type
類型對象。
Dubbo 中 Invoker
Invoker
是 Dubbo
的核心模型,代表一個可執行體。在服務提供方,Invoker
用於調用服務提供類。在服務消費方,Invoker
用於執行遠程調用。Invoker
是由 Protocol
實現類構建而來。Protocol
實現類有很多,它本身也是一個 SPI 類型。
上一篇文章中,分析了參數組裝,以及 Wrapper 類 和 SPI 組裝關係。
準備工作
這篇即重點分析 invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
。
結合上兩篇文章,由於 Protocol 沒有 默認的 Adaptive
類,所以將使用默認的 Protocol$Adaptive
類此處,有需要可以看這篇文章:
Dubbo 中默認的 Adaptive類生成過程及例子
由於 url是 registry
類型,所以最終得到的是 RegistryProtocl
,而裏面和Wrapper 類組裝而成的結構爲:
ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper->RegistryProtocol
起始最終 init 中 PROXY_FACTORY
類型爲 :ProtocolFilterWrapper
,有前面的分析,包裝類其實是鏈式的組裝結構,即最終會依次調用:
ProtocolFilterWrapper
、ProtocolListenerWrapper
、QosProtocolWrapper
的 refer
方法,而由代碼可知,當url
的協議爲 registry
時候,則默認會跳過而執行下一個 Protocol
的refer
。這樣一來,依次執行玩前三個Wrapper
之後,就會執行 RegistryProtocol
的 refer
。
本文將重點分析RegistryProtocol
的 refer,其中會包括Dubbo 集羣容錯的一些相關內容介紹。
RegistryProtocol 的 refer
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 拼湊 url
url = URLBuilder.from(url)
.setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
.removeParameter(REGISTRY_KEY)
.build();
// 獲取註冊中心
Registry registry = registryFactory.getRegistry(url);
// 如果類型就是 `RegistryService` 類型
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
String group = qs.get(GROUP_KEY);
if (group != null && group.length() > 0) {
if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
// 執行 doRefer
return doRefer(cluster, registry, type, url);
}
上文代碼中,主要做了以下幾步:
- 拼湊url
- 判斷是否爲
RegistryService
,是得化說明不是具體接口類型,所以直接返回 - 如果有group 參數的,則使用
MergeableCluster
構建invoker
,否則使用默認的FailoverCluster
構建。
Registry registry = registryFactory.getRegistry(url);
這是 AbstractRegistryFactory
中方法,是根據url獲取 Registry 過程,Registry
也沒有默認的 Adaptive 類,所以將使用默認的 Adaptive 類,而url在 Registry$Adatpive
被換爲了 zookeeper
協議, 而後會執行 Registry registry = REGISTRIES.get(key);
從緩存中獲取 Regisry
,否則會通過url新建一個。
@Override
public Registry getRegistry(URL url) {
// 重新設置path
url = URLBuilder.from(url)
.setPath(RegistryService.class.getName())
.addParameter(INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(EXPORT_KEY, REFER_KEY)
.build();
// 由url獲取key
String key = url.toServiceStringWithoutResolving();
// Lock the registry access process to ensure a single instance of the registry
LOCK.lock();
try {
// 嘗試從緩存中獲取
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
// 通過 SPI 或者 IOC 創建create registry by spi/ioc
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();
}
}
以上代碼做了如下幾件事:
- 將url中path設置爲
org.apache.dubbo.registry.RegistryService
- 嘗試獲取key的緩存,key的格式爲
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService
,這也就說明,不同的接口,可以有不同的註冊中心。 - 如果無法找到緩存,則執行 子類具體實現的
createRegistry
新建
ZookeeperRegistyFactory
工廠模式的應用,則是通過 createRegistry
新創鍵一個 ZookeeperRegistry
並返回:
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
可以看下面 ZookeeperRegistry 章節分析。
4. 判斷是否 有配置 group
,如果有則使用 MergeableCluster
,否則將使用默認的Cluster$Adaptive
即 FailoverCluster
去調用 子類具體實現的 doRefer
方法
可以看下面 doRefer 小節具體分析
ZookeeperRegistry
那麼 這個 ZookeeperRegistry 做了什麼事呢?
- ZookeeperRegistry 構造方法:
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
// 執行父類構造方法
super(url);
// 判斷是否爲 0.0.0.0 的host
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(PATH_SEPARATOR)) {
group = PATH_SEPARATOR + group;
}
this.root = group;
zkClient = zookeeperTransporter.connect(url);
zkClient.addStateListener(state -> {
if (state == StateListener.RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
});
}
- FailbackRegistry 構造方法:
public FailbackRegistry(URL url) {
super(url);
this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD);
// since the retry task will not be very much. 128 ticks is enough.
retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer", true), retryPeriod, TimeUnit.MILLISECONDS, 128);
}
- AbstractRegistry 構造方法:
public AbstractRegistry(URL url) {
// 設置url
setUrl(url);
// 嘗試從緩存文件中獲取配置
// Start file save timer
syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
String filename = url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(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 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.
// 嘗試從緩存文件中獲取配置
loadProperties();
// 告知 機制
notify(url.getBackupUrls());
}
以上列舉了 ZookeeperRegistry 的構造方法,以及繼承結構中父類 FailbackRegistry
和 AbstractRegistry
的構造方法。
從後往前講:
- 在 AbstractRegistry中,首先是檢查了是否有緩存文件配置,有則讀取出來,文件位置在
~/.dubbo
下,名字格式爲:
url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
例如以下:/home/anla7856/.dubbo/dubbo-registry-dubbo-consumer-127.0.0.1:2181.cache
。 而後則第一次通知了 其他消息監聽的去配置下 url 的backupUrl
,即Zookeeper集羣的其他配置地址。 FailbackRegistry
是ZookeeperRegistry
的父類,裏面主要封裝和記錄了一些失敗的通知或者訂閱信息,在裏面開啓了一個 定時器HashedWheelTimer
,用於失敗重試,而重試周期如果沒有指明,則爲5000ms。- 正題就是
ZookeeperRegistry
中的構造方法,除了調用父類的構造方法,它還負責連接Zookeeper
,即內部使用了 一個Dubbo 封裝的ZookeeperClient
和ZookeeperTransporter
用於操作Zookeeper
。由於可能有多種ZookeeperTransporter
類型,所以事實上它也是 SPI 類型。
最後,監聽Zookeeper
的狀態變更消息,從而執行recover
方法。recover
方法則主要是當需要重連時候,重新嘗試連接以及刷新已獲取的配置。
當獲取完 Registry
後,將其放入 REGISTRIES
中緩存起來,並最終返回該 registry
。
doRefer
上面的 獲取 registry
方法 是 僅僅是準備工作,即初始化了 相關的 Registry
、Cluster
。而在RegistryProtocol
各自的doRefer
方法 則會進行具體一些設定,例如Directory
配置,訂閱配置,註冊監聽器等。
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 創建一個 RegistryDirectory
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// 設置 Registry 和 protocol
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
// 從新構造訂閱的URL
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(subscribeUrl);
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
上面貼出了 RegistryProtocol
的 doRefer
方法,有以下幾個邏輯點:
- 創建一個
RegistryDirectory
,即服務目錄,服務目錄中存儲了一些和服務提供者有關的信息,通過服務目錄,服務消費者可獲取到服務提供者的信息,比如 ip、端口、服務協議等。通過這些信息,服務消費者就可通過 Netty 等客戶端進行遠程調用。而後設置Registry
、Protocol
、RegistriesConsumerUrl
。 - 而後,調用
registry.register(directory.getRegisteredConsumerUrl());
去註冊 這個 registeredConsumerUrl,默認使用Zookeeper
作爲註冊中心進行服務註冊和管理。由ZookeeperRegistry
->FailbackRegistry
->AbstractRegistry
依次初始化並調用。最終,執行ZookeeperRegistry
的doRegistry
語句在zk上面增加一個字節點:
@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);
}
}
- 執行
directory.buildRouterChain(subscribeUrl);
初始化 路由鏈 的初始化,具體路由鏈將由後面文章介紹。 - 執行
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
去訂閱監聽。
具體訂閱過程看下一篇詳細分析。 Invoker invoker = cluster.join(directory);
使用默認的Cluster$Adatpive
,創建一個Invoker 並返回,並且緩存入ProviderConsumerRegTable
中。
看下 Cluster
的SPI 文件:
mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
failback=org.apache.dubbo.rpc.cluster.support.FailbackCluster
forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
registryaware=org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster
裏面有個 Wrapper 類,所以Cluster 的返回必然是 Wrapper
-> Cluster
模式,具體可以看這篇文章分析:
SPI模式中 Wrapper和 SPI 類組裝邏輯。
再看看 MockClusterWrapper
類:
public class MockClusterWrapper implements Cluster {
private Cluster cluster;
public MockClusterWrapper(Cluster cluster) {
this.cluster = cluster;
}
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
}
裏面join 方法僅僅返回 一個 MockClusterInvoker
。
所以從代碼中可以推斷出 ,最終在 ReferenceConfig
返回必將是一個 MockClusterInvoker
。
由打斷點而獲取到的invoker 內容如下所示:
而最終再通過 return (T) PROXY_FACTORY.getProxy(invoker);
返回一個代理類。
所以另一方面,當我們寫代碼時候,直接端點進入,是無法直接進入 type
的 實現類,而是進入MockClusterInvoker
。
那具體訂閱過程是如何的呢?代理工廠如何生成代理類呢?
這些問題,且看下回分解,感謝閱讀。
覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路: