溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Dubbo中@DubboReference.version如何設(shè)置為*

發(fā)布時間:2021-09-17 10:26:17 來源:億速云 閱讀:143 作者:柒染 欄目:web開發(fā)

Dubbo中@DubboReference.version如何設(shè)置為*,針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

Dubbo在消費(fèi)端提供了一個功能,即將消費(fèi)者的版本號指定為*,那么不管服務(wù)端的接口版本是啥,都可以調(diào)用成功。

1 初步猜測

dubbo接口定位邏輯:接口(全路徑)+服務(wù)分組(group字段)+版本號(version字段)。

Zookeeper 是用樹狀來保存數(shù)據(jù)的,在 Zookeeper  中,可以利用Dubbo接口(全路徑)作為父節(jié)點(diǎn),再根據(jù)group和version信息寫入子節(jié)點(diǎn)。

而 Nacos,在 Nacos  的控制臺中,我們看到可以根據(jù)服務(wù)名或服務(wù)分組來模糊查詢服務(wù)列表,那么在消費(fèi)者訂閱的時候,就根據(jù)這兩個模糊查詢就可以了,查出來的健康提供者都是符合的。

下面就深入一下源碼,看看實際的邏輯是不是類似我們的猜想。

2 源碼剖析

2.1 Zookeeper 作為注冊中心

2.1.1 準(zhǔn)備

弄一個服務(wù)提供者、一個服務(wù)消費(fèi)者。服務(wù)提供者對外提供一個dubbo接口,版本有1.0.0和2.0.0;服務(wù)消費(fèi)者引入服務(wù)提供者提供的dubbo接口,version設(shè)置為*。

啟動服務(wù)提供者、接著啟動消費(fèi)者,觀察后臺日志打印:

我們可以看到,當(dāng)我們將@DubboReference的version設(shè)置為*的時候,他就根據(jù)注冊url(帶*)去找有哪些服務(wù)提供者,然后返回的urls會有多個,其中包含版本號為1.0.0和2.0.0的url。

2021-05-01 10:24:08.561 [main] [INFO ] [o.a.d.r.z.ZookeeperRegistry] [] [] -  [DUBBO] Subscribe: consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*, dubbo version: 2.7.7, current host: 127.0.0.1 2021-05-01 10:24:08.572 [main] [INFO ] [o.a.d.r.z.ZookeeperRegistry] [] [] -  [DUBBO] Notify urls for subscribe url consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*, urls: [dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=1.0.0&service.filter=default,dubboLogFilter&side=provider&timestamp=1619835820516&version=1.0.0, dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=2.0.0&service.filter=default,dubboLogFilter&side=provider&timestamp=1619835820187&version=2.0.0, empty://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=configurators&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*, empty://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*], dubbo version: 2.7.7, current host: 127.0.0.1

2.1.2 源碼分析

上面我們看到,version=*可以成功訂閱,并且服務(wù)提供者有兩個,分別是version=1.0.0和version=2.0.0。

結(jié)果是看得出來了,但是我們還是需要看看Zookeeper是怎么的判斷邏輯。

2.1.2.1 服務(wù)消費(fèi)者訂閱過程

我們都知道,正常發(fā)布Dubbo的消費(fèi)者,需要配置ReferenceConfig,然后調(diào)用export方法;當(dāng)然了,我們這里就不過于深入了,直接從日志的入口來開始:org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe

我們服務(wù)消費(fèi)者的訂閱url:

consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=16215&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619842106726&version=*

第一步:獲取dubbo接口全路徑

url.getServiceInterface() -> com.winfun.service.DubboServiceOne

接著判斷是否等于“*”,明顯不是,跳到else分支

第二步:根據(jù)url獲取path

獲取根節(jié)點(diǎn):

toCategoriesPath(url) -> /dubbo/com.winfun.service.DubboServiceOne/providers、/dubbo/com.winfun.service.DubboServiceOne/configurators、/dubbo/com.winfun.service.DubboServiceOne/consumers

第三步:遍歷第二步的path、創(chuàng)建父節(jié)點(diǎn)

重點(diǎn)在path=/dubbo/com.winfun.service.DubboServiceOne/providers,其他忽略即可

根據(jù)path創(chuàng)建節(jié)點(diǎn)(非持久化):zkClient.create(root, false);

給path添加子節(jié)點(diǎn)監(jiān)聽器:zkClient.addChildListener(path, zkListener) 并返回子節(jié)點(diǎn)列表

  • dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=1.0.0&service.filter=default,dubboLogFilter&side=provider×tamp=1619835820516&version=1.0.0

  • dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=2.0.0&service.filter=default,dubboLogFilter&side=provider×tamp=1619835820187&version=2.0.0

configurators 和 consumers 不存在子節(jié)點(diǎn),所以子節(jié)點(diǎn)是根據(jù)規(guī)則生成的url,前綴為empty

第四步:對上面獲取到的urls進(jìn)行監(jiān)聽

調(diào)用org.apache.dubbo.registry.support.FailbackRegistry#notify方法。

最后會去到  org.apache.dubbo.registry.support.AbstractRegistry#notify(org.apache.dubbo.common.URL,  org.apache.dubbo.registry.NotifyListener,  java.util.List)

重點(diǎn):在監(jiān)聽前,會先匹配根據(jù)path查詢的所有子節(jié)點(diǎn)中,匹配符合當(dāng)前消費(fèi)者的子節(jié)點(diǎn)(根據(jù)group和version判斷),利用org.apache.dubbo.common.utils.UrlUtils#isMatch判斷。

判斷中最重要的邏輯:

String ANY_VALUE = "*";  String consumerGroup = consumerUrl.getParameter(GROUP_KEY); String consumerVersion = consumerUrl.getParameter(VERSION_KEY); String consumerClassifier = consumerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);  String providerGroup = providerUrl.getParameter(GROUP_KEY); String providerVersion = providerUrl.getParameter(VERSION_KEY); String providerClassifier = providerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE); return (ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup))         && (ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))         && (consumerClassifier == null || ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier));

