背景

隨著雲原生時代的到來,Dubbo 3。0 的一個很重要的目標就是全面擁抱雲原生。正因如此,Dubbo 3。0 為了能夠更好的適配雲原生,將原來的介面級服務發現機制演進為應用級服務發現機制。

基於應用級服務發現機制,Dubbo 3。0 能大幅降低框架帶來的額外資源消耗,大幅提升資源利用率,主要體現在:

單機常駐記憶體下降 75%

能支援的叢集例項規模以百萬計的叢集

註冊中心總體資料量下降超 90%

目前關於 Dubbo 服務端暴露流程的技術文章很多,但是都是基於 Dubbo 介面級服務發現機制來解讀的。在 Dubbo 3。0 的應用級服務發現機制下,服務端暴露流程與之前有很大的變化,本文希望可以透過 對Dubbo 3。0 原始碼理解來解析服務端暴露全流程。

什麼是應用級服務發現

簡單來說,以前 Dubbo 是將介面的資訊全部註冊到註冊中心,而一個應用例項一般會存在多個介面,這樣一來註冊的資料量就要大很多,而且有冗餘。應用級服務發現的機制是同一個應用例項僅在註冊中心註冊一條資料,這種機制主要解決以下幾個問題:

對齊主流微服務模型,如:Spring Cloud

支援 Kubernetes native service,Kubernetes 中維護排程的服務都是基於應用例項級,不支援介面級

減少註冊中心資料儲存能力,降低了地址變更推送的壓力

假設應用 dubbo-application 部署了 3 個例項(instance1, instance2, instance3),並且對外提供了 3 個介面(sayHello, echo, getVersion)分別設定了不同的超時時間。在介面級和應用級服務發現機制下,註冊到註冊中心的資料是截然不同的。如下圖所示:

介面級服務發現機制下注冊中心中的資料

“sayHello”: [

{“application”:“dubbo-application”,“name”:“instance1”, “ip”:“127。0。0。1”, “metadata”:{“timeout”:1000}},

{“application”:“dubbo-application”,“name”:“instance2”, “ip”:“127。0。0。2”, “metadata”:{“timeout”:2000}},

{“application”:“dubbo-application”,“name”:“instance3”, “ip”:“127。0。0。3”, “metadata”:{“timeout”:3000}},

],

“echo”: [

{“application”:“dubbo-application”,“name”:“instance1”, “ip”:“127。0。0。1”, “metadata”:{“timeout”:1000}},

{“application”:“dubbo-application”,“name”:“instance2”, “ip”:“127。0。0。2”, “metadata”:{“timeout”:2000}},

{“application”:“dubbo-application”,“name”:“instance3”, “ip”:“127。0。0。3”, “metadata”:{“timeout”:3000}},

],

