[ソースが一緒に学ぶ - マイクロサービス] Nexflixユーレカ出典8:EurekaClientレジストリクロール絶妙なデザインと分析!

序文

先行レビュー

私たちは、EurekaClientがサーバ側に登録されている方法を整理してユニットテストで話すと、サーバは、最も重要な問題は、レジストリのデータ構造である場合、処理された要求を受信する方法:ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>()

この講義ディレクトリ

次回のブログではなく、各ソースの総カタログ説明が直接分析される前に、私は振り返りました。記事の冒頭には、ディレクトリを追加し、最後になるので、経験の概要を追加して、この記事のソースを読み込みます。私はこの記事のブログのシリーズが良くなって願っています。

次のように内容は以下のとおりです。

  1. 最初の登録クロールレジストリのクライアント側のロジック全額
  2. サーバーの応答マルチレベルキャッシュメカニズムレジストリ情報の収集
  3. サーバー側のレジストリマルチレベルキャッシュはメカニズムを期限切れにアクティブ+のパッシブ+時限
  4. クライアント側のロジックレジストリ増分クロール

技術的なハイライト:

  1. レジストリクロールマルチレベルキャッシュメカニズム
  2. 増分によって返されるデータの総量は、データの整合性を保証するためにハッシュコード、ハッシュコードコントラストとローカルデータを、クロール

TucaoロジックEurekaClientは今日、登録されるまで少しは、ここで長いったらしい後のデザインのロジックEurekaClientレジストリクロール、できないヘルプ嘆くの機微を見て、ここで言っEurekaServerは、レジストリのための絶妙なデザインを終了指しロジックを読みます、判定ハッシュ増分取得のロジックと一貫性をキャッシュし、本当に素晴らしい、私は彼らが多くのことを学んだ気がします。早朝に非常に興奮し、ハッハッハ、このコードを読んだ後、それを見てみましょう。

説明

元は容易ではない、転載は、ソースを明記してください必要があります。花はロマンチックなカウント

EurekaClient全額クロールレジストリロジック

私は、これは新しいモデルも、最初に描画し、ソースコード、および解釈を使用して、表現するコードワードを読んだ後、自分の理解を置く方法を考えています。

04_EurekaClientレジストリ全体量クロールロジック.PNG

絵のルックスは非常に単純な、クライアントのHTTPリクエストはクライアント側の全額にサーバー側のレジストリ情報のリターン、サーバー側に送信されます。次のステップは、一般的な印象を持っているために、ここで、コードのステップ解析によるステップに従うことです

ソース決意

  1. クライアントは、レジストリの総量を取得するためのリクエストを送信します
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {

    // 省略很多无关代码

    if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
        fetchRegistryFromBackup();
    }

}

private boolean fetchRegistry(boolean forceFullRegistryFetch) {
    Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

    try {
        // If the delta is disabled or if it is the first time, get all
        // applications
        Applications applications = getApplications();

        if (clientConfig.shouldDisableDelta()
                || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                || forceFullRegistryFetch
                || (applications == null)
                || (applications.getRegisteredApplications().size() == 0)
                || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
        {
            logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
            logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
            logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
            logger.info("Application is null : {}", (applications == null));
            logger.info("Registered Applications size is zero : {}",
                    (applications.getRegisteredApplications().size() == 0));
            logger.info("Application version is -1: {}", (applications.getVersion() == -1));
            getAndStoreFullRegistry();
        } else {
            getAndUpdateDelta(applications);
        }
        applications.setAppsHashCode(applications.getReconcileHashCode());
        logTotalInstances();
    } catch (Throwable e) {
        logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e);
        return false;
    } finally {
        if (tracer != null) {
            tracer.stop();
        }
    }

    // 删减掉一些代码

    // registry was fetched successfully, so return true
    return true;
}

private void getAndStoreFullRegistry() throws Throwable {
    long currentUpdateGeneration = fetchRegistryGeneration.get();

    logger.info("Getting all instance registry info from the eureka server");

    Applications apps = null;
    EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
            ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
            : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
    if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
        apps = httpResponse.getEntity();
    }
    logger.info("The response status is {}", httpResponse.getStatusCode());

    if (apps == null) {
        logger.error("The application is null for some reason. Not storing this information");
    } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
        localRegionApps.set(this.filterAndShuffle(apps));
        logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
    } else {
        logger.warn("Not updating applications as another thread is updating it already");
    }
}