上面完全可以體現(xiàn)出:當(dāng)版本號等于*號,dubbo接口根節(jié)點(diǎn)下的服務(wù)都會作為當(dāng)前消費(fèi)者的服務(wù)提供者。

好了,到這里,我們可以知道Zookeeper是怎么為version=*的消費(fèi)者訂閱服務(wù)的,直接根據(jù)接口全路徑名到Zookeeper里獲取所有子節(jié)點(diǎn),并都可以作為服務(wù)提供者。

其實這里會有一個擴(kuò)展點(diǎn):多個服務(wù)提供者,調(diào)用的時候是怎么負(fù)載的,其實在@DubboReference中的loadbance屬性中看得出,默認(rèn)的負(fù)載策略是隨機(jī)。

/**  * Load balance strategy, legal values include: random, roundrobin, leastactive  * <p>  * see Constants#DEFAULT_LOADBALANCE  */ String loadbalance() default "";  org.apache.dubbo.common.constants.CommonConstants#DEFAULT_LOADBALANCE="random";

2.1.2.2 服務(wù)消費(fèi)者執(zhí)行過程

proxy執(zhí)行入口

我們可以通過debug模式,進(jìn)入到Dubbo方法執(zhí)行的入口:org.apache.dubbo.rpc.proxy.InvokerInvocationHandler#invoke

第一步:初步判斷

如果是 Object類 或者 toString、destory、hashCode等方法,直接執(zhí)行

第二步:創(chuàng)建RpcInvocation

根據(jù)執(zhí)行方法、參數(shù)等信息創(chuàng)建RpcInvocation

獲取serviceKey:-> serviceKey = dubbo-api-path/group:version

RpcInvocation設(shè)置TargetServiceUniqueName

第三步:調(diào)用invoker的invoke方法

來到org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke

判斷是否設(shè)置了 mock 或 force

  • 如果是調(diào)用org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#doMockInvoke方法

  • 否則來到org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster.InterceptorInvokerNode#invoke

第四步:AbstractClusterInvoker#invoke

調(diào)用AbstractClusterInvoker#list獲取invoker列表,可以看到拿到的就是1.0.0版本和2.0.0版本的服務(wù)提供者

接著調(diào)用 initLoadBalance  方法來初始化負(fù)載均衡策略,從訂閱url里面獲取loadbalance的值,如果沒有設(shè)置,返回默認(rèn)值“random”

/**  * Init LoadBalance.  * <p>  * if invokers is not empty, init from the first invoke's url and invocation  * if invokes is empty, init a default LoadBalance(RandomLoadBalance)  * </p>  *  * @param invokers   invokers  * @param invocation invocation  * @return LoadBalance instance. if not need init, return null.  */ protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {     if (CollectionUtils.isNotEmpty(invokers)) {         return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()                 .getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));     } else {         return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);     } }

第五步:根據(jù)集群策略執(zhí)行方法

由于Dubbo默認(rèn)的集群策略是  failover,所以會來到來到:org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoker

首先會從注冊url里面的retries字段獲取重試次數(shù)(如果為空,默認(rèn)重試次數(shù)為2),此次取的是默認(rèn)值,所以最后最大調(diào)用次數(shù)為3.

循環(huán)retries+1次

  • 來到:org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#select  選擇Invoker

在RandomLoadBalance#doSelect  中,首先會根據(jù)服務(wù)提供者的權(quán)重判斷,如果權(quán)重沒賦值,最后會利用ThreadLocalRandom.current().nextInt(invokers.size())隨機(jī)選擇一個invoker。

  • 下一步:org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#doSelect

  • 下一步:org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance#select由于默認(rèn)是random的負(fù)載均衡策略,所以最后來到:org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect

  • 執(zhí)行invoke方法,返回結(jié)果

  • 如果有錯誤,記錄著,下次循環(huán)打印warn日志

  • 如果超過retries+1次調(diào)用失敗,往外拋出RpcException異常

到這里,我們已經(jīng)非常清楚Zookeeper 是如何支持消費(fèi)者將 version設(shè)置為*,并且方法調(diào)用時是如何選擇服務(wù)提供者。

