Eureka源码分析(八) 自我保护机制

下面来说下eureka的自我保护机制。当每分钟心跳次数( renewsLastMin ) 小于 numberOfRenewsPerMinThreshold 时,并且开启自动保护模式开关( eureka.enableSelfPreservation = true ) 时,触发自动保护机制,不再自动过期租约

public void evict(long additionalLeaseMs) {
    logger.debug("Running the evict task");

    if (!isLeaseExpirationEnabled()) {
        logger.debug("DS: lease expiration is currently disabled.");
        return;
    }

    // 获得 所有过期的租约
    // We collect first all expired items, to evict them in random order. For large eviction sets,
    // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
    // the impact should be evenly distributed across all applications.
    List> expiredLeases = new ArrayList<>();
    for (Entry>> groupEntry : registry.entrySet()) {
        Map> leaseMap = groupEntry.getValue();
        if (leaseMap != null) {
            for (Entry> leaseEntry : leaseMap.entrySet()) {
                Lease lease = leaseEntry.getValue();
                if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) { // 过期
                    expiredLeases.add(lease);
                }
            }
        }
    }

    // 计算 最大允许清理租约数量
    // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
    // triggering self-preservation. Without that we would wipe out full registry.
    int registrySize = (int) getLocalRegistrySize();
    int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
    int evictionLimit = registrySize - registrySizeThreshold;

    // 计算 清理租约数量
    int toEvict = Math.min(expiredLeases.size(), evictionLimit);
    if (toEvict > 0) {
        logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);

        // 逐个过期
        Random random = new Random(System.currentTimeMillis());
        for (int i = 0; i < toEvict; i++) {
            // Pick a random item (Knuth shuffle algorithm)
            int next = i + random.nextInt(expiredLeases.size() - i);
            Collections.swap(expiredLeases, i, next);
            Lease lease = expiredLeases.get(i);

            String appName = lease.getHolder().getAppName();
            String id = lease.getHolder().getId();
            EXPIRED.increment();
            logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
            internalCancel(appName, id, false);
        }
    }
}

EurekaServer 在启动时,从 EurekaServer集群获取注册信息,并首次初始化 numberOfRenewsPerMinThreshold 、 expectedNumberOfRenewsPerMin。Eureka-Server 定时重新计算 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin 。