ここでは繰り返しませんクライアント端末がコードを要求してどのようにユニットテストの前にコードが要求を受け入れられているサーバ・クラスの終わりをクリアしているので、ステップバイステップに従うことでApplicationsResource.java、クライアント側のメインコアコードがあるDiscoveryClient.javaで。

私たちは分析する必要がどこコードや先祖のコードの前に何回も読んでますが、コンテンツの多くを省略は、のみ表示されます。
clientConfig.shouldFetchRegistry()デフォルトの設定では、その後、真であるfetchRegistry方法getAndStoreFullRegistry()最初は全額レジストリ情報を得ることであるので、次に進みます。

getAndStoreFullRegistry メソッドは、HTTPリクエストがサーバー側に送信され見て、その後、全額Serverのレジストリ情報を返すために終了を待つことができます。

ここでは、要求の全額が実行されるためeurekaTransport.queryClient.getApplications(remoteRegionsRef.get())

次いで、すべての方法トラックまでAbstractJersey2EurekaHttpClient.javagetApplicationsInternal方法、送信が送られるGET要求は、サーバ側ApplicationsResource.javaGET方法getContainersのロジックを見るのを

サーバーの応答マルチレベルキャッシュメカニズムレジストリ情報の収集

すでに見クロールロジック・クライアントは、サーバーの側面図に、レジストリの全体量を送りとしてApplicationsResource.javaGET方法getContainersは、このセクションのソースを見て

private final ResponseCache responseCache;

@GET
public Response getContainers(@PathParam("version") String version,
                              @HeaderParam(HEADER_ACCEPT) String acceptHeader,
                              @HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
                              @HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
                              @Context UriInfo uriInfo,
                              @Nullable @QueryParam("regions") String regionsStr) {

    // 省略部分代码

    Key cacheKey = new Key(Key.EntityType.Application,
            ResponseCacheImpl.ALL_APPS,
            keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
    );

    Response response;
    if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
        response = Response.ok(responseCache.getGZIP(cacheKey))
                .header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
                .header(HEADER_CONTENT_TYPE, returnMediaType)
                .build();
    } else {
        response = Response.ok(responseCache.get(cacheKey))
                .build();
    }
    CurrentRequestVersion.remove();
    return response;
}

この要求のクライアント端末を受け取った後、私達は行くだろうresponseCacheデータの総量を取ることに。
プロパティ名を見ることができますから、これはキャッシュからデータを取得することです。

ResponseCacheImpl.java

String get(final Key key, boolean useReadOnlyCache) {
    Value payload = getValue(key, useReadOnlyCache);
    if (payload == null || payload.getPayload().equals(EMPTY_PAYLOAD)) {
        return null;
    } else {
        return payload.getPayload();
    }
}

Value getValue(final Key key, boolean useReadOnlyCache) {
    Value payload = null;
    try {
        if (useReadOnlyCache) {
            final Value currentPayload = readOnlyCacheMap.get(key);
            if (currentPayload != null) {
                payload = currentPayload;
            } else {
                payload = readWriteCacheMap.get(key);
                readOnlyCacheMap.put(key, payload);
            }
        } else {
            payload = readWriteCacheMap.get(key);
        }
    } catch (Throwable t) {
        logger.error("Cannot get value for key : {}", key, t);
    }
    return payload;
}

ここでの主な関心事getValueの方法は、ここでの主なマップ、1が2つ存在しているreadOnlyCacheMap他のreadWriteCacheMap私たちは名前を見て、知ることができるが、読み取り専用キャッシュ、読み書きキャッシュ、二つの層を持つキャッシュ構造、読み取り専用の場合ありキャッシュは、クエリキャッシュが空の場合は、直接読める返す空ではありません。

私たちは、見下すし続けるキャッシュについて説明します。

サーバー側のレジストリマルチレベルキャッシュはメカニズムを期限切れにアクティブ+のパッシブ+時限

ここでは、マルチレベルのキャッシュを使用していますあなたには、いくつかの質問があるかもしれませんが、関連するキャッシュを見て続行します。

  1. どのように2つのキャッシュデータの同期?
  2. どのようにキャッシュデータの有効期限が切れ?

質問で、私たちは、ソースコードを見て続けなければなりません