“getVersion”: [

{“application”:“dubbo-application”,“name”:“instance1”, “ip”:“127。0。0。1”, “metadata”:{“timeout”:1000}},

{“application”:“dubbo-application”,“name”:“instance2”, “ip”:“127。0。0。2”, “metadata”:{“timeout”:2000}},

{“application”:“dubbo-application”,“name”:“instance3”, “ip”:“127。0。0。3”, “metadata”:{“timeout”:3000}}

應用級服務發現機制下注冊中心中的資料

“dubbo-application”: [

{“name”:“instance1”, “ip”:“127。0。0。1”, “metadata”:{“timeout”:1000}},

{“name”:“instance2”, “ip”:“127。0。0。2”, “metadata”:{“timeout”:2000}},

{“name”:“instance3”, “ip”:“127。0。0。3”, “metadata”:{“timeout”:3000}}

透過對比我們可以發現,採用應用級服務發現機制確實使註冊中心中的資料量減少了很多,那些原有的介面級的資料儲存在元資料中心中。

服務端暴露全流程

引入應用級服務發現機制以後,Dubbo 3。0 服務端暴露全流程和之前有很大的區別。暴露服務端全流程的核心程式碼在 DubboBootstrap#doStart 中,具體如下:

private void doStart() {

// 1。 暴露Dubbo服務

exportServices();

// If register consumer instance or has exported services

if (isRegisterConsumerInstance() || hasExportedServices()) {

// 2。 暴露元資料服務

exportMetadataService();

// 3。 定時更新和上報元資料

registerServiceInstance();

。。。。

}

……

}

假設以 Zookeeper 作為註冊中,對外暴露 Triple 協議的服務為例,服務端暴露全流程時序圖如下:

深入解析 Dubbo 3.0 服務端暴露全流程

我們可以看到,整個的暴露流程還是挺複雜的,一共可以分為四個部分:

暴露 injvm 協議的服務

註冊 service-discovery-registry 協議

暴露 Triple 協議的服務並註冊 registry 協議

暴露 MetadataService 服務

下面會分別從這四個部分對服務暴露全流程進行詳細講解。

1、暴露 injvm 協議的服務

injvm 協議的服務是暴露在本地的,主要原因是在一個應用上往往既有 Service(暴露服務)又有 Reference(服務引用)的情況存在,並且 Reference 引用的服務就是在該應用上暴露的 Service。為了支援這種使用場景,Dubbo 提供了 injvm 協議,將 Service 暴露在本地,Reference 就可以不需要走網路直接在本地呼叫 Service。

深入解析 Dubbo 3.0 服務端暴露全流程

整體時序圖

由於這部分內容在之前的介面級服務發現機制中是類似的,所以相關的核心程式碼就不在這裡展開討論了。

2、註冊

service-discovery-registry 協議

註冊

service-discovery-registry 協議的核心目的是為了註冊與服務相關的元資料,預設情況下元資料透過 InMemoryWritableMetadataService 將資料儲存在本地記憶體和本地檔案。

深入解析 Dubbo 3.0 服務端暴露全流程

整體時序圖

核心程式碼在 ServiceConfig#exportRemote 中,具體如下:

註冊 service-discovery-registry 協議的入口

private URL exportRemote(URL url, List registryURLs) {

if (CollectionUtils。isNotEmpty(registryURLs)) {

// 如果是多個註冊中心,透過迴圈對每個註冊中心進行註冊

for (URL registryURL : registryURLs) {

// 判斷是否是service-discovery-registry協議

// 將service-name-mapping引數的值設定為true

if (SERVICE_REGISTRY_PROTOCOL。equals(registryURL。getProtocol())) {

url = url。addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, “true”);

}

……

// 註冊service-discovery-registry協議複用服務暴露流程

doExportUrl(registryURL。putAttribute(EXPORT_KEY, url), true);

}

……

return url;

}

invoker 中包裝 Metadata

核心程式碼在 ServiceConfig#doExportUrl 中,具體如下:

private void doExportUrl(URL url, boolean withMetaData) {

Invoker<?> invoker = PROXY_FACTORY。getInvoker(ref, (Class) interfaceClass, url);

// 此時的withMetaData的值為true

// 將invoker包裝成DelegateProviderMetaDataInvoker

if (withMetaData) {

invoker = new DelegateProviderMetaDataInvoker(invoker, this);

}

Exporter<?> exporter = PROTOCOL。export(invoker);

exporters。add(exporter);

}

透過 RegistryProtocol 將 Invoker 轉化成 Exporter

核心程式碼在 ProtocolListenerWrapper#export 中,具體如下:

public Exporter export(Invoker invoker) throws RpcException {

// 此時的protocol為RegistryProtocol型別

if (UrlUtils。isRegistry(invoker。getUrl())) {

return protocol。export(invoker);

}

……

}

RegistryProtocol 將 Invoker 轉化成 Exporter 的核心流程

核心程式碼在 RegistryProtocol#export 中,具體如下:

public Exporter export(final Invoker originInvoker) throws RpcException {

URL registryUrl = getRegistryUrl(originInvoker);

URL providerUrl = getProviderUrl(originInvoker);

……

// 再次暴露Triple協議的服務

final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);

// registryUrl中包含service-discovery-registry協議

// 透過該協議建立ServiceDiscoveryRegistry物件

// 然後組合RegistryServiceListener監聽器,

// 最後包裝成ListenerRegistryWrapper物件

final Registry registry = getRegistry(registryUrl);

final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

boolean register = providerUrl。getParameter(REGISTER_KEY, true);

if (register) {

// 註冊service-discovery-registry協議

// 觸發RegistryServiceListener的onRegister事件

register(registry, registeredProviderUrl);

}

……

// 觸發RegistryServiceListener的onRegister事件

notifyExport(exporter);

return new DestroyableExporter<>(exporter);

}

暴露 Triple 協議的服務

核心程式碼在 RegistryProtocol#doLocalExport 中,具體如下:

private ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) {

String key = getCacheKey(originInvoker);

// 此時的protocol為Triple協議的代理類

// 和暴露injvm協議的PROTOCOL相同

return (ExporterChangeableWrapper) bounds。computeIfAbsent(key, s -> {

Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);

return new ExporterChangeableWrapper<>((Exporter) protocol。export(invokerDelegate), originInvoker);

});

}