private void scheduleRenewalThresholdUpdateTask() {
    timer.schedule(new TimerTask() {
                       @Override
                       public void run() {
                           updateRenewalThreshold();
                       }
                   }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
            serverConfig.getRenewalThresholdUpdateIntervalMs());
}
private void updateRenewalThreshold() {
    try {
        // 计算 应用实例数
        Applications apps = eurekaClient.getApplications();
        int count = 0;
        for (Application app : apps.getRegisteredApplications()) {
            for (InstanceInfo instance : app.getInstances()) {
                if (this.isRegisterable(instance)) {
                    ++count;
                }
            }
        }
        // 计算 expectedNumberOfRenewsPerMin 、 numberOfRenewsPerMinThreshold 参数
        synchronized (lock) {
            // Update threshold only if the threshold is greater than the
            // current expected threshold of if the self preservation is disabled.
            if ((count * 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold)
                    || (!this.isSelfPreservationModeEnabled())) {
                this.expectedNumberOfRenewsPerMin = count * 2;
                this.numberOfRenewsPerMinThreshold = (int) ((count * 2) * serverConfig.getRenewalPercentThreshold());
            }
        }
        logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
    } catch (Throwable e) {
        logger.error("Cannot update renewal threshold", e);
    }
}
  • 配置 eureka.renewalThresholdUpdateIntervalMs 参数,定时重新计算。默认,15 分钟。

  • 代码块 !this.isSelfPreservationModeEnabled() :当未开启自我保护机制时,每次都进行重新计算。事实上,这两个参数不仅仅自我保护机制会使用到,配合 Netflix Servo实现监控信息采集 numberOfRenewsPerMinThresholdexpectedNumberOfRenewsPerMin

  • 代码块 (count * 2) > (serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold) :当开启自我保护机制时,应用实例每分钟最大心跳数( count * 2 ) 小于期望最小每分钟续租次数( serverConfig.getRenewalPercentThreshold() * numberOfRenewsPerMinThreshold ),不重新计算。如果重新计算,自动保护机制会每次定时执行后失效
    应用实例注册时,增加 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin。

    public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        // 获取读锁
        read.lock();
        Map> gMap = registry.get(registrant.getAppName());
        // 增加 注册次数 到 监控
        REGISTER.increment(isReplication);
        // 获得 应用实例信息 对应的 租约
        if (gMap == null) {
            final ConcurrentHashMap> gNewMap = new ConcurrentHashMap>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap); // 添加 应用
            if (gMap == null) { // 添加 应用 成功
                gMap = gNewMap;
            }
        }
        Lease existingLease = gMap.get(registrant.getId());
        // Retain the last dirty timestamp without overwriting it, if there is already a lease
        if (existingLease != null && (existingLease.getHolder() != null)) { // 已存在时,使用数据不一致的时间大的应用注册信息为有效的
            Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp(); // Server 注册的 InstanceInfo
            Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp(); // Client 请求的 InstanceInfo
            logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
    
            // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
            // InstanceInfo instead of the server local copy.
            if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
                        " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
                registrant = existingLease.getHolder();
            }
        } else {
            // The lease does not exist and hence it is a new registration
            // 【自我保护机制】增加 `numberOfRenewsPerMinThreshold` 、`expectedNumberOfRenewsPerMin`
            synchronized (lock) {
                if (this.expectedNumberOfRenewsPerMin > 0) {
                    // Since the client wants to cancel it, reduce the threshold
                    // (1
                    // for 30 seconds, 2 for a minute)
                    this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
                    this.numberOfRenewsPerMinThreshold =
                            (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                }
            }
            logger.debug("No previous lease information found; it is new registration");
        }
        // 创建 租约
        Lease lease = new Lease(registrant, leaseDuration);
        if (existingLease != null) { // 若租约已存在,设置 租约的开始服务的时间戳
            lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
        }
        // 添加到 租约映射
        gMap.put(registrant.getId(), lease);
        // 添加到 最近注册的调试队列
        synchronized (recentRegisteredQueue) {
            recentRegisteredQueue.add(new Pair(
                    System.currentTimeMillis(),
                    registrant.getAppName() + "(" + registrant.getId() + ")"));
        }
        // 添加到 应用实例覆盖状态映射(Eureka-Server 初始化使用)
        // This is where the initial state transfer of overridden status happens
        if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
            logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
                            + "overrides", registrant.getOverriddenStatus(), registrant.getId());
            if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
            }
        }
        // 设置 应用实例覆盖状态
        InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
        if (overriddenStatusFromMap != null) {
            logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
            registrant.setOverriddenStatus(overriddenStatusFromMap);
        }
    
        // 获得 应用实例状态
        // Set the status based on the overridden status rules
        InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
        // 设置 应用实例状态
        registrant.setStatusWithoutDirty(overriddenInstanceStatus);
    
        // 设置 租约的开始服务的时间戳(只有第一次有效)
        // If the lease is registered with UP status, set lease service up timestamp
        if (InstanceStatus.UP.equals(registrant.getStatus())) {
            lease.serviceUp();
        }
        // 设置 应用实例信息的操作类型 为 添加
        registrant.setActionType(ActionType.ADDED);
        // 添加到 最近租约变更记录队列
        recentlyChangedQueue.add(new RecentlyChangedItem(lease));
        // 设置 租约的最后更新时间戳
        registrant.setLastUpdatedTimestamp();
        // 设置 响应缓存 过期
        invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
        logger.info("Registered instance {}/{} with status {} (replication={})",
                registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
    } finally {
        // 释放锁
        read.unlock();
    }
    

    }
    应用实例下线时,减少 numberOfRenewsPerMinThreshold 、expectedNumberOfRenewsPerMin。

    protected boolean internalCancel(String appName, String id, boolean isReplication) {
    try {
        // 获得读锁
        read.lock();
        // 增加 取消注册次数 到 监控
        CANCEL.increment(isReplication);
        // 移除 租约映射
        Map> gMap = registry.get(appName);
        Lease leaseToCancel = null;
        if (gMap != null) {
            leaseToCancel = gMap.remove(id);
        }
        // 添加到 最近取消注册的调试队列
        synchronized (recentCanceledQueue) {
            recentCanceledQueue.add(new Pair(System.currentTimeMillis(), appName + "(" + id + ")"));
        }
        // 移除 应用实例覆盖状态映射
        InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
        if (instanceStatus != null) {
            logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
        }
        // 租约不存在
        if (leaseToCancel == null) {
            CANCEL_NOT_FOUND.increment(isReplication); // 添加 取消注册不存在 到 监控
            logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
            return false; // 失败
        } else {
            // 设置 租约的取消注册时间戳
            leaseToCancel.cancel();
            // 添加到 最近租约变更记录队列
            InstanceInfo instanceInfo = leaseToCancel.getHolder();
            String vip = null;
            String svip = null;
            if (instanceInfo != null) {
                instanceInfo.setActionType(ActionType.DELETED);
                recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
                instanceInfo.setLastUpdatedTimestamp();
                vip = instanceInfo.getVIPAddress();
                svip = instanceInfo.getSecureVipAddress();
            }
            // 设置 响应缓存 过期
            invalidateCache(appName, vip, svip);
            logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
            return true; // 成功
        }
    } finally {
        // 释放锁
        read.unlock();
    }
    }
    

eureka的自我保护机制就介绍到这里了。

你可能感兴趣的:(Eureka源码分析(八) 自我保护机制)