[출처 함께 배울 - 마이크로 서비스] Nexflix 유레카 소스 여덟 : 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 요청은 서버 측에 보내 본 후 전액 서버 레지스트리 정보를 반환하는 말을 기다릴 수 있습니다.

실행 요청의 전액 여기를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방법, 여기에서이 두 가지 주요지도가 readOnlyCacheMap다른 readWriteCacheMap우리가 알 수있는 이름을 보는 곳은 읽기 전용 캐시, 읽기 - 쓰기 캐시, 두 개의 층으로 캐시 구조이며, 읽기 전용의 경우 캐시는 쿼리 캐시가 비어있는 경우 직접 읽을 수 반환 비어 있지 않습니다.

캐시가 아래로보고 계속에 대해 우리는 설명한다.

서버 측 레지스트리 멀티 레벨 캐시 메커니즘 만료 : 활성 +의 패시브 +를 초과

관련 캐시를보고 계속, 당신은 몇 가지 질문이있을 수 있습니다 여기에 멀티 레벨 캐시를 사용합니다 :

  1. 어떻게 두 개의 캐시 데이터 동기화?
  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의, 스레드 안전 사용.
    readWriteCacheMap작은 파트너를 이해하지 마십시오 GuavaCache를 사용하면 자신을 따라 내 이전 블로그이 설명 할 필요 읽을 수있는, 구조는지도 구글 오픈 소스 프로젝트 구아바 메모리 기반 캐시뿐만 아니라 내부의 구현입니다.

  2. GuavaCache에서 우리의 모습의 주요 초점은 여기에 초기 크기 인 serverConfig.getInitialCapacityOfResponseCache()1000 기본값도 원래 크기를지도.
    expireAfterWrite새로 고침 시간이이다 serverConfig.getResponseCacheAutoExpirationInSeconds()기본 시간은 180s입니다.
    빌드 방법 다음 레지스트리 정보는 여기에서 사용되는 얻을 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(가) 먼저 눈에 삽입 증분 탐색의 의미는, 레지스트리 증분 크롤링 논리 될 것입니다 돌아 보면.

우리는 관심을 지불 할 필요가 위의 논리 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();
                }
            }
        }
    };
}

여기에 예약 된 작업이 재생되고, 타이밍 비교기는 수준이 캐시와 경우 갈 것 아닌 경우는 보조 캐시 버퍼로 덮여됩니다. 첫 번째 질문이 대답은 위의 두 개의 캐시 일관성 문제는 기본 30 대를 수행합니다. 여전히 문제가있을 것입니다 그래서, 캐시 여기에 결국 일관성의 생각, 30 대에 불일치가있을 수 있습니다.

의 image.png

그런 다음 읽기 및 쓰기 캐시 데이터를 다음 위에 쓰기 읽기 전용 캐시로 돌아갈 수 ResponseCacheImpl.java여기에 코드의 전체 양이 읽은 잡아 레지스트리에, 로직, 여기에 주요 하이라이트는 수익에 캐싱 전략의 두 가지 수준의 사용이다 해당 데이터.

만료 된 마무리에서, 또한 위에 던져 두 번째 질문에 응답하여 다음 몇 가지 메커니즘.

그림으로 결론하려면 :

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

  1. 활동 만료
    readWriteCacheMap, 읽기 및 쓰기 캐시

    새로운 서비스 인스턴스 등록, 오프라인, 오류를 발생 그들은 (등록 방법 AbstractInstanceRegistry가 invalidateCache () 메소드 마지막됩니다 클라이언트 등록시) readWriteCacheMap을 새로 갈 것입니다

    예를 들어, 지금, 이전에 캐시 된 좋은 등록이 끝난 후, Instance010이) 사실, ResponseCache.invalidate을 (호출 한 다음 캐시 새로 고침을 가지고해야, 등록 서비스 A는, ServiceA는, 새로운 서비스 인스턴스가있다 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. 값이 서버에서 반환 지역 해시 코드의 해시 코드 값과 일치하고 경우, 레지스트리 정보를 한 번 전액을 얻을

마지막 사진은 증분 크롤링 레지스트리 논리를 요약 한 것입니다 :

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

요약 및 인사이트

나는 전체 데이터 볼륨 증가 +를 할 경우, 나는이 멀티 레벨 캐싱 메커니즘 + 증분 데이터 일관성 해시 대비 프로토콜 훌륭한 일을 할 기분이 기사는 조금 긴, 실제로 그는 쓰기에 아주 단단하다 동기 나는이 계획 도움이됩니다.

소스 코드를 배울 수있는 참조 누군가 다른 사람의 디자인입니다. 요약 섹션은 위의 차트의 일부 소스 코드는 다음에 배울 수있는 레지스트리 잡아보고, 또한 메커니즘, 클러스터 등의 일부의 소스를 보호하기 위해 하트 비트 메커니즘 뒤에 볼 수 있도록 준비 할 수 있습니다.

여기에 소스 코드를 읽은 후 다음 질문으로 전송됩니다 :

등록 된 서비스 인스턴스가, 왜 30 초 후에 가을에 인식 될 수있는 다른 서비스의 서비스를 호출 할 수있는 실패, 조립 라인에서, 거기에 가정? 대부분 30 초로 서비스 레지스트리, 멀티 - 레벨 캐싱 메커니즘은 캐시를 업데이트 할 때 그 이후로 여기까지.

선언

: 내 블로그에서 시작하는이 문서 https://www.cnblogs.com/wang-meng 공공 번호 : 하나는 낭만적 인 고려 ramiflorous 하시기 바랍니다 다시 인쇄해야는 소스를 나타냅니다!

관심있는 파트너는 개별 공공의 적은 수에 대해 우려 할 수있다 : 하나 개의 지점은 로맨틱 한 꽃을 계산

22.jpg

추천

출처www.cnblogs.com/wang-meng/p/12118203.html