private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();
private final LoadingCache<Key, Value> readWriteCacheMap;

ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
    // 省略部分代码

    long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
    this.readWriteCacheMap =
            CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
                    .expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
                    .removalListener(new RemovalListener<Key, Value>() {
                        @Override
                        public void onRemoval(RemovalNotification<Key, Value> notification) {
                            Key removedKey = notification.getKey();
                            if (removedKey.hasRegions()) {
                                Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
                                regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
                            }
                        }
                    })
                    .build(new CacheLoader<Key, Value>() {
                        @Override
                        public Value load(Key key) throws Exception {
                            if (key.hasRegions()) {
                                Key cloneWithNoRegions = key.cloneWithoutRegions();
                                regionSpecificKeys.put(cloneWithNoRegions, key);
                            }
                            Value value = generatePayload(key);
                            return value;
                        }
                    });

    // 省略部分代码

}
  1. readOnlyCacheMapConcurrentHashMapの、スレッドセーフを使用します。
    readWriteCacheMapGuavaCacheを使用した小型のパートナーは以下を読むことができる自分自身を理解していない、私の以前のブログでも、これを説明する必要があり、構造がマップGoogleのオープンソースプロジェクトグアバメモリベースのキャッシュが、また、その内部実装です。

  2. GuavaCacheで私達の表情の主な焦点は、ここでは初期サイズでserverConfig.getInitialCapacityOfResponseCache()も、元のサイズの地図、1000にデフォルト設定されています。
    expireAfterWriteリフレッシュ時間は、serverConfig.getResponseCacheAutoExpirationInSeconds()デフォルトの時間は180秒です。
    ビルド方法に続いて、レジストリ情報は、ここで使用されますgeneratePayloadレジストリ内のクエリreadWriteCacheMap情報が空の場合、それはビルド方法を実行します、方法。

フォローアップを続行generatePayloadする方法を:

private Value generatePayload(Key key) {
    Stopwatch tracer = null;
    try {
        String payload;
        switch (key.getEntityType()) {
            case Application:
                boolean isRemoteRegionRequested = key.hasRegions();

                if (ALL_APPS.equals(key.getName())) {
                    if (isRemoteRegionRequested) {
                        tracer = serializeAllAppsWithRemoteRegionTimer.start();
                        payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));
                    } else {
                        tracer = serializeAllAppsTimer.start();
                        payload = getPayLoad(key, registry.getApplications());
                    }
                } else if (ALL_APPS_DELTA.equals(key.getName())) {
                    if (isRemoteRegionRequested) {
                        tracer = serializeDeltaAppsWithRemoteRegionTimer.start();
                        versionDeltaWithRegions.incrementAndGet();
                        versionDeltaWithRegionsLegacy.incrementAndGet();
                        payload = getPayLoad(key,
                                registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
                    } else {
                        tracer = serializeDeltaAppsTimer.start();
                        versionDelta.incrementAndGet();
                        versionDeltaLegacy.incrementAndGet();
                        payload = getPayLoad(key, registry.getApplicationDeltas());
                    }
                }
                break;
        }
        return new Value(payload);
    } finally {
        if (tracer != null) {
            tracer.stop();
        }
    }
}

このコードは、このロジックに行きます、レジストリへの増分クロールの削除の一部でありALL_APPS、キャプチャの総量である、ALL_APPS_DELTA第1の眼を挿入増分クロールの意味は、レジストリは、増分クロールロジックになります振り返ります。

我々は唯一の注意を払うに必要以上のロジックregistry.getApplicationsFromMultipleRegionsには、これは論理的なレジストリを得ることです。そして、コードでダウンを続行:

AbstractInstanceRegistry.java

public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) {

    Applications apps = new Applications();
    apps.setVersion(1L);
    for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {
        Application app = null;

        if (entry.getValue() != null) {
            for (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) {
                Lease<InstanceInfo> lease = stringLeaseEntry.getValue();
                if (app == null) {
                    app = new Application(lease.getHolder().getAppName());
                }
                app.addInstance(decorateInstanceInfo(lease));
            }
        }
        if (app != null) {
            apps.addApplication(app);
        }
    }
    if (includeRemoteRegion) {
        for (String remoteRegion : remoteRegions) {
            RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
            if (null != remoteRegistry) {
                Applications remoteApps = remoteRegistry.getApplications();
                for (Application application : remoteApps.getRegisteredApplications()) {
                    if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
                        logger.info("Application {}  fetched from the remote region {}",
                                application.getName(), remoteRegion);

                        Application appInstanceTillNow = apps.getRegisteredApplications(application.getName());
                        if (appInstanceTillNow == null) {
                            appInstanceTillNow = new Application(application.getName());
                            apps.addApplication(appInstanceTillNow);
                        }
                        for (InstanceInfo instanceInfo : application.getInstances()) {
                            appInstanceTillNow.addInstance(instanceInfo);
                        }
                    } else {
                        logger.debug("Application {} not fetched from the remote region {} as there exists a "
                                        + "whitelist and this app is not in the whitelist.",
                                application.getName(), remoteRegion);
                    }
                }
            } else {
                logger.warn("No remote registry available for the remote region {}", remoteRegion);
            }
        }
    }
    apps.setAppsHashCode(apps.getReconcileHashCode());
    return apps;
}

