深入理解Eureka - Eureka Client获取注册信息机制
深入理解Eureka - Eureka Client获取注册信息机
Eureka Client提供了定时获取注册信息的机制。Eureka Client获取注册信息的所有逻辑都在DiscoveryClient类里。
Eureka在初始化的时候根据获取注册信息的开关(默认开启)来决定是否初始化获取注册信息定时任务(默认30S同步一次):
- if(clientConfig.shouldFetchRegistry()){
- // registry cache refresh timer
- int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
- int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
- scheduler.schedule(
- newTimedSupervisorTask(
- "cacheRefresh",
- scheduler,
- cacheRefreshExecutor,
- registryFetchIntervalSeconds,
- TimeUnit.SECONDS,
- expBackOffBound,
- newCacheRefreshThread()
- ),
- registryFetchIntervalSeconds,TimeUnit.SECONDS);
- }
Eureka Client提供了两种获取注册信息的模式
- 全量获取
- 增量获取
Eureka Client全量获取注册信息
- /**
- * Fetches the registry information.
- *
- * <p>
- * This method tries to get only deltas after the first fetch unless there
- * is an issue in reconciling eureka server and client registry information.
- * </p>
- *
- * @param forceFullRegistryFetch Forces a full registry fetch.
- *
- * @return true if the registry was fetched
- */
- privateboolean 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);
- returnfalse;
- }finally{
- if(tracer !=null){
- tracer.stop();
- }
- }
- // Notify about cache refresh before updating the instance remote status
- onCacheRefreshed();
- // Update remote status based on refreshed data held in the cache
- updateInstanceRemoteStatus();
- // registry was fetched successfully, so return true
- returntrue;
- }
全量获取的条件
1、配置禁用了增量获取
2、首次请求(当前applications为空 | 当前获取到的application为0)
3、如果增量同步失败,还会强制全量同步
全量同步流程
全量同步时,通过访问Eureka Server的接口来获取全部服务信息
- /**
- * Gets the full registry information from the eureka server and stores it locally.
- * When applying the full registry, the following flow is observed:
- *
- * if (update generation have not advanced (due to another thread))
- * atomically set the registry to the new registry
- * fi
- *
- * @return the full registry information.
- * @throws Throwable
- * on error.
- */
- privatevoid getAndStoreFullRegistry()throwsThrowable{
- 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");
- }elseif(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");
- }
- }
Eureka Server的com.netflix.eureka.resources.ApplicationsResource#Containers接收到请求后将服务信息返回。
- /**
- * Get information about all {@link com.netflix.discovery.shared.Applications}.
- *
- * @param version the version of the request.
- * @param acceptHeader the accept header to indicate whether to serve JSON or XML data.
- * @param acceptEncoding the accept header to indicate whether to serve compressed or uncompressed data.
- * @param eurekaAccept an eureka accept extension, see {@link com.netflix.appinfo.EurekaAccept}
- * @param uriInfo the {@link java.net.URI} information of the request made.
- * @param regionsStr A comma separated list of remote regions from which the instances will also be returned.
- * The applications returned from the remote region can be limited to the applications
- * returned by {@link EurekaServerConfig#getRemoteRegionAppWhitelist(String)}
- *
- * @return a response containing information about all {@link com.netflix.discovery.shared.Applications}
- * from the {@link AbstractInstanceRegistry}.
- */
- @GET
- publicResponse getContainers(@PathParam("version")String version,
- @HeaderParam(HEADER_ACCEPT)String acceptHeader,
- @HeaderParam(HEADER_ACCEPT_ENCODING)String acceptEncoding,
- @HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT)String eurekaAccept,
- @ContextUriInfo uriInfo,
- @Nullable@QueryParam("regions")String regionsStr){
- boolean isRemoteRegionRequested =null!= regionsStr &&!regionsStr.isEmpty();
- String[] regions =null;
- if(!isRemoteRegionRequested){
- EurekaMonitors.GET_ALL.increment();
- }else{
- regions = regionsStr.toLowerCase().split(",");
- Arrays.sort(regions);// So we don't have different caches for same regions queried in different order.
- EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();
- }
- // Check if the server allows the access to the registry. The server can
- // restrict access if it is not
- // ready to serve traffic depending on various reasons.
- if(!registry.shouldAllowAccess(isRemoteRegionRequested)){
- returnResponse.status(Status.FORBIDDEN).build();
- }
- CurrentRequestVersion.set(Version.toEnum(version));
- KeyType keyType =Key.KeyType.JSON;
- String returnMediaType =MediaType.APPLICATION_JSON;
- if(acceptHeader ==null||!acceptHeader.contains(HEADER_JSON_VALUE)){
- keyType =Key.KeyType.XML;
- returnMediaType =MediaType.APPLICATION_XML;
- }
- Key cacheKey =newKey(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();
- }
- return response;
- }
Eureka Client增量获取注册信息
增量获取条件
只有开启了增量获取的开关,且非首次获取时,才进行增量的获取。
增量获取流程
增量获取时,通过访问Eureka Server的增量接口,来获取增量的服务信息(注意增量同步失败后会全量同步)
- /**
- * Get the delta registry information from the eureka server and update it locally.
- * When applying the delta, the following flow is observed:
- *
- * if (update generation have not advanced (due to another thread))
- * atomically try to: update application with the delta and get reconcileHashCode
- * abort entire processing otherwise
- * do reconciliation if reconcileHashCode clash
- * fi
- *
- * @return the client response
- * @throws Throwable on error
- */
- privatevoid getAndUpdateDelta(Applications applications)throwsThrowable{
- long currentUpdateGeneration = fetchRegistryGeneration.get();
- Applications delta =null;
- EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
- if(httpResponse.getStatusCode()==Status.OK.getStatusCode()){
- delta = httpResponse.getEntity();
- }
- if(delta ==null){
- logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
- +"Hence got the full registry.");
- getAndStoreFullRegistry();
- }elseif(fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration +1)){
- logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
- String reconcileHashCode ="";
- if(fetchRegistryUpdateLock.tryLock()){
- try{
- updateDelta(delta);
- reconcileHashCode = getReconcileHashCode(applications);
- }finally{
- fetchRegistryUpdateLock.unlock();
- }
- }else{
- logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
- }
- // There is a diff in number of instances for some reason
- if(!reconcileHashCode.equals(delta.getAppsHashCode())|| clientConfig.shouldLogDeltaDiff()){
- reconcileAndLogDifference(delta, reconcileHashCode);// this makes a remoteCall
- }
- }else{
- logger.warn("Not updating application delta as another thread is updating it already");
- logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
- }
- }
然后Eureka Server的接口接收到增量请求后,将增量服务返回给Eureka Client
- /**
- * Get information about all delta changes in {@link com.netflix.discovery.shared.Applications}.
- *
- * <p>
- * The delta changes represent the registry information change for a period
- * as configured by
- * {@link EurekaServerConfig#getRetentionTimeInMSInDeltaQueue()}. The
- * changes that can happen in a registry include
- * <em>Registrations,Cancels,Status Changes and Expirations</em>. Normally
- * the changes to the registry are infrequent and hence getting just the
- * delta will be much more efficient than getting the complete registry.
- * </p>
- *
- * <p>
- * Since the delta information is cached over a period of time, the requests
- * may return the same data multiple times within the window configured by
- * {@link EurekaServerConfig#getRetentionTimeInMSInDeltaQueue()}.The clients
- * are expected to handle this duplicate information.
- * <p>
- *
- * @param version the version of the request.
- * @param acceptHeader the accept header to indicate whether to serve JSON or XML data.
- * @param acceptEncoding the accept header to indicate whether to serve compressed or uncompressed data.
- * @param eurekaAccept an eureka accept extension, see {@link com.netflix.appinfo.EurekaAccept}
- * @param uriInfo the {@link java.net.URI} information of the request made.
- * @return response containing the delta information of the
- * {@link AbstractInstanceRegistry}.
- */
- @Path("delta")
- @GET
- publicResponse getContainerDifferential(
- @PathParam("version")String version,
- @HeaderParam(HEADER_ACCEPT)String acceptHeader,
- @HeaderParam(HEADER_ACCEPT_ENCODING)String acceptEncoding,
- @HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT)String eurekaAccept,
- @ContextUriInfo uriInfo,@Nullable@QueryParam("regions")String regionsStr){
- boolean isRemoteRegionRequested =null!= regionsStr &&!regionsStr.isEmpty();
- // If the delta flag is disabled in discovery or if the lease expiration
- // has been disabled, redirect clients to get all instances
- if((serverConfig.shouldDisableDelta())||(!registry.shouldAllowAccess(isRemoteRegionRequested))){
- returnResponse.status(Status.FORBIDDEN).build();
- }
- String[] regions =null;
- if(!isRemoteRegionRequested){
- EurekaMonitors.GET_ALL_DELTA.increment();
- }else{
- regions = regionsStr.toLowerCase().split(",");
- Arrays.sort(regions);// So we don't have different caches for same regions queried in different order.
- EurekaMonitors.GET_ALL_DELTA_WITH_REMOTE_REGIONS.increment();
- }
- CurrentRequestVersion.set(Version.toEnum(version));
- KeyType keyType =Key.KeyType.JSON;
- String returnMediaType =MediaType.APPLICATION_JSON;
- if(acceptHeader ==null||!acceptHeader.contains(HEADER_JSON_VALUE)){
- keyType =Key.KeyType.XML;
- returnMediaType =MediaType.APPLICATION_XML;
- }
- Key cacheKey =newKey(Key.EntityType.Application,
- ResponseCacheImpl.ALL_APPS_DELTA,
- keyType,CurrentRequestVersion.get(),EurekaAccept.fromString(eurekaAccept), regions
- );
- if(acceptEncoding !=null
- && acceptEncoding.contains(HEADER_GZIP_VALUE)){
- returnResponse.ok(responseCache.getGZIP(cacheKey))
- .header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
- .header(HEADER_CONTENT_TYPE, returnMediaType)
- .build();
- }else{
- returnResponse.ok(responseCache.get(cacheKey))
- .build();
- }
- }
备注:Register、renew、cancel、evict最终都会更新recnetlyChangedQueue队列,Eureka Client获取的增量信息都是从这个队列中获取的。
Spring Cloud实战项目Jbone地址
github地址:https://github.com/417511458/jbone
码云地址:https://gitee.com/majunwei2017/jbone
原创文章,转载请注明出处:转载自小马过河 - 深入理解Eureka - Eureka Client获取注册信息机制