註冊service-discovery-registry協議

核心程式碼在 ServiceDiscoveryRegistry#register和ServiceDiscoveryRegistry#doRegister 中,具體如下:

1、ServiceDiscoveryRegistry#register

public final void register(URL url) {

// 只有服務端(Provider)才需要註冊

if (!shouldRegister(url)) {

return;

}

// 註冊service-discovery-registry協議

doRegister(url);

}

2、ServiceDiscoveryRegistry#doRegister

public void doRegister(URL url) {

url = addRegistryClusterKey(url);

// 註冊元資料

if (writableMetadataService。exportURL(url)) {

if (logger。isInfoEnabled()) {

logger。info(format(“The URL[%s] registered successfully。”, url。toString()));

}

} else {

if (logger。isWarnEnabled()) {

logger。warn(format(“The URL[%s] has been registered。”, url。toString()));

}

}

}

註冊元資料

核心程式碼在

InMemoryWritableMetadataService#exportURL 中,具體如下:

public boolean exportURL(URL url) {

// 如果是MetadataService,則不註冊元資料

if (MetadataService。class。getName()。equals(url。getServiceInterface())) {

this。metadataServiceURL = url;

return true;

}

updateLock。readLock()。lock();

try {

String[] clusters = getRegistryCluster(url)。split(“,”);

for (String cluster : clusters) {

MetadataInfo metadataInfo = metadataInfos。computeIfAbsent(cluster, k -> new MetadataInfo(ApplicationModel。getName()));

// 將Triple協議的服務中介面相關的資料生成ServiceInfo

// 將ServiceInfo註冊到MetadataInfo中

metadataInfo。addService(new ServiceInfo(url));

}

metadataSemaphore。release();

return addURL(exportedServiceURLs, url);

} finally {

updateLock。readLock()。unlock();

}

}

釋出 onRegister 事件

核心程式碼在 ListenerRegistryWrapper#register 中,具體如下:

public void register(URL url) {

try {

// registry為ServiceDiscoveryRegistry物件

// 此時已經呼叫完ServiceDiscoveryRegistry#registry方法

registry。register(url);

} finally {

if (CollectionUtils。isNotEmpty(listeners) && !UrlUtils。isConsumer(url)) {

RuntimeException exception = null;

for (RegistryServiceListener listener : listeners) {

if (listener != null) {

try {

// 註冊完service-discovery-registry協議後釋出onRegister事件

listener。onRegister(url, registry);

} catch (RuntimeException t) {

logger。error(t。getMessage(), t);

exception = t;

}

}

}

if (exception != null) {

throw exception;

}

}

}

}

釋出服務註冊事件

核心程式碼在 RegistryProtocol#notifyExport 中,具體如下:

private void notifyExport(ExporterChangeableWrapper exporter) {

List listeners = ExtensionLoader。getExtensionLoader(RegistryProtocolListener。class)

。getActivateExtension(exporter。getOriginInvoker()。getUrl(), “registry。protocol。listener”);

if (CollectionUtils。isNotEmpty(listeners)) {

for (RegistryProtocolListener listener : listeners) {

// 釋出RegistryProtocolListener的onExport事件

listener。onExport(this, exporter);

}

}

}