ここで確認しregistry.entrySet()、それが特に誠心誠意になりませんか?Map<String, Map<String, Lease<InstanceInfo>>私たちは、クライアントここでは、このデータ構造に対応し、案の定は、すべての登録情報を取得することをレジストリに登録情報に登録して、カプセル化された上での話Applicationsオブジェクトを。

ここでは最後にapps.setAppsHashCode()目の背後にあるロジックは、同様な応力増分同期ロジックを挿入するために、後で振り返ります。そして、データ復帰後振り返るreadWriteCacheMap動作ロジックを。

if (shouldUseReadOnlyResponseCache) {
    timer.schedule(getCacheUpdateTask(),
            new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                    + responseCacheUpdateIntervalMs),
            responseCacheUpdateIntervalMs);
}

private TimerTask getCacheUpdateTask() {
    return new TimerTask() {
        @Override
        public void run() {
            logger.debug("Updating the client cache from response cache");
            for (Key key : readOnlyCacheMap.keySet()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Updating the client cache from response cache for key : {} {} {} {}",
                            key.getEntityType(), key.getName(), key.getVersion(), key.getType());
                }
                try {
                    CurrentRequestVersion.set(key.getVersion());
                    Value cacheValue = readWriteCacheMap.get(key);
                    Value currentCacheValue = readOnlyCacheMap.get(key);
                    if (cacheValue != currentCacheValue) {
                        readOnlyCacheMap.put(key, cacheValue);
                    }
                } catch (Throwable th) {
                    logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);
                } finally {
                    CurrentRequestVersion.remove();
                }
            }
        }
    };
}

ここでは、スケジュールされたタスクが再生され、タイミングコンパレータは、レベル2キャッシュにしている場合行く、とされていない場合には、二次キャッシュバッファに覆われます。上記の最初の質問にこの答えは、2つのキャッシュコヒーレンシの問題は、デフォルトの30代を行います。それでも問題が存在しますので、キャッシュはここで最終的に一貫性の考えである、30代の矛盾を存在してもよいです。

image.png

そして、読み取りと書き込みのキャッシュデータを、その後の上にある書き込み読み出し専用キャッシュ、に戻ってもらうResponseCacheImpl.javaここでのコードの完全な量が読まれたグラブレジストリに、ロジック、ここでの主なハイライトは復帰へのキャッシング戦略の二つのレベルの使用であります対応するデータ。

期限切れの仕上げの下ではなく、上に投げ2番目の質問に応答して、その後、いくつかのメカニズム。

絵と結論するには:

