org.apache.dubbo 服務消費原理源碼分析:
本文主要針對 dubbo-spring-boot-starter 2.7.7版本, 對應的 org.apache.dubbo 2.7.7 版本的源碼。
本文主要從以下幾個點來分析:
- 服務消費的入口。
- 構建遠程服務的代理。
- RegistryDirectory 目錄服務獲取目標服務的url地址。
- 客戶端服務通信的建立。
- 接口調用流程。
服務消費的入口:
Dubbo 提供了一個自動配置類 DubboAutoConfiguration,其中除了會自動注入服務端的註解處理器 ServiceAnnotationBeanPostProcessor 之外,還會創建一個客戶端的註解處理器 ReferenceAnnotationBeanPostProcessor。
ReferenceAnnotationBeanPostProcessor 重寫了 doGetInjectedBean 方法,在依賴注入的時候會調用該方法進行依賴注入,而我們知道。在使用@DubboReference 的時候,這個類一定是個動態代理對象。我們先來看一下這個代碼:
@Override
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
/**
* The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
*/
String referencedBeanName = buildReferencedBeanName(attributes, injectedType);
/**
* The name of bean that is declared by {@link Reference @Reference} annotation injection
*/
String referenceBeanName = getReferenceBeanName(attributes, injectedType);
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);
// 註冊一個ReferenceBean到Spring IOC容器中
registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);
cacheInjectedReferenceBean(referenceBean, injectedElement);
//調用 getOrCreateProxy 返回一個動態代理對象
return getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);
}
最終這裏會調用 ReferenceConfig#get 來獲取該代理對象:
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}
開始調用init方法進行ref也就是代理對象的初始化動作
- 檢查配置信息
- 根據dubbo配置,構建map集合。
- 調用 createProxy 創建動態代理對象
public synchronized void init() {
if (initialized) {//如果已經初始化,則直接返回
return;
}
if (bootstrap == null) {
bootstrap = DubboBootstrap.getInstance();
bootstrap.init();
}
//檢查配置
checkAndUpdateSubConfigs();
//檢查本地存根 local與stub
checkStubAndLocal(interfaceClass);
ConfigValidationUtils.checkMock(interfaceClass, this);
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, CONSUMER_SIDE);
//添加運行時參數
ReferenceConfigBase.appendRuntimeParameters(map);
if (!ProtocolUtils.isGeneric(generic)) {//檢查是否爲泛化接口
//獲取版本信息
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
//獲取接口方法列表,添加到map中
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
}
}
//通過class加載配置信息
map.put(INTERFACE_KEY, interfaceName);
AbstractConfig.appendParameters(map, getMetrics());
AbstractConfig.appendParameters(map, getApplication());
AbstractConfig.appendParameters(map, getModule());
// remove 'default.' prefix for configs from ConsumerConfig
// appendParameters(map, consumer, Constants.DEFAULT_KEY);
AbstractConfig.appendParameters(map, consumer);
AbstractConfig.appendParameters(map, this);
//將元數據配置信息放入到map中
MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
if (metadataReportConfig != null && metadataReportConfig.isValid()) {
map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
}
//遍歷methodConfig,組裝method參數信息
Map<String, AsyncMethodInfo> attributes = null;
if (CollectionUtils.isNotEmpty(getMethods())) {
attributes = new HashMap<>();
for (MethodConfig methodConfig : getMethods()) {
AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName());
String retryKey = methodConfig.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(methodConfig.getName() + ".retries", "0");
}
}
AsyncMethodInfo asyncMethodInfo = AbstractConfig.convertMethodConfig2AsyncInfo(methodConfig);
if (asyncMethodInfo != null) {
// consumerModel.getMethodModel(methodConfig.getName()).addAttribute(ASYNC_KEY, asyncMethodInfo);
attributes.put(methodConfig.getName(), asyncMethodInfo);
}
}
}
//獲取服務消費者ip地址
String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
if (StringUtils.isEmpty(hostToRegistry)) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(REGISTER_IP_KEY, hostToRegistry);
serviceMetadata.getAttachments().putAll(map);
ref = createProxy(map);
serviceMetadata.setTarget(ref);
serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);
ConsumerModel consumerModel = repository.lookupReferredService(serviceMetadata.getServiceKey());
consumerModel.setProxyObject(ref);
consumerModel.init(attributes);
initialized = true;
// dispatch a ReferenceConfigInitializedEvent since 2.7.4
dispatch(new ReferenceConfigInitializedEvent(this, invoker));
}
ReferenceConfig#createProxy:首先我們需要注意一個點,這裏是創建一個代理對象,而這個代理對象應該也和協議有關係,也就是不同的協議,使用的代理對象也應該不一樣
private T createProxy(Map<String, String> map) {
if (shouldJvmRefer(map)) {
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
invoker = REF_PROTOCOL.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
urls.clear();
// 我們在註解上配置的uel信息,表示點對點通信方式
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (StringUtils.isEmpty(url.getPath())) {
url = url.setPath(interfaceName);
}
if (UrlUtils.isRegistry(url)) {
urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration
// if protocols not injvm checkRegistry
//檢查協議是否是injvm
if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
checkRegistry();//檢查註冊中心配置
//獲取註冊中心列表,由於可以配置多個註冊中心,所以這裏返回所有配置的註冊中心信息
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {//遍歷註冊中心列表,添加到urls中
for (URL u : us) {
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
if (monitorUrl != null) {
map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version "
+ Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
}
//如果urls=1,表示只配置了一個註冊中心,則直接取一個。
if (urls.size() == 1) {
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
//否則,它會把多個註冊中心的協議都分別構建一個invoker。 這就是服務提供者的服務列表
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (UrlUtils.isRegistry(url)) {
registryURL = url; // use last registry url
}
}
//如果registryURL不爲空
//則通過CLUSTER.join把invokers以靜態的Directory形式構建一個invoker對象。
//目的是實現註冊中心的路由
if (registryURL != null) { // registry url is available
// for multi-subscription scenario, use 'zone-aware' policy by default
URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
// The invoker wrap relation would be like: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke. 由於我們註冊中心是寫死在配置文件的,所以採用靜態的 Directory
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
}
if (shouldCheck() && !invoker.isAvailable()) {
invoker.destroy();
throw new IllegalStateException("Failed to check the status of the service "
+ interfaceName
+ ". No provider available for the service "
+ (group == null ? "" : group + "/")
+ interfaceName +
(version == null ? "" : ":" + version)
+ " from the url "
+ invoker.getUrl()
+ " to the consumer "
+ NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
/**
* @since 2.7.0
* ServiceData Store
*/
String metadata = map.get(METADATA_KEY);
WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
if (metadataService != null) {
URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
metadataService.publishServiceDefinition(consumerURL);
}
// create service proxy
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}
- 首先檢查是不是本地調用,如果是,則使用injvm protocol的refer方法產生一個InjvmInvoker實例
- 否則,再判斷是否是點對點通信,也就是url參數不爲空,而url參數又可以配置多個,所以這裏需要遍歷
- 遍歷所有註冊中心的url,針對每個註冊中心協議構建invoker,並保存到invokers中
- 通過Cluster的StaticDirectory把多個靜態註冊中心組件成一個集羣。
在上面這個方法中,有兩個核心的代碼需要關注,分別是。
- REF_PROTOCOL.refer, 這個是生成invoker對象,之前我們說過,它是一個調用器,是dubbo中比較重要的領域對象,它在這裏承擔這服務調用的核心邏輯.
- PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic)), 構建一個代理對象,代理客戶端的請求。
我們先來分析refer方法。REF_PROTOCOL是一個自適應擴展點,現在我們看到這個代碼,應該是比較熟悉了。它會生成一個Protocol$Adaptive的類,然後根據refer傳遞的的url參數來決定當前路由到哪個具體的協議處理器。
前面我們分析服務發佈的時候,已經說過了這個過程,不同的是這裏會經過Wrapper 進行包裝。先直接進入到RegistryProtocol.refer中
RegistryProtocol這個類我們已經很熟悉了,服務註冊和服務啓動都是在這個類裏面觸發的。現在我們又通過這個方法來獲得一個inovker對象,那我們繼續去分析refer裏面做了什麼事情。
- 組裝註冊中心協議的url
- 判斷是否配置了group,如果有,則cluster=getMergeableCluster(),構建invoker
- doRefer構建invoker
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//獲得註冊中心的url地址,此時,這裏得到的是zookeeper://
url = getRegistryUrl(url);
//這塊跟服務發佈的時候是一樣的,返回一個 ZookeeperRegistry
Registry registry = registryFactory.getRegistry(url);
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);
}
}
return doRefer(cluster, registry, type, url);
}
doRefer方法創建一個RegistryDirectory實例,然後生成服務者消費者連接,並向註冊中心進行註冊。註冊完畢後,緊接着訂閱providers、configurators、roters等節點下的數據。完成訂閱後,RegistryDirectory會收到到這幾個節點下的子節點信息。由於一個服務可能部署在多臺服務器上,這樣就會在providers產生多個節點,這個時候就需要Cluster將多個服務節點合併爲一個,並生成一個invoker。
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//初始化RegistryDirectory
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
//註冊consumer://協議url
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (directory.isShouldRegister()) {
//註冊服務消費者的url地址
directory.setRegisteredConsumerUrl(subscribeUrl);
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(subscribeUrl);
//進行訂閱
//subscribe訂閱信息消費url、通知監聽、配置監聽、訂閱url
//toSubscribeUrl:訂閱信息:category、providers、configurators、routers
directory.subscribe(toSubscribeUrl(subscribeUrl));
//一個註冊中心會存在多個服務提供者,所以在這裏需要把多個服務提供者通過cluster.join合併成一個
Invoker<T> invoker = cluster.join(directory);
List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
if (CollectionUtils.isEmpty(listeners)) {
return invoker;
}
//通過RegistryInvokerWrapper進行包裝
RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
for (RegistryProtocolListener listener : listeners) {
listener.onRefer(this, registryInvokerWrapper);
}
return registryInvokerWrapper;
}
doRefer到底做了什麼?只是初始化了一個RegistryDirectory,然後通過 Cluster.join 來返回一個Invoker對象?如果我們只需要關注核心流程,那麼首先我們來看一下Cluster是什麼?我們只關注一下Invoker這個代理類的創建過程,其他的暫且不關心
cluster其實是在RegistryProtocol中通過set方法完成依賴注入的,Cluster是一個被依賴注入的自適應擴展點,注入的對象實例是一個Cluster$Adaptive的動態適配器類。並且,它還是一個被包裝的
在動態適配的類中會基於extName,選擇一個合適的擴展點進行適配,由於默認情況下cluster:failover,所以getExtension("failover")理論上應該返回FailOverCluster。但實際上,這裏做了包裝MockClusterWrapper(FailOverCluster)
所以再回到doRefer方法,下面這段代碼, 實際是調用MockClusterWrapper(FailOverCluster.join),先走到 MockClusterWrapper#join
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
再調用AbstractCluster中的join方法,doJoin返回的是FailoverClusterInvoker。
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
}
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<>(directory);
}
buildClusterInterceptors:從名字可以看出,這裏是構建一個Cluster的攔截器。
private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {
AbstractClusterInvoker<T> last = clusterInvoker;
//通過激活擴展點來獲得ClusterInterceptor集合. 如果沒有配置激活參數,默認會有一個ConsumerContextClusterInterceptor攔截器.@Adaptive 註解在類上
List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);
//遍歷攔截器集合,構建一個攔截器鏈.
if (!interceptors.isEmpty()) {
for (int i = interceptors.size() - 1; i >= 0; i--) {
final ClusterInterceptor interceptor = interceptors.get(i);
final AbstractClusterInvoker<T> next = last;
last = new InterceptorInvokerNode<>(clusterInvoker, interceptor, next);
}
}
//最終返回的last= InterceptorInvokerNode
// ConsumerContextClusterInterceptor -next- FailoverClusterInvoker
return last;
}
因此 Cluster.join,實際上是獲得一個Invoker對象,這個Invoker實現了Directory的包裝成了 MockClusterInvoker(AbstractCluster$InterceptorInvokerNode(FailoverClusterInvoker)) ,並且配置了攔截器。至於它是幹嘛的,我們後續再分析。
構建遠程服務的代理:
返回了Invoker之後,再回到createProxy這段代碼中,這裏最終會調用一個getProxy來返回一個動態代理對象。並且這裏把invoker作爲參數傳遞進去,上面我們知道invoker這個對象的構建過程,實際上封裝了一個directory在invoker中。
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
而這裏的proxyFactory是一個自適應擴展點,它會根據url中攜帶的proxy參數來決定選擇哪種動態代理技術來構建動態代理對象,默認是javassist
JavassistProxyFactory#getProxy:通過這個方法生成了一個動態代理類,並且對invoker再做了一層處理,InvokerInvocationHandler。意味着後續發起服務調用的時候,會由InvokerInvocationHandler來進行處理。
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
至此,我們在分析服務消費者的動態代理的構建過程中可以發現,雖然代碼非常非常的多,但是本質上就做了兩件事情。
- 構建一個RegistryDirectory對象
- 生成遠程服務的代理
那麼這裏我們又要思考了,服務地址的獲取呢? 遠程通信的建立呢?如果這些步驟沒有完成,因此我們又得往前面翻代碼,去看看動態代理的構建過程中,是不是還有什麼地方存在遺漏的呢?
RegistryDirectory 目錄服務獲取目標服務的url地址:
在RegistryProtocol中,調用doRefer這個方法。這個doRefer方法中,構建了一個RegistryDirectory,它的作用很重要,它負責動態維護服務提供者列表。
RegistryDirectory是Dubbo中的服務目錄,從名字上來看,也比較容易理解,服務目錄中存儲了一些和服務提供者有關的信息,通過服務目錄,服務消費者可獲取到服務提供者的信息,比如 ip、端口、服務協議等。通過這些信息,服務消費者就可通過 Netty 等客戶端進行遠程調用。以下是它的類關係圖.
從這個圖上不難發現,Dubbo提供了兩種服務目錄,一種是靜態的,另一種是基於註冊中心的。基於註冊中心,那我們是不是清楚,註冊中心會有一個動態更新服務列表的機制,因此RegistryDirectory實現了NotifyListener這個接口,也就是當服務地址發生變化時,RegistryDirectory中維護的地址列表需要及時變更。
所以,再回憶一下之前我們通過Cluster.join去構建Invoker時,傳遞了一個directory進去,是不是也能夠理解爲什麼這麼做了呢?因爲Invoker是一個調用器,在發起遠程調用時,必然需要從directory中去拿到所有的服務提供者列表,然後再通過負載均衡機制來發起請求。
訂閱註冊中心指定節點的變化,如果發生變化,則通知到RegistryDirectory。Directory其實和服務的註冊以及服務的發現有非常大的關聯.
public void subscribe(URL url) {
setConsumerUrl(url);
//把當前RegistryDirectory作爲listener,去監聽zk上節點的變化CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
registry.subscribe(url, this);//訂閱 -> 這裏的registry是zookeeperRegsitry
}
此時,registry我們知道,它是ListenerRegistryWrapper(ZookeeperRegsistry)對象,我們先不管包裝類,直接進入到ZookeeperRegistry這個類中,但是該類中未實現,所以直接進入其父類 FailbackRegistry#subscribe
其中入參, listener爲RegistryDirectory,後續要用到 failbackRegistry這個類,從名字就可以看出,它的主要作用就是實現具有故障恢復功能的服務訂閱機制,簡單來說就是如果在訂閱服務註冊中心時出現異常,會觸發重試機制。
public void subscribe(URL url, NotifyListener listener) {
super.subscribe(url, listener);
//移除失效的listener,調用doSubscribe進行訂閱
removeFailedSubscribed(url, listener);
try {
// Sending a subscription request to the server side
// 發送訂閱請求到服務端
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);
}
}
ZookeeperRegistry.doSubscribe 這個方法是訂閱,邏輯實現比較多,可以分兩段來看,這裏的實現把所有Service層發起的訂閱以及指定的Service層發起的訂閱分開處理。所有Service層類似於監控中心發起的訂閱。指定的Service層發起的訂閱可以看作是服務消費者的訂閱。我們只需要關心指定service層發起的訂閱即可
public void doSubscribe(final URL url, final NotifyListener listener) {
try {
//針對所有service層發起的定於
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);
}
}
} else {
//針對指定的服務地址發起訂閱
List<URL> urls = new ArrayList<>();
//toCategoriesPath 會返回三個結果,分別是/providers、/configurations、/routers。 也就是服務啓動時需要監聽這三個節點下的數據變化
for (String path : toCategoriesPath(url)) {
//構建一個listeners集合,其中key是NotifyListener,其中前面我們知道RegistryDirectory實現了這個接口,所以這裏的key應該是RegistryDirectory
//value表示針對這個RegistryDirectory註冊的子節點監聽。
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
ChildListener zkListener = listeners.computeIfAbsent(listener, k -> (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, k, toUrlsWithEmpty(url, parentPath, currentChilds)));
zkClient.create(path, false);//創建一個/providers or/configurators、/routers節點
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
//調用notify方法觸發監聽
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
調用FailbackRegistry.notify, 對參數進行判斷。 然後調用AbstractRegistry.notify方法。這裏面會針對每一個category,調用listener.notify進行通知,然後更新本地的緩存文件
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) {//urls表示三個empty://協議的地址
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);
listener.notify(categoryList);//通過listener監聽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.
//保存到本地文件中,作爲服務地址的緩存信息
saveProperties(url);
}
}
上述代碼中,我們重點關注 listener.notify ,它會觸發一個事件通知,消費端的listener是最開始傳遞過來的RegistryDirectory,所以這裏會觸發RegistryDirectory.notify
public synchronized void notify(List<URL> urls) {
//對數據進行過濾
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull)
.filter(this::isValidCategory)
.filter(this::isNotCompatibleFor26x)
.collect(Collectors.groupingBy(this::judgeCategory));
//假設當前進來的通知是 providers節點
//判斷configurator是否爲空,這個節點下的配置,是在dubbo-admin控制檯上修改配置時,會先創建一個配置節點到這個路徑下,註冊中心收到這個變化時會通知服務消費者,服務消費者會根據新的配置重新構建Invoker
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
//判斷路由規則配置是否爲空,如果不爲空,同樣將路由規則添加到url中。
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
toRouters(routerURLs).ifPresent(this::addRouters);
// providers
// providers 得到服務提供者的地址列表
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
/**
* 3.x added for extend URL address
*/
ExtensionLoader<AddressListener> addressListenerExtensionLoader = ExtensionLoader.getExtensionLoader(AddressListener.class);
//獲得一個地址監聽的激活擴展點。 這裏目前應該還沒有任何實現,可以先不用管
List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
if (supportedListeners != null && !supportedListeners.isEmpty()) {
for (AddressListener addressListener : supportedListeners) {
providerURLs = addressListener.notify(providerURLs, getConsumerUrl(),this);
}
}
refreshOverrideAndInvoker(providerURLs);
}
refreshOverrideAndInvoker :逐個調用註冊中心裏面的配置,覆蓋原來的url,組成最新的url 放入overrideDirectoryUrl 存儲,此時我們沒有在dubbo-admin中修改任何配置,所以這裏沒必要去分析
根據 provider urls,重新刷新Invoker。直接進入 RegistryDirectory#refreshInvoker從名字可以看到,這裏是刷新invoker。怎麼理解呢?當註冊中心的服務地址發生變化時,會觸發更新。而更新之後並不是直接把url地址存儲到內存,而是把url轉化爲invoker進行存儲,這個invoker是作爲通信的調用器來構建的領域對象,所以如果地址發生變化,那麼需要把老的invoker銷燬,然後用心的invoker替代。
private void refreshInvoker(List<URL> invokerUrls) {
Assert.notNull(invokerUrls, "invokerUrls should not be null");
//如果只有一個服務提供者,並且如果是空協議,那麼這個時候直接返回進制訪問,並且銷燬所有的invokers
if (invokerUrls.size() == 1
&& invokerUrls.get(0) != null
&& EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
this.forbidden = true; // Forbid to access
this.invokers = Collections.emptyList();
routerChain.setInvokers(this.invokers);
destroyAllInvokers(); // Close all invokers
} else {//第一次初始化的時候,invokerUrls爲空。
this.forbidden = false; // Allow to access
//獲取老的invoker集合
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
if (invokerUrls == Collections.<URL>emptyList()) {
invokerUrls = new ArrayList<>();//爲null則初始化
}
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<>();
this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
}
if (invokerUrls.isEmpty()) {
return;
}
//把invokerUrls轉化爲InvokerMap
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
/**
* If the calculation is wrong, it is not processed.
*
* 1. The protocol configured by the client is inconsistent with the protocol of the server.
* eg: consumer protocol = dubbo, provider only has other protocol services(rest).
* 2. The registration center is not robust and pushes illegal specification data.
*
*/
if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls
.toString()));
return;
}
//轉化爲list
List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
// pre-route and build cache, notice that route cache should build on original Invoker list.
// toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
routerChain.setInvokers(newInvokers);
//如果服務配置了分組,則把分組下的provider包裝成StaticDirectory,組成一個invoker
//實際上就是按照group進行合併
this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
this.urlInvokerMap = newUrlInvokerMap;
try {//舊的url 是否在新map裏面存在,不存在,就是銷燬url對應的Invoker
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}
toInvokers:這個方法中有比較長的判斷和處理邏輯,我們只需要關心invoker是什麼時候初始化的就行。這裏用到了protocol.refer來構建了一個invoker
invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
//構建完成之後,會保存
newUrlInvokerMap.put(key, invoker);
簡單總結一下,RegistryDirectory.subscribe,其實總的來說就相當於做了兩件事
- 定於指定註冊中心的以下三個路徑的子節點變更事件/dubbo/org.apache.dubbo.demo.DemoService/providers、/dubbo/org.apache.dubbo.demo.DemoService/configurators、/dubbo/org.apache.dubbo.demo.DemoService/routers
- 觸發時間變更之後,把變更的節點信息轉化爲Invoker
Cluster.join這個操作,把RegistryDirectory作爲參數傳遞進去了,那麼是不是意味着返回的invoker中可以拿到真正的服務提供者地址,然後進行遠程訪問。
客戶端服務通信的建立:
protocol.refer:咱們繼續往下看,在toInvokers這個方法中,invoker是通過 protocol.refer來構建的。那麼我們再來分析一下refer裏面做了什麼?
首先protocol,是被依賴注入進來的自適應擴展點Protocol$Adaptive. ,此時傳進去的參數,此時url對應的地址應該是dubbo://開頭的協議地址,所以最終獲得的是通過包裝之後的DubboProtocol對象。QosProtocolWrapper(ProtocolFilterWrapper(ProtocolListenerWrapper(DubboProtocol)))
其中,在Qos這邊,會啓動Qosserver、在FilterWrapper中,會構建一個過濾器鏈,其中包含訪問日誌過濾、訪問限制過濾等等,這個最終在調用的時候會通過一條過濾器鏈路對請求進行處理。
DubboProtocol中沒有refer方法,而是調用父類的refer。
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
}
protocolBindingRefer:主要做了兩件事:
- 優化序列化
- 構建DubboInvoker
在構建DubboInvoker時,會構建一個ExchangeClient,通過getClients(url)方法,這裏基本可以猜到到是服務的通信建立。
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
// 構建序列化
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
DubboProtocol#getClients:這裏面是獲得客戶端連接的方法。判斷是否爲共享連接,默認是共享同一個連接進行通信,是否配置了多個連接通道 connections,默認只有一個共享連接。
private ExchangeClient[] getClients(URL url) {
// whether to share connection
boolean useShareConnect = false;
int connections = url.getParameter(CONNECTIONS_KEY, 0);
List<ReferenceCountExchangeClient> shareClients = null;
// if not configured, connection is shared, otherwise, one connection for one service
// 如果沒有配置的情況下,默認是採用共享連接,否則,就是針對一個服務提供一個連接。
if (connections == 0) {
useShareConnect = true;
/*
* The xml configuration should have a higher priority than properties.
*/
String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
//返回共享連接
shareClients = getSharedClient(url, connections);
}
//從共享連接中獲得客戶端連接進行返回
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (useShareConnect) {
clients[i] = shareClients.get(i);
} else {//如果不是使用共享連接,則初始化一個新的客戶端連接進行返回
clients[i] = initClient(url);
}
}
return clients;
}
getSharedClient :獲取共享連接:
private List<ReferenceCountExchangeClient> getSharedClient(URL url, int connectNum) {
String key = url.getAddress();
List<ReferenceCountExchangeClient> clients = referenceClientMap.get(key);
//檢查當前的key檢查連接是否已經創建過並且可用,如果是,則直接返回並且增加連接的個數的統計
if (checkClientCanUse(clients)) {
batchClientRefIncr(clients);
return clients;
}
//如果連接已經關閉或者連接沒有創建過
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
clients = referenceClientMap.get(key);
// dubbo check// 雙重檢查
if (checkClientCanUse(clients)) {
batchClientRefIncr(clients);
return clients;
}
// 連接數必須大於等於1
// connectNum must be greater than or equal to 1
connectNum = Math.max(connectNum, 1);
//如果當前消費者還沒有和服務端產生連接,則初始化
// If the clients is empty, then the first initialization is
if (CollectionUtils.isEmpty(clients)) {
clients = buildReferenceCountExchangeClientList(url, connectNum);
referenceClientMap.put(key, clients);
} else {//如果clients不爲空,則從clients數組中進行遍歷
for (int i = 0; i < clients.size(); i++) {
ReferenceCountExchangeClient referenceCountExchangeClient = clients.get(i);
// If there is a client in the list that is no longer available, create a new one to replace him.
// 如果在集合中存在一個連接但是這個連接處於closed狀態,則重新構建一個進行替換
if (referenceCountExchangeClient == null || referenceCountExchangeClient.isClosed()) {
clients.set(i, buildReferenceCountExchangeClient(url));
continue;
}
//增加個數
referenceCountExchangeClient.incrementAndGetCount();
}
}
/*
* I understand that the purpose of the remove operation here is to avoid the expired url key
* always occupying this memory space.
*/
locks.remove(key);
return clients;
}
}
buildReferenceCountExchangeClient:開始初始化客戶端連接.也就是說,dubbo消費者在啓動的時候,先從註冊中心上加載最新的服務提供者地址,然後轉化成invoker,但是轉化的時候,也會同步去建立一個連接。而這個連接默認採用的是共享連接,因此就會存在對於同一個服務提供者,假設客戶端依賴了多個@DubboReference,那麼這個時候這些服務的引用會使用同一個連接進行通信。
initClient:終於進入到初始化客戶端連接的方法了,猜測應該是根據url中配置的參數進行遠程通信的構建
private ExchangeClient initClient(URL url) {
// 獲取客戶端通信類型,默認是netty
// client type setting.
String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT));
//獲取編碼解碼類型,默認是Dubbo自定義類型
url = url.addParameter(CODEC_KEY, DubboCodec.NAME);
// enable heartbeat by default
// 開啓心跳並設置心跳間隔,默認60s
url = url.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT));
// 如果沒有找到指定類型的通信擴展點,則拋出異常。
// BIO is not allowed since it has severe performance issue.
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: " + str + "," +
" supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
}
ExchangeClient client;
try {// 是否延遲建立連接
// connection should be lazy
if (url.getParameter(LAZY_CONNECT_KEY, false)) {
client = new LazyConnectExchangeClient(url, requestHandler);
} else {//默認是直接在啓動時建立連接
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
return client;
}
看到了 Exchangers 這個東東,是不是有點眼熟,在服務發佈註冊的源碼裏面也有這個類的參與,說明馬上要通過Netty 進行連接了。
果然是這樣的。這裏就不貼代碼了,跟服務端的流程一摸一樣。最後走到 org.apache.dubbo.remoting.transport.netty4.NettyTransporter#connect
@Override
public Client connect(URL url, ChannelHandler handler) throws RemotingException {
return new NettyClient(url, handler);
}
然後就是與服務端Netty 的連接過程了。總結:RegistryProtocol.refer 過程中有一個關鍵步驟,即在監聽到服務提供者url時觸發RegistryDirectory.notify() 方法。RegistryDirectory.notify() 方法調用 refreshInvoker() 方法將服務提供者urls轉換爲對應的 遠程invoker ,最終調用到 DubboProtocol.refer() 方法生成對應的 DubboInvoker 。
DubboInvoker 的構造方法中有一項入參 ExchangeClient[] clients ,即對應本文要講的網絡客戶端 Client 。DubboInvoker就是通過調用 client.request() 方法完成網絡通信的請求發送和響應接收功能。Client 的具體生成過程就是通過 DubboProtocol 的 initClient(URL url) 方法創建了一個HeaderExchangeClient 。最後附上上述全流程流程圖:
接口調用流程:
客戶端的調用很明顯。需要用到代理類創建的時候的 Handler, JavassistProxyFactory#getProxy
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
一旦代碼被調用,就會觸發InvokerInvocationHandler.invoke.進入到InvokerInvocationHandler.invoke方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}//獲取方法名
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0) {
if ("toString".equals(methodName)) {
return invoker.toString();
} else if ("$destroy".equals(methodName)) {
invoker.destroy();
return null;
} else if ("hashCode".equals(methodName)) {
return invoker.hashCode();
}
} else if (parameterTypes.length == 1 && "equals".equals(methodName)) {
return invoker.equals(args[0]);
}//構建請求實體類
RpcInvocation rpcInvocation = new RpcInvocation(method, invoker.getInterface().getName(), args);
String serviceKey = invoker.getUrl().getServiceKey();
rpcInvocation.setTargetServiceUniqueName(serviceKey);
if (consumerModel != null) {
rpcInvocation.put(Constants.CONSUMER_MODEL, consumerModel);
rpcInvocation.put(Constants.METHOD_MODEL, consumerModel.getMethodModel(method));
}
//發起調用
return invoker.invoke(rpcInvocation).recreate();
}
其中invoker這個對象, 是在啓動注入動態代理類時,初始化的一個調用器對象,我們得先要知道它是誰,才能知道它下一步調用的是哪個對象的方法.是: MockClusterInvoker(AbstractCluster$InterceptorInvokerNode(FailoverClusterInvoker)) ,因爲它是通過MockClusterWrapper來進行包裝的。
所以這個 invoker.invoke 會先走 MockClusterInvoker#invoke。在這裏面有兩個邏輯
- 是否客戶端強制配置了mock調用,那麼在這種場景中主要可以用來解決服務端還沒開發好的時候直接使用本地數據進行測試
- 是否出現了異常,如果出現異常則使用配置好的Mock類來實現服務的降級
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || "false".equalsIgnoreCase(value)) {
//no mock
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
} else {
//fail-mock
try {
result = this.invoker.invoke(invocation);
//fix:#4585
if(result.getException() != null && result.getException() instanceof RpcException){
RpcException rpcException= (RpcException)result.getException();
if(rpcException.isBiz()){
throw rpcException;
}else {
result = doMockInvoke(invocation, rpcException);
}
}
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
}
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
return result;
}
然後繼續走 this.invoker.invoke ,即 AbstractCluster$InterceptorInvokerNode.invoker.爲什麼到這個類,看過前面生成動態代理過程的同學應該都知道,invoker會通過一個Interceptor進行包裝。構建了一個攔截器鏈。
這個攔截器是的組成是: ConsumerContextClusterInterceptor -next-> FailoverClusterInvoker, 當存在多註冊中心的時候,這裏會先走ZoneAwareClusterInvoker 進行註冊中心的負載均衡進行區域路由,繞一圈再回來FailoverClusterInvoker
其中before方法是設置上下文信息,接着調用interceptor.interceppt方法進行攔截處理
調用ClusterInterceptor的默認方法。
default Result intercept(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) throws RpcException {
return clusterInvoker.invoke(invocation);
}
此時傳遞過來的clusterInvoker對象,是攔截器鏈中的第二個節點 FailoverClusterInvoker
AbstractClusterInvoker#invoke:
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// 綁定attachment到invocation中
// binding attachments into invocation.
Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
}
//獲取invoker列表,這裏的列表應該是直接從directory中獲取
List<Invoker<T>> invokers = list(invocation);
//初始化負載均衡算法
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
//調用子類的doInvoke方法
return doInvoke(invocation, invokers, loadbalance);
}
FailoverClusterInvoker.doInvoke:顧名思義,就是集羣容錯的處理,默認的集羣容錯策略是重試,所以也不難猜出這裏面的實現方式。這段代碼邏輯也很好理解,因爲我們之前在講Dubbo的時候說過容錯機制,而failover是失敗重試,所以這裏面應該會實現容錯的邏輯
- 獲得重試的次數,並且進行循環
- 獲得目標服務,並且記錄當前已經調用過的目標服務防止下次繼續將請求發送過去
- 如果執行成功,則返回結果
- 如果出現異常,判斷是否爲業務異常,如果是則拋出,否則,進行下一次重試
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
checkInvokers(copyInvokers, invocation);//檢查
String methodName = RpcUtils.getMethodName(invocation);
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// retry loop.
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if (i > 0) {
checkWhetherDestroyed();
copyInvokers = list(invocation);
// check again
checkInvokers(copyInvokers, invocation);
}
// 負載均衡選擇
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {//進行遠程調用,走AbstractInvoker#invoke
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + methodName
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyInvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
throw new RpcException(le.getCode(), "Failed to invoke the method "
+ methodName + " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers " + providers
+ " (" + providers.size() + "/" + copyInvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
+ Version.getVersion() + ". Last error is: "
+ le.getMessage(), le.getCause() != null ? le.getCause() : le);
}
然後走 AbstractInvoker#invoke 最後調用 DubboInvoker#doInvoke 發起遠程調用。流程圖如下
最後的調用與老版本有一定的區別。詳細參閱官方文檔.