eureka 之如何从eureka server全量或增量拉取服务实例注册表?

1.源码定位

com.netflix.discovery.DiscoveryClient#fetchRegistry 这个方法是用于从eureka服务端拉取服务信息使用的,那么拉取的流程是怎样的呢?我们先简单的读一下源码

源码版本: v1.7x

源码地址:https://github.com/Netflix/eureka/tree/v1.7.x


2. 源码简读

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

        try {
            // 获取本地缓存
            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);
            }
            // 设置注册表一致性hash值
            applications.setAppsHashCode(applications.getReconcileHashCode());
            // debug日志记录总实例数
            logTotalInstances();
        } catch (Throwable e) {
            logger.error(PREFIX + appPathIdentifier + " - was unable to refresh its cache! status = " + e.getMessage(), e);
            return false;
        } finally {
            if (tracer != null) {
                tracer.stop();
            }
        }

        // Notify about cache refresh before updating the instance remote status
        // 在更新向事件总线发布注册表缓存更新通知
        onCacheRefreshed();

        // 基于eureka server 更新自身服务状态
        updateInstanceRemoteStatus();

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

上面总结一下:

  1. 本地没有拉全量覆盖
  2. 不允许增量更新拉全量覆盖
  3. 增量覆盖+eureka client和eureka server的一致性hash校验,校验不通过还得全量覆盖
  4. 更新一下注册表的一致性hash校验值
  5. 发布注册表更新事件通知
  6. 基于eureka server来更新自身的InstanceInfo状态

3.拉取注册表之全量拉取源码解读com.netflix.discovery.DiscoveryClient#getAndStoreFullRegistry

private void getAndStoreFullRegistry() throws Throwable {
        // 版本控制器: 以确保过时的线程不会将注册表重置为旧版本
        long currentUpdateGeneration = fetchRegistryGeneration.get();

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

        Applications apps = null;
        // 从eureka server 拉取到全量注册表
        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");
        }
    }

小结:

  1. 注册表更新会有个版本控制,如果版本不对,那么就不会更新
  2. 拉取到全量列表后要进行是否过滤掉离线服务和打乱注册表(打乱顺序的原因我觉得吧,应该是为了让人在查看eureka 监控面板时上能够感受到注册表发生了拉取行为,你觉得呢?)

4.增量拉取com.netflix.discovery.DiscoveryClient#getAndUpdateDelta

private void getAndUpdateDelta(Applications applications) throws Throwable {
    // 版本控制
    long currentUpdateGeneration = fetchRegistryGeneration.get();

    // 增量服务实例操作信息
    Applications delta = null;
    EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
    if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
        delta = httpResponse.getEntity();
    }
    // 当delta为空时,表示eureka server 不允许增量拉取
    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();
        // 拉取注册版本信息比较,多线程执行时防止低版本覆盖高版本注册表
    } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
        // 获取增量注册表携带的hashcode ,这个适用于检测客户端数据是否与服务端一致
        logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
        String reconcileHashCode = "";
        if (fetchRegistryUpdateLock.tryLock()) {
            // 拉取注册并并行操作加锁
            try {
                // 对增,删,改的服务实例进行更新
                updateDelta(delta);
                // 计算增量更新后的调和hash值,用来比对本地注册表和服务器注册表是否一致
                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());
    }
}

小结:

  1. queryClient.getDelta 拉取增量列表
  2. 判断增量列表是否为null,为null说明eureka server 不允许增量更新
  3. updateDelta增量更新(第五点详细分析)
  4. 比较本地注册表的reconcileHashCode 和 服务器的是否一致
  5. 不一致则拉取全量注册表进行替换

5.增量更新com.netflix.discovery.DiscoveryClient#updateDelta

/**
     * Updates the delta information fetches from the eureka server into the
     * local cache.
     * 这里才是服务注册表增量更新的具体更新内容
     * @param delta
     *            the delta information received from eureka server in the last
     *            poll cycle.
     */
    private void updateDelta(Applications delta) {
        // 首先说明一下 app表示服务(集群名称)  instance表示服务实例   比如一个ServcieA服务有多个服务实例SserverA1和ServerA2
        int deltaCount = 0;
        for (Application app : delta.getRegisteredApplications()) {
            // 一个应用可能有多个实例,比如服务A部署了多台机器
            for (InstanceInfo instance : app.getInstances()) {
                // 获取本地缓存的注册表
                Applications applications = getApplications();
                // region相关的不看,非AWS服务走不到
                String instanceRegion = instanceRegionChecker.getInstanceRegion(instance);
                if (!instanceRegionChecker.isLocalRegion(instanceRegion)) {
										// 走不到 ,不看
                    Applications remoteApps = remoteRegionVsApps.get(instanceRegion);
                    if (null == remoteApps) {
                        remoteApps = new Applications();
                        remoteRegionVsApps.put(instanceRegion, remoteApps);
                    }
                    applications = remoteApps;
                }

                ++deltaCount;
                if (ActionType.ADDED.equals(instance.getActionType())) {
                    // 增量注册表的实例行为: 新增服务实例
                    // 从本地注册表中查看是否存在
                    Application existingApp = applications.getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        // 新增实例到本地注册表
                        applications.addApplication(app);
                    }
                    logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion);
                    // 这里其实是存在重复操作的,第一次注册该服务时,服务实例会被重复注册两次
                    applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);
                } else if (ActionType.MODIFIED.equals(instance.getActionType())) {
                    //增量注册表的服务实例行为: 修改服务实例
                    // 这个跟新增的代码好像没啥区别啊
                    Application existingApp = applications.getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        applications.addApplication(app);
                    }
                    logger.debug("Modified instance {} to the existing apps ", instance.getId());

                    applications.getRegisteredApplications(instance.getAppName()).addInstance(instance);

                } else if (ActionType.DELETED.equals(instance.getActionType())) {
                    //增量注册表的服务实例行为: 删除服务实例
                    Application existingApp = applications.getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        applications.addApplication(app);
                    }
                    logger.debug("Deleted instance {} to the existing apps ", instance.getId());
                    // 跟新增和修改唯一的差别
                    applications.getRegisteredApplications(instance.getAppName()).removeInstance(instance);
                }
            }
        }
        logger.debug("The total number of instances fetched by the delta processor : {}", deltaCount);

        getApplications().setVersion(delta.getVersion());
        // 打乱注册表和过滤下线实例,这里打乱的意义是啥呢?
        getApplications().shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
        // AWS云服务使用的,国内用不到
        for (Applications applications : remoteRegionVsApps.values()) {
            applications.setVersion(delta.getVersion());
            applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
        }
    }

小结:

  1. 遍历增量注册列表,然后看每个服务实例是否在本地缓存中存在
  2. 如果存在则基于实例的getActionType来做出对应的行为,否则进行新增
  3. 其中新增和修改是进行服务实例删除再新增
  4. 删除是移除服务实例

小问题:

其实updateDelta这里有点问题,如果一个服务实例第一次进行增量新增时,会先加到注册表再从注册表移除,再新增到注册表

而删除会出现的情况是,我本地没有这个,但是你服务器发布的增量是删除,那么我就要先添加,再删除。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值