05_EurekaServer多节缓存过期机制.png

  1. アクティブ期限切れの
    readWriteCacheMap、読み取りと書き込みのキャッシュ

    新しいサービスインスタンスは、登録、オフライン、障害を発生し、彼らは(、registerメソッドでAbstractInstanceRegistryがinvalidateCache()メソッドを続くクライアントの登録時に)readWriteCacheMapをリフレッシュするために行くだろう

    たとえば、今、サービスA、サービスAが存在し、新しいサービスインスタンスがあり、Instance010は、以前にキャッシュされた良い、登録が終わった後、実際には、)キャッシュのリフレッシュを持っている必要があり、その後、ResponseCache.invalidate(呼び出します、登録されALL_APPSは、対応するキャッシュキー、彼を離れて期限切れの与えます

    ALL_APPSが出て期限切れに対応するキャッシュ内のキーreadWriteCacheMapをキャッシュ

  2. タイミングが期限切れ

    後ででreadWriteCacheMapにデータを置くように、ビルド時にreadWriteCacheMap、指定した時間の自動有効期限は、デフォルト値が自動的に180秒間待機します後、180秒であり、このデータは、彼が期限切れになります

  3. パッシブの有効期限が切れ

    readOnlyCacheMap怎么过期呢?
    默认是每隔30秒,执行一个定时调度的线程任务,TimerTask,有一个逻辑,会每隔30秒,对readOnlyCacheMap和readWriteCacheMap中的数据进行一个比对,如果两块数据是不一致的,那么就将readWriteCacheMap中的数据放到readOnlyCacheMap中来。

    比如说readWriteCacheMap中,ALL_APPS这个key对应的缓存没了,那么最多30秒过后,就会同步到readOnelyCacheMap中去。

client端增量抓取注册表逻辑

上面抓取全量注册表的代码已经说了,这里来讲一下增量抓取,入口还是在DiscoverClient.java
中,当初始化完DiscoverClient.java 后会执行一个初始化定时任务的方法initScheduledTasks(), 其中这个里面就会每隔30s 增量抓取一次注册表信息。

这里就不跟着这里的逻辑一步步看了,看过上面的代码后 应该会对这里比较清晰了,这里我们直接看Server端代码了。

还记的我们上面插过的眼,获取全量用的是ALL_APPS 增量用的是ALL_APPS_DELTA, 所以我们这里只看增量的逻辑就行了。

else if (ALL_APPS_DELTA.equals(key.getName())) {
    if (isRemoteRegionRequested) {
        tracer = serializeDeltaAppsWithRemoteRegionTimer.start();
        versionDeltaWithRegions.incrementAndGet();
        versionDeltaWithRegionsLegacy.incrementAndGet();
        payload = getPayLoad(key,
                registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
    } else {
        tracer = serializeDeltaAppsTimer.start();
        versionDelta.incrementAndGet();
        versionDeltaLegacy.incrementAndGet();
        payload = getPayLoad(key, registry.getApplicationDeltas());
    }
}

上面只是截取了部分代码,这里直接看主要的逻辑registry.getApplicationDeltasFromMultipleRegions即可,这个和全量的方法名只有一个Deltas的区别。

public Applications getApplicationDeltasFromMultipleRegions(String[] remoteRegions) {
    if (null == remoteRegions) {
        remoteRegions = allKnownRemoteRegions; // null means all remote regions.
    }

    boolean includeRemoteRegion = remoteRegions.length != 0;

    if (includeRemoteRegion) {
        GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS_DELTA.increment();
    } else {
        GET_ALL_CACHE_MISS_DELTA.increment();
    }

    Applications apps = new Applications();
    apps.setVersion(responseCache.getVersionDeltaWithRegions().get());
    Map<String, Application> applicationInstancesMap = new HashMap<String, Application>();
    try {
        write.lock();
        Iterator<RecentlyChangedItem> iter = this.recentlyChangedQueue.iterator();
        logger.debug("The number of elements in the delta queue is :{}", this.recentlyChangedQueue.size());
        while (iter.hasNext()) {
            Lease<InstanceInfo> lease = iter.next().getLeaseInfo();
            InstanceInfo instanceInfo = lease.getHolder();
            logger.debug("The instance id {} is found with status {} and actiontype {}",
                    instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name());
            Application app = applicationInstancesMap.get(instanceInfo.getAppName());
            if (app == null) {
                app = new Application(instanceInfo.getAppName());
                applicationInstancesMap.put(instanceInfo.getAppName(), app);
                apps.addApplication(app);
            }
            app.addInstance(new InstanceInfo(decorateInstanceInfo(lease)));
        }

        if (includeRemoteRegion) {
            for (String remoteRegion : remoteRegions) {
                RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
                if (null != remoteRegistry) {
                    Applications remoteAppsDelta = remoteRegistry.getApplicationDeltas();
                    if (null != remoteAppsDelta) {
                        for (Application application : remoteAppsDelta.getRegisteredApplications()) {
                            if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
                                Application appInstanceTillNow =
                                        apps.getRegisteredApplications(application.getName());
                                if (appInstanceTillNow == null) {
                                    appInstanceTillNow = new Application(application.getName());
                                    apps.addApplication(appInstanceTillNow);
                                }
                                for (InstanceInfo instanceInfo : application.getInstances()) {
                                    appInstanceTillNow.addInstance(new InstanceInfo(instanceInfo));
                                }
                            }
                        }
                    }
                }
            }
        }

        Applications allApps = getApplicationsFromMultipleRegions(remoteRegions);
        apps.setAppsHashCode(allApps.getReconcileHashCode());
        return apps;
    } finally {
        write.unlock();
    }
}

这里代码还是比较多的,我们只需要抓住重点即可:

  1. recentlyChangedQueue中获取注册信息,从名字可以看出来 这是最近改变的client注册信息的队列
  2. 使用writeLock,因为这里是获取增量注册信息,是从队列中获取,如果不加写锁,那么获取的时候又有新数据加入队列中,新数据会获取不到的

基于上面第一点,我们来看看这个队列怎么做的:

  1. 数据结构:ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue
  2. AbstractInstanceRegistry.java初始化的时候会启动一个定时任务,默认30s中执行一次。如果注册时间小于当前时间的180s,就会放到这个队列中

AbstractInstanceRegistry.java具体的なコードは次のよう:

protected AbstractInstanceRegistry(EurekaServerConfig serverConfig, EurekaClientConfig clientConfig, ServerCodecs serverCodecs) {
    this.serverConfig = serverConfig;
    this.clientConfig = clientConfig;
    this.serverCodecs = serverCodecs;
    this.recentCanceledQueue = new CircularQueue<Pair<Long, String>>(1000);
    this.recentRegisteredQueue = new CircularQueue<Pair<Long, String>>(1000);

    this.renewsLastMin = new MeasuredRate(1000 * 60 * 1);

    this.deltaRetentionTimer.schedule(getDeltaRetentionTask(),
            serverConfig.getDeltaRetentionTimerIntervalInMs(),
            serverConfig.getDeltaRetentionTimerIntervalInMs());
}

private TimerTask getDeltaRetentionTask() {
    return new TimerTask() {

        @Override
        public void run() {
            Iterator<RecentlyChangedItem> it = recentlyChangedQueue.iterator();
            while (it.hasNext()) {
                if (it.next().getLastUpdateTime() <
                        System.currentTimeMillis() - serverConfig.getRetentionTimeInMSInDeltaQueue()) {
                    it.remove();
                } else {
                    break;
                }
            }
        }

    };
}

ここでNengkanmingbaiは、増分クロールは、3分以内に保存されますことを、クライアント情報のEurekaServerの終わりを変更します。
最後に、明るいスポットがある、我々はコードは、完全または増分量のクロールクロールするかどうかを、上記と述べ、最終的にレジストリの全体量のハッシュを返しapps.setAppsHashCode(allApps.getReconcileHashCode());アプリがの復帰でどこApplications財産、そして最終的に我々が見てこのハッシュコードの使用。

戻るDiscoveryClient.java、見つけるrefreshRegistry方法は、その後、追跡のすべての方法getAndUpdateDeltaは次のような方法、具体的なコードは、ここで私は我慢しないであろう、プロセスは次のとおりです。

  1. インクリメンタルデータ収集デルタ
  2. 増分データとローカルレジストリデータをマージするためによると
  3. ローカルレジストリ情報の値のハッシュコードの計算
  4. 値は、サーバから返されたローカルのhashCodeのハッシュコード値と矛盾していると、レジストリ情報いったん全額を取得した場合

最後の画像は、増分クロールレジストリロジックを要約したものです。

06_EurekaClient增量抓取注册表流程.png

概要&洞察

記事が少し長いですし、確かに彼は、書き込みには非常に難しい私は、完全なデータ量の増加分+をすれば、私は、インクリメンタルこのマルチレベルキャッシュメカニズム+データの整合性ハッシュコントラストプロトコルは、優れた仕事を感じます同期私はこのスキームの恩恵を受ける。

ソースコードを学ぶことができます参照してください誰か他の人のデザインです。要約セクションでは、上記のチャートの一部、ソースコードは、この上で学ぶために、レジストリのグラブを見て、またメカニズム、クラスターなどのいくつかのソースを保護するために、ハートビートメカニズムの後ろに見えるように準備することができます。

ここでは、ソースコードを読んだ後、次の質問に送信されます。

なぜ、30秒後の秋に認知することができる他のサービスのサービスを呼び出すために失敗し、組立ラインオフ、登録されたサービスインスタンスがあると仮定?ほとんど、わずか30秒で、サービスレジストリ、マルチレベルのキャッシュメカニズムは、キャッシュを更新するとき、それ以来ここに来ます。

宣言

:私のブログから始まるこの記事https://www.cnblogs.com/wang-mengと公共番号:ロマンチックみなさ一つramiflorous BEは、ソースを明記してください転載必要があります!

興味のパートナーは、個々の国民の少数心配することができる:一つの枝にはロマンチックな花を数えます

22.jpg

おすすめ

転載: www.cnblogs.com/wang-meng/p/12118203.html