zoukankan      html  css  js  c++  java
  • Eureka源码解析(五)

    服务发现

    我们继续来研究服务的发现过程,就是客户端需要能够满足两个功能

    • 在启动的时候获取指定服务提供者的地址列表
    • Eureka server端地址发生变化时,需要动态感知

    DiscoveryClient构造时进行查询

    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
    
    
        if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
            fetchRegistryFromBackup();
        }
    }
    

    DiscoveryClient.fetchRegistry

    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();
            }
        }
    
        // 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
        return true;
    }
    

    定时任务每隔30s更新一次本地地址列表

    在DiscoveryClient构造的时候,会初始化一些任务。其中有一个任务动态更新本地服务地址列表,叫 cacheRefreshTask 。这个任务最终执行的是CacheRefreshThread这个线程。它是一个周期性执行的任务,具体如下:

    private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            cacheRefreshTask = new TimedSupervisorTask(
                "cacheRefresh",
                scheduler,
                cacheRefreshExecutor,
                registryFetchIntervalSeconds,
                TimeUnit.SECONDS,
                expBackOffBound,
                new CacheRefreshThread()
            );
            scheduler.schedule(
                cacheRefreshTask,
                registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }
    }
    

    TimedSupervisorTask

    从整体上看,TimedSupervisorTask是固定间隔的周期性任务,一旦遇到超时就会将下一个周期的间隔时间调大,如果连续超时,那么每次间隔时间都会增大一倍,一直到达外部参数设定的上限为止,一旦新任务不再超时,间隔时间又会自动恢复为初始值。

    public void run() {
        Future<?> future = null;
        try {
            //使用Future,可以设定子线程的超时时间,这样当前线程就不用无限等待了
            future = executor.submit(task);
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            //指定等待子线程的最长时间
            future.get(timeoutMillis, TimeUnit.MILLISECONDS);  // block until done or timeout
            //delay是个很有用的变量,后面会用到,这里记得每次执行任务成功都会将delay重置
            delay.set(timeoutMillis);
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            successCounter.increment();
        } catch (TimeoutException e) {
            logger.warn("task supervisor timed out", e);
            timeoutCounter.increment();
    
            long currentDelay = delay.get();
            //任务线程超时的时候,就把delay变量翻倍,但不会超过外部调用时设定的最大延时时间
            long newDelay = Math.min(maxDelay, currentDelay * 2);
            //设置为最新的值,考虑到多线程,所以用了CAS
            delay.compareAndSet(currentDelay, newDelay);
    
        } catch (RejectedExecutionException e) {
            //一旦线程池的阻塞队列中放满了待处理任务,触发了拒绝策略,就会将调度器停掉
            if (executor.isShutdown() || scheduler.isShutdown()) {
                logger.warn("task supervisor shutting down, reject the task", e);
            } else {
                logger.warn("task supervisor rejected the task", e);
            }
    
            rejectedCounter.increment();
        } catch (Throwable e) {
            //一旦出现未知的异常,就停掉调度器
            if (executor.isShutdown() || scheduler.isShutdown()) {
                logger.warn("task supervisor shutting down, can't accept the task");
            } else {
                logger.warn("task supervisor threw an exception", e);
            }
    
            throwableCounter.increment();
        } finally {
            //这里任务要么执行完毕,要么发生异常,都用cancel方法来清理任务;
            if (future != null) {
                future.cancel(true);
            }
    		//只要调度器没有停止,就再指定等待时间之后在执行一次同样的任务
            if (!scheduler.isShutdown()) {
                //这里就是周期性任务的原因:只要没有停止调度器,就再创建一次性任务,执行时间时dealy的值,
    	    //假设外部调用时传入的超时时间为30秒(构造方法的入参timeout),最大间隔时间为50秒(构造方法的入参expBackOffBound)
    	    //如果最近一次任务没有超时,那么就在30秒后开始新任务,
    	    //如果最近一次任务超时了,那么就在50秒后开始新任务(异常处理中有个乘以二的操作,乘以二后的60秒超过了最大间隔50秒)
                scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
            }
        }
    }
    

    CacheRefreshThread.refreshRegistry

    这段代码主要两个逻辑

    • 判断remoteRegions是否发生了变化
    • 调用fetchRegistry获取本地服务地址缓存
    void refreshRegistry() {
        try {
            boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries();
    
            boolean remoteRegionsModified = false;
            // //如果部署在aws环境上,会判断最后一次远程区域更新的信息和当前远程区域信息进行比较,如果不相等,则更新
            String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions();
            if (null != latestRemoteRegions) {
                String currentRemoteRegions = remoteRegionsToFetch.get();
                if (!latestRemoteRegions.equals(currentRemoteRegions)) {
                   //判断最后一次
            }
    
            boolean success = fetchRegistry(remoteRegionsModified);
            if (success) {
                registrySize = localRegionApps.get().size();
                lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
            }
    
          //...
        } catch (Throwable e) {
            logger.error("Cannot fetch registry from server", e);
        }
    }
    

    DisccoveryClient.fetchRegistry

    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();
            //判断多个条件,确定是否触发全量更新,如下任一个满足都会全量更新:
            //1. 是否禁用增量更新;
            //2. 是否对某个region特别关注;
            //3. 外部调用时是否通过入参指定全量更新;
            //4. 本地还未缓存有效的服务列表信息;
            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);
            }
            //重新计算和设置一致性hash码
            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();
            }
        }
    
        // Notify about cache refresh before updating the instance remote status
        //将本地缓存更新的事件广播给所有已注册的监听器,注意该方法已被CloudEurekaClient类重写
        onCacheRefreshed();
    
        // Update remote status based on refreshed data held in the cache
        //检查刚刚更新的缓存中,有来自Eureka server的服务列表,其中包含了当前应用的状态,
        //当前实例的成员变量lastRemoteInstanceStatus,记录的是最后一次更新的当前应用状态,
        //上述两种状态在updateInstanceRemoteStatus方法中作比较 ,如果不一致,就更新lastRemoteInstanceStatus,并且广播对应的事件
        updateInstanceRemoteStatus();
    
        // registry was fetched successfully, so return true
        return true;
    }
    

    DiscoveryClient.getAndStoreFullRegistry

    从eureka server端获取服务注册中心的地址信息,然后更新并设置到本地缓存 localRegionApps 。

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

    服务端查询服务地址流程

    客户端发起服务地址的查询有两种,一种是全量、另一种是增量。对于全量查询请求,会调用Eureka-server的ApplicationsResource的getContainers方法。
    而对于增量请求,会调用ApplicationsResource.getContainerDifferential。

    ApplicationsResource.getContainers

    @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) {
    
        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.
        // EurekaServer无法提供服务,返回403
        if (!registry.shouldAllowAccess(isRemoteRegionRequested)) {
            return Response.status(Status.FORBIDDEN).build();
        }
        CurrentRequestVersion.set(Version.toEnum(version));
        // 设置返回数据格式,默认JSON
        KeyType keyType = Key.KeyType.JSON;
        String returnMediaType = MediaType.APPLICATION_JSON;
        // 如果接收到的请求头部没有具体格式信息,则返回格式为XML
        if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) {
            keyType = Key.KeyType.XML;
            returnMediaType = MediaType.APPLICATION_XML;
        }
    	// 构建缓存键
        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.getGZIP

    从缓存中读取数据。

    public byte[] getGZIP(Key key) {
        Value payload = getValue(key, shouldUseReadOnlyResponseCache);
        if (payload == null) {
            return null;
        }
        return payload.getGzipped();
    }
    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;
    }
    
  • 相关阅读:
    [Android] 升级了新的android studio之后 发生如下的报错,The following classes could not be instantiated:
    [IOS]译Size Classes with Xcode 6: One Storyboard for all Sizes
    [IOS] 利用@IBInspectable
    [IOS]swift自定义uicollectionviewcell
    [IOS]Swift使用SVGKit的记录
    [IOS初学]ios 第一篇 storyboard 与viewcontroller的关系
    [Android]关于filed 遍历资源文件的排序问题
    [Android]关于Installation error: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED ,修改包名
    [Android]用图库打开指定的文件夹,没错是第一个画面直接是图库的文件夹画面
    [Android]新版的sdk中新建一个android应用,增加的PlaceholderFragment这个静态类发生的事情
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14132689.html
Copyright © 2011-2022 走看看