我們可以看出註冊

service-discovery-registry 協議的核心目的是為了將服務的介面相關的資訊儲存在記憶體中。從相容性和平滑遷移兩方面來考慮,社群在實現的時候採取複用 ServiceConfig 的暴露流程的方式。

3、暴露Triple協議服務並註冊registry協議

暴露 Triple 協議的服務並註冊 registry 協議是 Dubbo 3。0 服務暴露的核心流程,一共分為兩部分:

暴露 Triple 協議的服務

註冊 registry 協議

由於暴露 Triple 協議服務的流程和暴露 Injvm 協議服務的流程是一致的,所以不再贅述。註冊 registry 協議的過程僅僅註冊了應用例項相關的資訊,也就是之前提到的應用級服務發現機制。

深入解析 Dubbo 3.0 服務端暴露全流程

整體時序圖

透過 InterfaceCompatibleRegistryProtocol 將 Invoker 轉化成 Exporter

核心程式碼在 ProtocolListenerWrapper#export 中,具體如下:

public Exporter export(Invoker invoker) throws RpcException {

// 此時的protocol為InterfaceCompatibleRegistryProtocol型別(繼承了RegistryProtocol)

// 注意:在註冊service-discovery-registry協議的時候protocol為RegistryProtocol型別

if (UrlUtils。isRegistry(invoker。getUrl())) {

return protocol。export(invoker);

}

……

}

RegistryProtocol 將 Invoker 轉化成 Exporter 的核心流程

核心程式碼在 RegistryProtocol#export 中,具體如下:

public Exporter export(final Invoker originInvoker) throws RpcException {

URL registryUrl = getRegistryUrl(originInvoker);

URL providerUrl = getProviderUrl(originInvoker);

……

// 再次暴露Triple協議的服務

final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);

// registryUrl中包含registry協議

// 透過該協議建立ZookeeperRegistry物件

// 然後組合RegistryServiceListener監聽器,

// 最後包裝成ListenerRegistryWrapper物件

// 注意:

// 1。 service-discovery-registry協議對應的是ServiceDiscoveryRegistry

// 2。 registry協議對應的是ZookeeperRegistry

final Registry registry = getRegistry(registryUrl);

final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

boolean register = providerUrl。getParameter(REGISTER_KEY, true);

if (register) {

// 註冊registry協議

// 觸發RegistryServiceListener的onRegister事件

register(registry, registeredProviderUrl);

}

……

// 釋出RegistryProtocolListener的onExport事件

notifyExport(exporter);

return new DestroyableExporter<>(exporter);

}

註冊 registry 協議

核心程式碼在 FailbackRegistry#register 和 ServiceDiscoveryRegistry#doRegister 中(ZookeeperRegistry 繼承 FailbackRegistry)中,具體如下:

1、FailbackRegistry#register

public void register(URL url) {

if (!acceptable(url)) {

……

try {

// 註冊registry協議

doRegister(url);

} catch (Exception e) {

……

}

}

}

2、ServiceDiscoveryRegistry#doRegister

public void doRegister(URL url) {

try {

// 在zookeeper上註冊Provider

// 目錄:/dubbo/xxxService/providers/***

// 資料:dubbo://192。168。31。167:20800/xxxService?anyhost=true&

// application=application-name&async=false&deprecated=false&dubbo=2。0。2&

// dynamic=true&file。cache=false&generic=false&interface=xxxService&

// metadata-type=remote&methods=hello&pid=82470&release=&

// service-name-mapping=true&side=provider×tamp=1629588251493

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);

}

}

訂閱地址變更

核心程式碼在 FailbackRegistry#subscribe 和 ZookeeperRegistry#doSubscribe 中,具體如下:

1、FailbackRegistry#subscribe

public void subscribe(URL url, NotifyListener listener) {

……

try {

// 呼叫ZookeeperRegistry#doSubscribe

doSubscribe(url, listener);

} catch (Exception e) {

……

}

2、ZookeeperRegistry#doSubscribe

public void doSubscribe(final URL url, final NotifyListener listener) {

try {

if (ANY_VALUE。equals(url。getServiceInterface())) {

……

} else {

……

for (String path : toCategoriesPath(url)) {

ConcurrentMap listeners = zkListeners。computeIfAbsent(url, k -> new ConcurrentHashMap<>());

ChildListener zkListener = listeners。computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, path, k, latch));

if (zkListener instanceof RegistryChildListenerImpl) {

((RegistryChildListenerImpl) zkListener)。setLatch(latch);

}

// 建立臨時節點用來儲存configurators資料

// 目錄:/dubbo/xxxService/configurators

// 資料:應用的配置資訊,可以在dubbo-admin中進行修改,預設為空

zkClient。create(path, false);

// 新增監聽器,用來監聽configurators中的變化

List children = zkClient。addChildListener(path, zkListener);

if (children != null) {

urls。addAll(toUrlsWithEmpty(url, path, children));

}

}

……

}

} catch (Throwable e) {

……

}

}

建立暴露的 Triple 協議服務與 Metadata 之間的聯絡

核心程式碼在 ServiceConfig#exportUrl、MetadataUtils#publishServiceDefinition、

InMemoryWritableMetadataService#publishServiceDefinition、RemoteMetadataServiceImpl#publishServiceDefinition 和 MetadataReport#storeProviderMetadata 中,具體如下:

1、ServiceConfig#exportUrl

private void exportUrl(URL url, List registryURLs) {

……

if (!SCOPE_NONE。equalsIgnoreCase(scope)) {

……

if (!SCOPE_LOCAL。equalsIgnoreCase(scope)) {

url = exportRemote(url, registryURLs);

// 釋出事件,更新服務介面相關的資料

MetadataUtils。publishServiceDefinition(url);

}

}

……

}

2、MetadataUtils#publishServiceDefinition

public static void publishServiceDefinition(URL url) {

// 將服務介面相關的資料存在到InMemoryWritableMetadataService中

WritableMetadataService。getDefaultExtension()。publishServiceDefinition(url);

// 將服務介面相關的資料存在到遠端的元資料中心

if (REMOTE_METADATA_STORAGE_TYPE。equalsIgnoreCase(url。getParameter(METADATA_KEY))) {

getRemoteMetadataService()。publishServiceDefinition(url);

}

}

3、

InMemoryWritableMetadataService#publishServiceDefinition

public void publishServiceDefinition(URL url) {

try {

String interfaceName = url。getServiceInterface();

if (StringUtils。isNotEmpty(interfaceName)

&& !ProtocolUtils。isGeneric(url。getParameter(GENERIC_KEY))) {

Class interfaceClass = Class。forName(interfaceName);

ServiceDefinition serviceDefinition = ServiceDefinitionBuilder。build(interfaceClass);

Gson gson = new Gson();

String data = gson。toJson(serviceDefinition);

// 儲存服務介面相關資料

// 資料格式:

// {

// “canonicalName”: “xxxService”,

// “codeSource”: “file:/Users/xxxx”,

// “methods”: [{

// “name”: “hello”,

// “parameterTypes”: [“java。lang。String”],

// “returnType”: “java。lang。String”,

// “annotations”: []

// }],

// “types”: [{

// “type”: “java。lang。String”

// }],

// “annotations”: []

// }

serviceDefinitions。put(url。getServiceKey(), data);

return;

} else if (CONSUMER_SIDE。equalsIgnoreCase(url。getParameter(SIDE_KEY))) {

……

}

……

} catch (Throwable e) {

……

}

}

4、RemoteMetadataServiceImpl#publishServiceDefinition

public void publishServiceDefinition(URL url) {

checkRemoteConfigured();

String side = url。getSide();

if (PROVIDER_SIDE。equalsIgnoreCase(side)) {

// 釋出服務端(Provider)的服務介面資訊到元資料中心

publishProvider(url);

} else {

……

}

}