2.2 Nacos 作為注冊中心

2.2.1 準(zhǔn)備

弄一個服務(wù)提供者、一個服務(wù)消費(fèi)者,這次不再是Zookeeper作為注冊中心,而是Nacos作為注冊中心。服務(wù)提供者對外提供一個dubbo接口,版本有1.0.0和2.0.0;服務(wù)消費(fèi)者引入服務(wù)提供者提供的dubbo接口,version設(shè)置為*。

啟動服務(wù)提供者、接著啟動消費(fèi)者,觀察后臺日志打?。?/p>

2.2.2 源碼分析

2.2.2.1 服務(wù)消費(fèi)者訂閱過程

Nacos 源碼分析也是直接從 NacosRegistry#doSubscribe 入口開始:

org.apache.dubbo.registry.nacos.NacosRegistry#doSubscribe(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener)

消費(fèi)者的注冊url:

consumer://192.168.3.3/com.winfun.service.DubboServiceOne?application=dubbo-consumer-nacos&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=39203&qos.enable=false&reference.filter=default,dubboLogFilter&release=2.7.8&revision=*&side=consumer&sticky=false&timestamp=1620177626113&version=*

第一步、根據(jù)url獲取serviceName集合:

org.apache.dubbo.registry.nacos.NacosRegistry#getServiceNames0

1、創(chuàng)建 NacosServiceName:

providers:com.winfun.service.DubboServiceOne:*:

2、接著到:org.apache.dubbo.registry.nacos.NacosRegistry#filterServiceNames(org.apache.dubbo.registry.nacos.NacosServiceName)

根據(jù)上面的servicename過濾出所有的serviceName

2.1、 先利用NamingProxy查詢:

com.alibaba.nacos.client.naming.net.NamingProxy#getServiceList(int, int, java.lang.String, com.alibaba.nacos.api.selector.AbstractSelector)

利用接口全路徑名+group查詢,沒有帶版本號

2.2、最后到:

com.alibaba.nacos.common.http.client.NacosRestTemplate#exchangeForm

http 請求:

url:

http://127.0.0.1:8848/nacos/v1/ns/service/list params:{app=unknown, pageSize=2147483647, groupName=DEFAULT_GROUP, namespaceId=public, pageNo=1}

返回結(jié)果:

RestResult{code=200, message='null', data={"doms":["providers:com.winfun.service.DubboServiceOne:1.0.0:","providers:com.winfun.service.DubboServiceOne:2.0.0:"],"count":2}}

明顯包含兩個版本的service

第二步、根據(jù)條件過濾合適的 service

public boolean isCompatible(NacosServiceName concreteServiceName) {      if (!concreteServiceName.isConcrete()) { // The argument must be the concrete NacosServiceName         return false;     }      // Not match comparison     if (!StringUtils.isEquals(this.category, concreteServiceName.category)             && !matchRange(this.category, concreteServiceName.category)) {         return false;     }      if (!StringUtils.isEquals(this.serviceInterface, concreteServiceName.serviceInterface)) {         return false;     }      // wildcard condition     // 重點(diǎn)在這里     if (isWildcard(this.version)) {         return true;     }      if (isWildcard(this.group)) {         return true;     }      // range condition     if (!StringUtils.isEquals(this.version, concreteServiceName.version)             && !matchRange(this.version, concreteServiceName.version)) {         return false;     }      if (!StringUtils.isEquals(this.group, concreteServiceName.group) &&             !matchRange(this.group, concreteServiceName.group)) {         return false;     }      return true; }  private boolean isWildcard(String value) {     return WILDCARD.equals(value); }  public static final String WILDCARD = "*";

過濾后的 service 有兩個,分別是1.0.0和2.0.0

那么繼續(xù)深一步的訂閱流程:org.apache.dubbo.registry.nacos.NacosRegistry#doSubscribe(org.apache.dubbo.common.URL,  org.apache.dubbo.registry.NotifyListener, java.util.Set)

第三步、遍歷serviceNames,根據(jù)serviceName+group查詢所有實例列表并且進(jìn)行實例監(jiān)聽

List<Instance> instances = new LinkedList<>(); for (String serviceName : serviceNames) {     instances.addAll(namingService.getAllInstances(serviceName             , getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP)));     notifySubscriber(url, listener, instances);     subscribeEventListener(serviceName, url, listener); }

到這里,整個訂閱流程已經(jīng)結(jié)束,主要是看version=*如何判斷哪些服務(wù)實例可提供服務(wù),再深入的就沒有了。

Nacos 作為注冊中心,查詢服務(wù)實例主要是根據(jù)  serviceName(接口全路徑名)和group(分組),這是因為Nacos的數(shù)據(jù)結(jié)構(gòu)本身主要的就是服務(wù)名+分組名。

Dubbo中@DubboReference.version如何設(shè)置為*

2.2.2.2 服務(wù)消費(fèi)者調(diào)用過程

這個就不再深入講解了,調(diào)用過程和 Zookeeper 上基本一致。

關(guān)于Dubbo中@DubboReference.version如何設(shè)置為*問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI