Dubbo 3.0サーバー露出の全プロセスの詳細な分析

Dubbo 3.0サーバー露出の全プロセスの詳細な分析

背景

クラウドネイティブ時代の到来に伴い、Dubbo 3.0 の重要な目標はクラウドネイティブを完全に採用することです。このため、クラウド ネイティブへの適応性を高めるために、Dubbo 3.0 では、元のインターフェイス レベルのサービス検出メカニズムをアプリケーション レベルのサービス検出メカニズムに進化させました。

アプリケーションレベルのサービス検出メカニズムに基づいて、Dubbo 3.0 はフレームワークによって発生する追加のリソース消費を大幅に削減し、リソース使用率を大幅に向上させることができます。これは主に次の点に反映されます。

単一マシンの常駐メモリが75%減少

数百万のクラスターインスタンスをサポートできるクラスター登録センターの全体的なデータ量は 90% 以上減少しました。
Dubbo サーバーの公開プロセスに関する技術記事は多数ありますが、それらはすべて Dubbo インターフェース レベルのサービス検出メカニズムに基づいて解釈されています。 Dubbo 3.0 のアプリケーション レベルのサービス検出メカニズムでは、サーバー公開プロセスが以前とは大きく変わりました。この記事では、Dubbo 3.0 のソースコードを理解することで、サーバーの公開プロセス全体を分析したいと考えています。

アプリケーションレベルのサービス検出とは何ですか?

簡単に言えば、Dubbo はすべてのインターフェース情報を登録センターに登録していましたが、アプリケーション インスタンスには通常複数のインターフェースがあるため、登録されるデータの量が非常に多く、冗長になっています。アプリケーション レベルのサービス検出のメカニズムでは、同じアプリケーション インスタンスが登録センターに 1 つのデータのみを登録します。このメカニズムは主に以下の問題を解決します。

Spring Cloudなどの主流のマイクロサービスモデルに合わせる
Kubernetes ネイティブ サービスをサポートします。 Kubernetes で維持およびスケジュールされるサービスはすべて、インターフェース レベルではなく、アプリケーション インスタンス レベルに基づいています。登録センターのデータ保存容量を削減し、住所変更プッシュの圧力を軽減します。アプリケーション dubbo-application が 3 つのインスタンス (instance1、instance2、instance3) を展開し、異なるタイムアウトが設定された 3 つのインターフェース (sayHello、echo、getVersion) を提供するとします。インターフェース レベルとアプリケーション レベルのサービス検出メカニズムでは、登録センターに登録されるデータは完全に異なります。次の図に示すように:

インターフェースレベルのサービス検出メカニズムのレジストリ内のデータ

  1. "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" "メタデータ" :{ "タイムアウト" 1000 }}、{ "アプリケーション" "dubbo-application" "名前" "インスタンス2" "ip" "127.0.0.2" "メタデータ" :{ "タイムアウト" 2000 }}、{ "アプリケーション" "dubbo-application" "名前" "インスタンス3" "ip" "127.0.0.3" "メタデータ" :{ "タイムアウト" 3000 }}]

アプリケーションレベルのサービス検出メカニズムのレジストリ内のデータ

  1. 「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 にあります。

  1. プライベート  void doStart() { // 1. Dubboサービスを公開する exportServices(); // コンシューマーインスタンスを登録するか、サービスをエクスポートした場合 if (isRegisterConsumerInstance() || hasExportedServices()) { // 2. メタデータ サービスを公開する exportMetadataService(); // 3. メタデータを定期的に更新してレポートする registerServiceInstance(); .... } ......}  

Zookeeper が Triple プロトコルの登録と公開のためのサービスとして使用されている場合、サーバー公開のプロセス全体のシーケンス図は次のようになります。

露出プロセス全体は非常に複雑で、次の 4 つの部分に分けられることがわかります。

injvm プロトコルのサービスを公開し、service-discovery-registry プロトコルを登録します。 Triple プロトコルのサービスを公開し、レジストリ プロトコルに登録します。 MetadataService サービスを公開します。以下では、これら 4 つの部分からサービス公開のプロセス全体を詳しく説明します。

1. injvmプロトコルサービスを公開する

injvm プロトコルのサービスはローカルに公開されます。主な理由は、アプリケーションにはサービス (公開されたサービス) と参照 (サービス参照) の両方が存在することが多く、参照によって参照されるサービスはアプリケーションで公開されるサービスであるためです。この使用シナリオをサポートするために、Dubbo はサービスをローカルで公開する injvm プロトコルを提供します。これにより、参照はネットワークを経由せずにローカルで直接サービスを呼び出すことができます。

全体的なタイミング図

この部分は以前のインターフェース レベルのサービス検出メカニズムと似ているため、関連するコア コードについてはここでは説明しません。

2. サービス検出レジストリプロトコルを登録する

サービス検出レジストリ プロトコルを登録する主な目的は、サービスに関連するメタデータを登録することです。デフォルトでは、メタデータは InMemoryWritableMetadataService を通じてローカル メモリとローカル ファイルに保存されます。

全体的なタイミング図

コア コードは、次のように ServiceConfig#exportRemote にあります。

サービス検出レジストリプロトコルのエントリを登録する

  1. private URL exportRemote(URL url, List<URL> registryURLs) { if (CollectionUtils.isNotEmpty(registryURLs)) { // レジストリが複数ある場合は、ループして各レジストリを登録します for (URL registryURL : registryURLs) { // サービス検出レジストリ プロトコルかどうかを判断します // サービス名マッピング パラメータの値を true に設定します if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) { url = url.addParameterIfAbsent(SERVICE_NAME_MAPPING_KEY, "true"); } ...... // サービス検出レジストリ プロトコル再利用サービス公開プロセスを登録します。doExportUrl(registryURL.putAttribute(EXPORT_KEY, url), true); } ...... URLを返します;}  

Invoker でのメタデータのパッケージ化

コア コードは、ServiceConfig#doExportUrl にあります。次のようになります。

  1. プライベート  void doExportUrl(URL url, boolean withMetaData) { Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); // この時点での withMetaData の値は true です // 呼び出し元を DelegateProviderMetaDataInvoker にラップします if (withMetaData) {invoker = new DelegateProviderMetaDataInvoker(invoker, this); } エクスポーター<?> exporter = PROTOCOL.export(invoker); exporters.add(エクスポーター);}  

RegistryProtocol 経由で Invoker を Exporter に変換する

コア コードは、次のように ProtocolListenerWrapper#export にあります。

  1. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { // この時点でのプロトコルは RegistryProtocol 型です if (UrlUtils.isRegistry(invoker.getUrl())) { return protocol.export(invoker); } ......}  

RegistryProtocol は Invoker を Exporter コアプロセスに変換します

コア コードは、次のように RegistryProtocol#export にあります。

  1. public <T> Exporter<T> export( final Invoker<T> originInvoker) throws RpcException { URL registryUrl = getRegistryUrl(originInvoker); URL プロバイダーUrl = getProviderUrl(originInvoker); ...... // Triple プロトコルのサービスを再度公開します final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // registryUrl には、サービス検出レジストリ プロトコルが含まれています // このプロトコルを使用して ServiceDiscoveryRegistry オブジェクトを作成します // 次に、RegistryServiceListener リスナーを結合します // 最後に、それを ListenerRegistryWrapper オブジェクトにラップします final Registry registry = getRegistry(registryUrl);最終 URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);ブール値 register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { // service-discovery-registry プロトコルを登録します // RegistryServiceListener の onRegister イベントをトリガーします register(registry, registeredProviderUrl); } ...... // RegistryServiceListener の onRegister イベントをトリガーします。notifyExport(exporter);新しい DestroyableExporter<>(exporter) を返します。}  

トリプルプロトコルを公開するサービス

コア コードは、次のように RegistryProtocol#doLocalExport にあります。

  1. private <T> ExporterChangeableWrapper<T> doLocalExport( final Invoker<T> originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); // この時点でのプロトコルは、Triple プロトコルのプロキシ クラスです // injvm プロトコルを公開する PROTOCOL と同じです return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker); });}  

サービス検出レジストリプロトコルを登録する

コア コードは、次のように ServiceDiscoveryRegistry#register と ServiceDiscoveryRegistry#doRegister にあります。

1.サービスディスカバリレジストリ#登録

  1. 公共 ファイナル  void register(URL url) { // サーバー (プロバイダー) のみを登録する必要があります if (!shouldRegister(url)) { return; } // サービス検出レジストリ プロトコルを登録します。doRegister(url);}  

2. サービスディスカバリレジストリ#doRegister

  1. 公共  void doRegister(URL url) { url = addRegistryClusterKey(url); // メタデータを登録する if (writableMetadataService.exportURL(url)) { if (logger.isInfoEnabled()) { logger.info(format("URL[%s] が正常に登録されました。", url.toString())); } } else { if (logger.isWarnEnabled()) { logger.warn(format("URL[%s]が登録されました。", url.toString())); } }}  

登録メタデータ

コア コードは、次のように InMemoryWritableMetadataService#exportURL にあります。

  1. 公共  boolean exportURL(URL url) { // MetadataService の場合、メタデータは登録されません if (MetadataService.class.getName().equals(url.getServiceInterface())) { this.metadataServiceURL = url; true を返します。 } updateLock.readLock().lock();試してください { String[] clusters = getRegistryCluster(url).split(","); (String cluster : clusters) の場合 { MetadataInfo metadataInfo = metadataInfos.computeIfAbsent(cluster, k -> new MetadataInfo(ApplicationModel.getName())); // トリプル プロトコル サービスのインターフェイス関連データから ServiceInfo を生成します // ServiceInfo を MetadataInfo に登録します metadataInfo.addService(new ServiceInfo(url)); } メタデータセマフォをリリースします。 addURL(エクスポートされたServiceURLs、url)を返します。 } 最後に { updateLock.readLock().unlock(); }}  

onRegisterイベントを公開

コア コードは、次のように ListenerRegistryWrapper#register にあります。

  1. 公共  void register(URL url) { try { // registry は ServiceDiscoveryRegistry オブジェクトです // この時点で ServiceDiscoveryRegistry#registry メソッドが呼び出されています registry.register(url); } 最後に { if (CollectionUtils.isNotEmpty(listeners) && !UrlUtils.isConsumer(url)) { RuntimeException 例外 = null; for (RegistryServiceListener listener : listeners) { if (listener != null) { try { // service-discovery-registry プロトコルを登録した後に onRegister イベントを発行します listener.onRegister(url, registry); } キャッチ (RuntimeException t) { logger.error(t.getMessage(), t);例外 = t; } } } if (exception != null) { 例外をスローします; } } }}  

サービス登録イベントを公開する

コア コードは、次のように RegistryProtocol#notifyExport にあります。

  1. private <T> void notificationExport(ExporterChangeableWrapper<T> exporter) { List<RegistryProtocolListener> 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); } }}  

サービス検出レジストリ プロトコルを登録する主な目的は、サービス インターフェイスに関連する情報をメモリに保存することであることがわかります。互換性とスムーズな移行を考慮して、コミュニティは実装時に ServiceConfig の公開プロセスを再利用するというアプローチを採用しました。

3. トリプルプロトコルサービスを公開し、レジストリプロトコルを登録する

トリプル プロトコル サービスの公開とレジストリ プロトコルの登録は、Dubbo 3.0 サービス公開のコア プロセスであり、次の 2 つの部分に分かれています。

トリプルプロトコルを公開するサービス

レジストリ プロトコルの登録 Triple プロトコル サービスを公開するプロセスは、Injvm プロトコル サービスを公開するプロセスと同じなので、詳細には説明しません。レジストリ プロトコルを登録するプロセスでは、アプリケーション インスタンスに関連する情報のみが登録されます。これは、前述のアプリケーション レベルのサービス検出メカニズムです。

全体的なタイミング図

InterfacecompatibleRegistryProtocol 経由で Invoker を Exporter に変換する

コア コードは、次のように ProtocolListenerWrapper#export にあります。

  1. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { // この時点でのプロトコルは InterfacecompatibleRegistryProtocol タイプです (RegistryProtocol を継承します) // 注: service-discovery-registry プロトコルを登録する場合、プロトコルは RegistryProtocol タイプです if (UrlUtils.isRegistry(invoker.getUrl())) { return protocol.export(invoker); } ......}  

RegistryProtocol は Invoker を Exporter コアプロセスに変換します

コア コードは、次のように RegistryProtocol#export にあります。

  1. public <T> Exporter<T> export( final Invoker<T> originInvoker) throws RpcException { URL registryUrl = getRegistryUrl(originInvoker); URL プロバイダーUrl = getProviderUrl(originInvoker); ...... // Triple プロトコルのサービスを再度公開します final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // registryUrl にはレジストリ プロトコルが含まれます // このプロトコルを使用して ZookeeperRegistry オブジェクトを作成します // 次に RegistryServiceListener リスナーを結合します // 最後にそれを ListenerRegistryWrapper オブジェクトにラップします // 注: // 1. service-discovery-registry プロトコルは ServiceDiscoveryRegistry に対応します // 2. registry プロトコルは ZookeeperRegistry に対応します final Registry registry = getRegistry(registryUrl);最終 URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);ブール値 register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { // レジストリ プロトコルを登録する // RegistryServiceListener の onRegister イベントをトリガーする register(registry, registeredProviderUrl); } ...... // RegistryProtocolListener の onExport イベントを公開します。notifyExport(exporter);新しい DestroyableExporter<>(exporter) を返します。}  

レジストリプロトコルを登録する

コア コードは、次のように FailbackRegistry#register と ServiceDiscoveryRegistry#doRegister (ZookeeperRegistry は FailbackRegistry を継承) にあります。

1. フェイルバックレジストリ#register

  1. 公共  void register(URL url) { if (!acceptable(url)) { ...... try { // レジストリプロトコルを登録する doRegister(url); } キャッチ (例外 e) { ...... } }}  

2. サービスディスカバリレジストリ#doRegister

  1. 公共  void doRegister(URL url) { try { // プロバイダーを Zookeeper に登録 // ディレクトリ: /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("" + url + " を Zookeeper " + getUrl() + " に登録できませんでした。原因: " + e.getMessage(), e); }}  

購読アドレスの変更

コア コードは、次のように FailbackRegistry#subscribe と ZookeeperRegistry#doSubscribe にあります。

1. フェイルバックレジストリ#subscribe

  1. 公共  void subscribe(URL url, NotificationListener listener) { ...... try { // ZookeeperRegistry#doSubscribe を呼び出します。doSubscribe(url, listener); } 例外 e をキャッチします { ......}  

2. Zookeeperレジストリ#doSubscribe

  1. 公共  void doSubscribe(最終URL url、最終NotificationListener リスナー) { try { if (ANY_VALUE.equals(url.getServiceInterface())) { ...... } else { ...... for (String path : toCategoriesPath(url)) { ConcurrentMap<NotifyListener、ChildListener> listeners = zkListeners.computeIfAbsent(url、k -> new ConcurrentHashMap<>());子リスナー zkListener = listeners.computeIfAbsent(リスナー、k ->新しいRegistryChildListenerImpl(url、パス、k、ラッチ)); zkListener のinstanceof RegistryChildListenerImpl の場合、((RegistryChildListenerImpl) zkListener).setLatch(latch); } // コンフィギュレータ データを保存するための一時ノードを作成します // ディレクトリ:/dubbo/xxxService/configurators // データ: dubbo-admin で変更できるアプリケーション構成情報。デフォルト値は空です。 zkClient.create(パス、false); // コンフィギュレータの変更を監視するリスナーを追加します。List<String> children = zkClient.addChildListener(path, zkListener);子要素が null の場合、 urls.addAll(toUrlsWithEmpty(url, path, children)); } } ...... } } キャッチ (Throwable e) { ...... }  

公開されたトリプル プロトコル サービスとメタデータ間の接続を確立するためのコア コードは、次のように、ServiceConfig#exportUrl、MetadataUtils#publishServiceDefinition、InMemoryWritableMetadataService#publishServiceDefinition、RemoteMetadataServiceImpl#publishServiceDefinition、および MetadataReport#storeProviderMetadata にあります。

1. サービス構成#エクスポートUrl

  1. プライベート  void exportUrl(URL url, List<URL> registryURLs) { ...... if (!SCOPE_NONE.equalsIgnoreCase(scope)) { ...... if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) { url = exportRemote(url, registryURLs); // サービス インターフェイスに関連するイベントを公開し、データを更新します。MetadataUtils.publishServiceDefinition(url); } } ......}  

2. メタデータユーティリティ#publishServiceDefinition

  1. 公共 静的  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

  1. 公共  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 = ServiceDefinitionBuilder.build(interfaceClass); Gson を新しいGson() に変換します。文字列データ = 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);戻る; } そうでない場合、(CONSUMER_SIDE.equalsIgnoreCase(url.getParameter(SIDE_KEY))) { ...... } ...... } catch (Throwable e) { ...... }}  

4. リモートメタデータサービス実装#publishServiceDefinition

  1. 公共  void publishServiceDefinition(URL url) { checkRemoteConfigured();文字列サイド = url.getSide(); if (PROVIDER_SIDE.equalsIgnoreCase(side)) { // サーバー (プロバイダー) のサービス インターフェース情報をメタデータ センターに公開します publishProvider(url); } else { ...... }}RemoteMetadataServiceImpl#publishProviderprivate void publishProvider(URL providerUrl) throws RpcException { ...... try { String interfaceName = providerUrl.getServiceInterface(); if (StringUtils.isNotEmpty(interfaceName)) { ...... for (Map.Entry<String, MetadataReport> entry : getMetadataReports().entrySet()) { // メタデータ センターへのアクセスに使用される MetadataReport サービスを取得します。MetadataReport metadataReport = entry.getValue(); // サービス インターフェイス情報をメタデータ センターに保存します。metadataReport.storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(), providerUrl.getVersion(), providerUrl.getGroup(), PROVIDER_SIDE, providerUrl.getApplication()), fullServiceDefinition); } 戻る; } ...... } (ClassNotFoundException e) をキャッチ { ...... }}  

5. 抽象メタデータレポート#storeProviderMetadata

  1. 公共  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);プロバイダーメタデータ識別子の削除に失敗しました。 Gson を新しい Gson() に変換します。 // データ形式: // { // "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); } キャッチ (例外 e) { ...... }}  

Triple プロトコル サービスと MetadataReport サービスの関係を確立するためのコア コードは、次のように、ServiceConfig#exported、MetadataServiceNameMapping#map、および ZookeeperMetadataReport#registerServiceAppMapping にあります。

1. ServiceConfig#エクスポート

  1. 保護された  void exported() { exported = true ;リスト<URL> exportedURLs = this .getExportedUrls(); exportedURLs.forEach(url -> { // URL が service-name-mapping フィールドでマークされているかどうかを判断します // このフィールドでマークされたサービスは、公開されたサービスをメタデータ センターに関連付ける必要があります // コンシューマーは、メタデータ センターのメッセージの変更を通じて、プロバイダー側​​のメタデータの変更を認識できます if (url.getParameters().containsKey(SERVICE_NAME_MAPPING_KEY)) { ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension(); // リレーションシップを確立します serviceNameMapping.map(url); } });エクスポートされた();}  

2. メタデータサービス名マッピング#map

  1. 公共  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(); } // マッピング ノードを作成しますメタデータ センターで、公開されたサービス データをメタデータ センターに保存します。ここでのメタデータ センターは、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. Zookeeperメタデータレポート#registerServiceAppMapping

  1. 公共 ブール型registerServiceAppMapping(文字列キー、文字列グループ、文字列コンテンツ、オブジェクトチケット) { try { if (ticket != null && !(ticket instanceof Stat)) { throw  新しいIllegalArgumentException( "zookeeper publishConfigCas には統計タイプのチケットが必要です" ); } 文字列 pathKey = buildPathKey(グループ、キー); // 1. /dubbo/mapping/xxxService ディレクトリを作成し、データを configItem として保存します。 // 2. バージョン番号を生成します。 zkClient.createOrUpdate(pathKey, content, false, ticket == null ? 0 : ((Stat) ticket).getVersion()); true を返します。 } catch (例外 e) { logger.warn("zookeeper publishConfigCas が失敗しました。", e); false を返します。 }}  

この時点で、トリプル プロトコル サービスを公開し、レジストリ プロトコルを登録するプロセスは完了です。主なことは、以前のインターフェース レベルのサービス検出メカニズムで登録センターに登録されたデータ (アプリケーション インスタンス データ + サービス インターフェース データ) を分割することです。登録レジストリプロトコル部分は、アプリケーションインスタンスデータを登録センターに登録します。エクスポーターが公開された後、MetadataUtils#publishServiceDefinition を呼び出すことによって、サービス インターフェイス データがメタデータ センターに登録されます。

4. MetadataService を公開する

MetadataService は主に、コンシューマー側にメタデータを取得するための API を提供します。公開プロセスは、トリプル プロトコルのサービス公開プロセスを再利用します。

全体的なタイミング図

MetadataServiceエントリを公開する

コアコードは DubboBootstrap#exportMetadataService にあります。次のようになります。

  1. プライベート  void exportMetadataService() { // MetadataServer を公開します。metadataServiceExporter.export();}  

MetadataService の公開

コア コードは、次のように ConfigurableMetadataServiceExporter#export にあります。

  1. } public ConfigurableMetadataServiceExporter export() { if (!isExported()) { // MetadataService の ServiceConfig を変更します ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>(); serviceConfig.setApplication(getApplicationConfig()); // MetadataService の ServiceConfig を設定します。serviceConfig.setRegistry(new RegistryConfig("N/A")); serviceConfig.setProtocol(メタデータプロトコルを生成()); serviceConfig.setInterface(MetadataService.class); serviceConfig.setDelay(0); serviceConfig.setRef(メタデータサービス); serviceConfig.setGroup(getApplicationConfig().getName()); serviceConfig.setVersion(metadataService.version()); serviceConfig.setMethods(メソッドConfigを生成) // 露出米データデータ服务过程露出多件协议服务服务// 使用Dubbo协议服务 

MetadataService を公開するプロセスは、前述の Triple protocol サービスを公開するプロセスを再利用するため、プロセス全体にいくつかの違いがあります。これらの違いは上記のコードでマークされているため、ここでは繰り返さないことにします。

ServiceInstance の登録

ServiceInstance を登録する目的は、メタデータを定期的に更新することです。アップデートがあった場合、コンシューマー側が認識できるように、MetadataReport を通じてバージョン番号が更新されます。

コア コードは、次のように DubboBootstrap#registerServiceInstance と DubboBootstrap#doRegisterServiceInstance にあります。

  1. プライベート  void registerServiceInstance() { .... // ServiceInstance を構築します // ServiceInstance には、次のフィールドが含まれます // 1. serviceName:${application-name} // 2. host: 192.168.31.167 // 3. port: 2080 // 4. metadata: メソッドなどのサービス インターフェース レベルに関するデータ // 同時に、ServiceInstance データ内のフィールドが補完され、次の 4 つの ServiceInstanceCustomizer インスタンスがそれぞれ呼び出されます // 1)ServiceInstanceMetadataCustomizer // 2)MetadataServiceURLParamsMetadataCustomizer // 3)ProtocolPortsMetadataCustomizer // 4)ServiceInstanceHostPortCustomizer ServiceInstance serviceInstance = createServiceInstance(serviceName);ブール値登録済み = true; try { // ServiceInstance を登録します doRegisterServiceInstance(serviceInstance); } catch (例外 e) { registered = false; logger.error("インスタンス登録エラー", e); } // 登録が成功した場合は、10 秒ごとにメタデータを定期的に更新します。 if(registered){ executorRepository.nextScheduledExecutor().scheduleAtFixedRate(() -> { ... try { // メタデータと ServiceInstance を更新します。 ServiceInstanceMetadataUtils.refreshMetadataAndInstance(serviceInstance); } catch (Exception e) { ... } finally { ... } }, 0, ConfigurationUtils.get(METADATA_PUBLISH_DELAY_KEY, DEFAULT_METADATA_PUBLISH_DELAY), TimeUnit.MILLISECONDS); }}  

DubboBootstrap#doRegisterServiceInstance

  1. プライベート  : : : : : : : : : : : : : : : serviceDiscovery.register(serviceInstanceForRegistry); }); }}  

上記の分析により、簡単に知ることができる

ServiceInstanceはメタデータに含まれています
メタデータは、ローカル メモリ領域を占有する InMemoryWritableMetadataService に保存されるメタデータです。
InMemoryWritableMetadataServiceはメタデータの更新に使用されます
ServiceInstanceはリモートメタデータレジストリに保存されるデータ構造です
RemoteMetadataServiceImpl は、メタデータレポートを呼び出して、ServiceInstance データをリモート メタデータ登録センターに更新します。

要約する

Dubbo 3.0サーバーの公開プロセス全体を分析すると、アプリケーションレベルのサービス検出メカニズムの実装ははるかに複雑ですが、ユーザーがスムーズに移行できるように、Dubbo 3.0は2.7.xバージョンと互換性があり、設計では多くの箇所が可能な限り再利用されていることがわかります。

Dubbo 3.0 が最近リリースしたベンチマーク データから判断すると、Dubbo 3.0 のパフォーマンスとリソース使用率は確かに大幅に向上しています。 Dubbo 3.0 がクラウド ネイティブを採用するまでには、まだ長い道のりが残っています。コミュニティは、Dubbo 3.0 のコア プロセスを整理し、最適化しています。後続のプランでは、複数インスタンスのアプリケーションの展開がサポートされます。 Dubbo のクラウド ネイティブ パスを目の当たりにすることに興味のある学生が、コミュニティへの貢献に積極的に参加できることを願っています。

<<:  Zookeeper における Kafka のデータ構造を完全に説明する図

>>:  クラウドウェブホスティングのメリット

推薦する

クラウドに移行する前に実行すべき 6 つのステップ

[[276793]] [51CTO.com クイック翻訳] あなたのビジネスはクラウドに移行する準備...

テクニカルウェブマスターになる方法

多くのウェブマスターはマーケティング志向のウェブマスターで、ウェブサイトを宣伝するためにあらゆる手段...

高品質なユーザーエクスペリエンスは、ユーザーと仲良くなることを学ぶべきである

ユーザーエクスペリエンスに関して、多くの友人は、高品質のユーザーエクスペリエンスをどのように構築する...

ビッグデータは本当にクラウドコンピューティングへの道を急速に進んでいるのでしょうか?

ソフトウェア スタートアップの AtScale は、昨年末に毎年恒例のビッグ データ成熟度調査 (旧...

SEO の 3 つのステップ: SEO をより効果的にする

SEO 最適化の質によって、サイトが検索エンジンからどれだけのトラフィックを獲得できるかが決まります...

Google の SEO ガイド: 分析ツール

ウェブサイト分析ツールを最大限に活用するGoogle ウェブマスター ツールやその他のサービスを使用...

検索エンジンに新しいサイトを素早く組み込む方法を共有する

新しいウェブサイトを持っている友人の中には、半月や1か月も待たずに、主要な検索エンジンで新しいウェブ...

UFIDA Smart Star:デジタル変革の波の中で企業の「中枢脳」をどう構築するか?

中国の企業サービス市場は、初期の情報化段階から現在のデジタル化に至るまで、1980年代に発展し始めま...

検索エンジンの長期的成功の秘密:ユーザー当たりの収益が同業他社よりはるかに高い

[要約] インターネットビジネスの収益貢献度という点では、検索エンジンは他のメディアやソーシャルネッ...

SEOはウェブサイト運営の成功に関係しています。仮想ホストの選択に注意する必要があります

SEO がウェブサイトの運用に与える影響については多くの人が知っていると思いますが、仮想ホスティング...

gcorelabs: イスラエルで最も安い VPS、月額 3.25 ユーロから、KVM 仮想化/512M メモリ/20g SSD/2T トラフィック/200M 帯域幅

gcorelabs は、地中海に近いイスラエル第 2 の都市テルアビブにデータセンターを追加しました...

プロモーションをシンプルかつシンプルかつ強力にする 11 月 11 日のマーケティングのヒント

月収10万元の起業の夢を実現するミニプログラム起業支援プランダブルイレブンが近づくにつれ、さまざまな...

Baidu の入札統計の違い、トラフィック損失はどこにありますか?

トラフィック統計の差異の問題は、常に多くの入札ユーザーを悩ませてきました。 Google であれ B...

nodeserv-new hen、$13/年/512MB メモリ/100GB ハード ドライブ/1TB トラフィック

nodeserv.com は 2009 年に設立され、独自の IP (AS19757) を持っていま...

女性ユーザーを獲得できるのは誰か?消費者の共有コミュニティについての簡単な考察

新時代の女性として、外見上の女性らしさを際限なく追求することなく、男性のような独立した心構えを持つこ...