RemoteMetadataServiceImpl#publishProvider

private void publishProvider(URL providerUrl) throws RpcException {

……

try {

String interfaceName = providerUrl。getServiceInterface();

if (StringUtils。isNotEmpty(interfaceName)) {

……

for (Map。Entry entry : getMetadataReports()。entrySet()) {

// 獲取MetadataReport服務,該服務用來訪問元資料中心

MetadataReport metadataReport = entry。getValue();

// 將服務介面資訊儲存到元資料中心

metadataReport。storeProviderMetadata(new MetadataIdentifier(providerUrl。getServiceInterface(),

providerUrl。getVersion(), providerUrl。getGroup(),

PROVIDER_SIDE, providerUrl。getApplication()), fullServiceDefinition);

}

return;

}

……

} catch (ClassNotFoundException e) {

……

}

}

5、AbstractMetadataReport#storeProviderMetadata

public void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition){

if (syncReport) {

storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition);

} else {

// 非同步儲存到元資料中心

reportCacheExecutor。execute(() -> storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition));

}

}

private void storeProviderMetadataTask(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {

try {

……

allMetadataReports。put(providerMetadataIdentifier, serviceDefinition);

failedReports。remove(providerMetadataIdentifier);

Gson gson = new Gson();

// data的資料格式:

// {

// “parameters”: {

// “side”: “provider”,

// “interface”: “xxxService”,

// “metadata-type”: “remote”,

// “service-name-mapping”: “true”,

// },

// “canonicalName”: “xxxService”,

// “codeSource”: “file:/Users/xxxx”,

// “methods”: [{

// “name”: “hello”,

// “parameterTypes”: [“java。lang。String”],

// “returnType”: “java。lang。String”,

// “annotations”: []

// }],

// “types”: [{

// “type”: “java。lang。String”

// }],

// “annotations”: []

// }

String data = gson。toJson(serviceDefinition);

// 儲存到元資料中心,例項中的元資料中心是ZookeeperMetadataReport

// 目錄:元資料中心Metadata-report的/dubbo/metadata/xxxService/provider/${application-name}節點下

doStoreProviderMetadata(providerMetadataIdentifier, data);

// 儲存到本地檔案

// 路徑:xxxService:::provider:${application-name}

saveProperties(providerMetadataIdentifier, data, true, !syncReport);

} catch (Exception e) {

……

}

}

建立 Triple 協議服務與 MetadataReport 服務之間的關係

核心程式碼在 ServiceConfig#exported、

MetadataServiceNameMapping#map 和 ZookeeperMetadataReport#registerServiceAppMapping 中,具體如下:

1、ServiceConfig#exported

protected void exported() {

exported = true;

List exportedURLs = this。getExportedUrls();

exportedURLs。forEach(url -> {

// 判斷URL中是否標記有service-name-mapping的欄位

// 標記有該欄位的服務是需要將暴露的服務與元資料中心關聯起來

// Consumer可以透過元資料中心的訊息變更感知到Provider端元資料的變更

if (url。getParameters()。containsKey(SERVICE_NAME_MAPPING_KEY)) {

ServiceNameMapping serviceNameMapping = ServiceNameMapping。getDefaultExtension();

// 建立關係

serviceNameMapping。map(url);

}

});

onExported();

}

2、

MetadataServiceNameMapping#map

public void map(URL url) {

execute(() -> {

String registryCluster = getRegistryCluster(url);

// 獲取MetadataReport,也就是元資料中心的訪問路徑

MetadataReport metadataReport = MetadataReportInstance。getMetadataReport(registryCluster);

……

int currentRetryTimes = 1;

boolean success;

String newConfigContent = getName();

do {

// 獲取元資料中心中儲存的應用的版本資訊

ConfigItem configItem = metadataReport。getConfigItem(serviceInterface, DEFAULT_MAPPING_GROUP);

String oldConfigContent = configItem。getContent();

if (StringUtils。isNotEmpty(oldConfigContent)) {

boolean contains = StringUtils。isContains(oldConfigContent, getName());

if (contains) {

break;

}

newConfigContent = oldConfigContent + COMMA_SEPARATOR + getName();

}

// 在元資料中心建立mapping節點,並將暴露的服務資料存到元資料中心,這裡的元資料中心用zookeeper實現的

// 目錄:/dubbo/mapping/xxxService

// 資料:configItem。content為${application-name},configItem。ticket為版本好

success = metadataReport。registerServiceAppMapping(serviceInterface, DEFAULT_MAPPING_GROUP, newConfigContent, configItem。getTicket());

} while (!success && currentRetryTimes++ <= CAS_RETRY_TIMES);

});

}

3、ZookeeperMetadataReport#registerServiceAppMapping

public boolean registerServiceAppMapping(String key, String group, String content, Object ticket) {

try {

if (ticket != null && !(ticket instanceof Stat)) {

throw new IllegalArgumentException(“zookeeper publishConfigCas requires stat type ticket”);

}

String pathKey = buildPathKey(group, key);

// 1。 建立/dubbo/mapping/xxxService目錄,儲存的資料為configItem

// 2。 生成版本號

zkClient。createOrUpdate(pathKey, content, false, ticket == null ? 0 : ((Stat) ticket)。getVersion());

return true;

} catch (Exception e) {

logger。warn(“zookeeper publishConfigCas failed。”, e);

return false;

}

}

到這裡,暴露Triple協議的服務並註冊 registry 協議的流程就結束了。主要是將以前介面級服務發現機制中註冊到註冊中心中的資料(應用例項資料+服務介面資料)拆分出來了。註冊 registry 協議部分將應用例項資料註冊到註冊中心,在 Exporter 暴露完以後透過呼叫 MetadataUtils#publishServiceDefinition 將服務介面資料註冊到元資料中心。

4、暴露MetadataService服務

MetadataService 主要是對 Consumer 側提供一個可以獲取元資料的 API,暴露流程是複用了 Triple 協議的服務暴露流程

深入解析 Dubbo 3.0 服務端暴露全流程

整體時序圖

暴露 MetadataService 的入口

核心程式碼在 DubboBootstrap#exportMetadataService 中,具體如下:

private void exportMetadataService() {

// 暴露MetadataServer

metadataServiceExporter。export();

}

暴露 MetadataService

核心程式碼在

ConfigurableMetadataServiceExporter#export 中,具體如下:

public ConfigurableMetadataServiceExporter export() {

if (!isExported()) {

// 定義MetadataService的ServiceConfig

ServiceConfig serviceConfig = new ServiceConfig<>();

serviceConfig。setApplication(getApplicationConfig());

// 不會註冊到註冊中心

serviceConfig。setRegistry(new RegistryConfig(“N/A”));

serviceConfig。setProtocol(generateMetadataProtocol());

serviceConfig。setInterface(MetadataService。class);

serviceConfig。setDelay(0);

serviceConfig。setRef(metadataService);

serviceConfig。setGroup(getApplicationConfig()。getName());

serviceConfig。setVersion(metadataService。version());

serviceConfig。setMethods(generateMethodConfig());

// 用暴露Triple協議服務的流程來暴露MetadataService

// 採用的是Dubbo協議

serviceConfig。export();

this。serviceConfig = serviceConfig;

}

return this;

}

由於暴露 MetadataService 的流程是複用前面提到的暴露 Triple 協議服務的流程,整個過程有少許地方會不同,這些不同之處在上面的程式碼中都已經標明,所以就不再贅述了。

註冊 ServiceInstance 例項

註冊 ServiceInstance 的目的是為了定時更新 Metadata,當有更新的時候就會透過 MetadataReport 來更新版本號讓 Consumer 端感知到。

核心程式碼在 DubboBootstrap#registerServiceInstance 和 DubboBootstrap#doRegisterServiceInstance 中,具體如下:

private void registerServiceInstance() {

。。。。

// 建立ServiceInstance

// ServiceInstance中包含以下欄位

// 1。 serviceName:${application-name}

// 2。 host: 192。168。31。167

// 3。 port: 2080

// 4。 metadata: 服務介面級相關的資料,比如:methods等資料

// 同時,還會對ServiceInstance資料中的欄位進行補充,分別呼叫下面4個ServiceInstanceCustomizer例項

// 1)ServiceInstanceMetadataCustomizer

// 2)MetadataServiceURLParamsMetadataCustomizer

// 3)ProtocolPortsMetadataCustomizer

// 4)ServiceInstanceHostPortCustomizer

ServiceInstance serviceInstance = createServiceInstance(serviceName);

boolean registered = true;

try {

// 註冊ServiceInstance

doRegisterServiceInstance(serviceInstance);

} catch (Exception e) {

registered = false;

logger。error(“Register instance error”, e);

}

// 如果註冊成功,定時更新Metadata,沒10s更新一次

if(registered){

executorRepository。nextScheduledExecutor()。scheduleAtFixedRate(() -> {

……

try {

// 重新整理Metadata和ServiceInstance

ServiceInstanceMetadataUtils。refreshMetadataAndInstance(serviceInstance);

} catch (Exception e) {

……

} finally {

……

}

}, 0, ConfigurationUtils。get(METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY), TimeUnit。MILLISECONDS);

}

}

DubboBootstrap#doRegisterServiceInstance

private void doRegisterServiceInstance(ServiceInstance serviceInstance) {

if (serviceInstance。getPort() > 0) {

// 釋出Metadata資料到遠端儲存元資料中心

// 呼叫RemoteMetadataServiceImpl#publishMetadata,

// 內部會呼叫metadataReport#publishAppMetadata

publishMetadataToRemote(serviceInstance);

logger。info(“Start registering instance address to registry。”);

getServiceDiscoveries()。forEach(serviceDiscovery ->{

ServiceInstance serviceInstanceForRegistry = new DefaultServiceInstance((DefaultServiceInstance) serviceInstance);

calInstanceRevision(serviceDiscovery, serviceInstanceForRegistry);

……

// 呼叫ZookeeperServiceDiscovery#doRegister註冊serviceInstance例項

// 將應用服務資訊註冊到註冊中心中

// 目錄:/services/${application-name}/192。168。31。167:20800

// 資料:serviceInstance序列化後的byte陣列

serviceDiscovery。register(serviceInstanceForRegistry);

});

}

}

透過上面的分析,我們可以很容易知道

ServiceInstance 是中包含 Metadata

Metadata 是儲存在 InMemoryWritableMetadataService 中的元資料,佔用的是本地記憶體空間

InMemoryWritableMetadataService 用來更新 Metadata

ServiceInstance 是儲存在遠端元資料註冊中心中的資料結構

RemoteMetadataServiceImpl 會呼叫 metadataReport 將 ServiceInstance 資料更新到遠端元資料註冊中心

總結

透過對 Dubbo 3。0 服務端暴露全流程的解析可以看出,儘管應用級服務發現機制的實現要複雜很多,但是 Dubbo 3。0 為了能夠讓使用者平滑遷移,相容了 2。7。x 的版本,所以在設計的時候很多地方都儘可能複用之前的流程。

從最近 Dubbo 3。0 釋出的 Benchmark 資料來看,Dubbo 3。0 的效能和資源利用上確實提升了不少。Dubbo 3。0 在擁抱雲原生的道路上還有很長的一段路要走,社群正在對 Dubbo 3。0 中核心流程進行梳理和最佳化,後續計劃支援多例項應用部署,希望有興趣見證 Dubbo 雲原生之路的同學可以積極參與社群貢獻!

作者:熊聘,Github賬號pinxiong,Apache Dubbo貢獻者,關注RPC、Service Mesh和雲原生等領域。現任職於攜程國際事業部研發團隊,負責市場營銷、雲原生等相關工作。

原文連結

本文為阿里雲原創內容,未經允許不得轉載。