Understanding eureka’s Self-protection Mechanism

  springcloud

Related parameters

eureka.instance.leaseRenewalIntervalInSeconds = 30 # hearbeat interval
eureka.server.renewalPercentThreshold = 0.85
eureka.server.renewalThresholdUpdateIntervalMs = 15 * 60 * 1000 # 15mins 

leaseRenewalIntervalInSeconds

The frequency at which the client sends heartbeats.

renewalPercentThreshold

Proportion Threshold of Heartbeats Triggering Self-protection

renewalThresholdUpdateIntervalMs

How often do I reset the heartbeat threshold

computational formula

Number of heartbeats expected from one client instance / min

factor = 60/leaseRenewalIntervalInSeconds

Number of heartbeats expected from N instances / min

factor * N

Minimum expected heartbeat threshold / min

factorNrenewalPercentThreshold

Source Code

this.expectedNumberOfRenewsPerMin = N * 2;
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());

When eureka starts up

eureka-core-1.4.12-sources.jar! /com/netflix/eureka/registry/PeerAwareInstanceRegistryImpl.java

@Override
    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        // Renewals happen every 30 seconds and for a minute it should be a factor of 2.
        this.expectedNumberOfRenewsPerMin = count * 2;
        this.numberOfRenewsPerMinThreshold =
                (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
        logger.info("Got " + count + " instances from neighboring DS node");
        logger.info("Renew threshold is: " + numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        boolean isAws = Name.Amazon == selfName;
        if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            primeAwsReplicas(applicationInfoManager);
        }
        logger.info("Changing status to UP");
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
        super.postInit();
    }

Count is the number of eureka, at this time expectedNumberOfRenewsPerMin is 2(1 registration center, sending a heartbeat every 30 seconds, then 2 heartbeats should be sent per minute for each client), numberofrenewspermin hreshold = (int) 2 * 0.85 = 1

Client registration

eureka-core-1.4.12-sources.jar! /com/netflix/eureka/registry/AbstractInstanceRegistry.java

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
            read.lock();
            Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
            REGISTER.increment(isReplication);
            if (gMap == null) {
                final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
                gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }
            Lease<InstanceInfo> 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();
                Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                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
                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<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
            if (existingLease != null) {
                lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
            }
            gMap.put(registrant.getId(), lease);
            synchronized (recentRegisteredQueue) {
                recentRegisteredQueue.add(new Pair<Long, String>(
                        System.currentTimeMillis(),
                        registrant.getAppName() + "(" + registrant.getId() + ")"));
            }
            // 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();
        }
    }

Among them, focus on this paragraph

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

Every time you register the previous instance, recalculate it, that is

this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());

In general, all instances are registered, that is

this.expectedNumberOfRenewsPerMin = N * 2;
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());

Calculation example

2 eureka, factor=6,N:16

leaseRenewalIntervalInSeconds: 10  --> 每分钟6次
this.expectedNumberOfRenewsPerMin = N * 2 = 32 ;
this.numberOfRenewsPerMinThreshold = (int) (32 * 0.85) = (int)27.2 = 27;

2 eureka, factor=2,N=3

leaseRenewalIntervalInSeconds: 30  --> 每分钟2次
this.expectedNumberOfRenewsPerMin = N * 2 = 6 ;
this.numberOfRenewsPerMinThreshold = (int) (6* 0.85) = (int)5.1 = 5;

For the stand-alone version of eureka, it is quite special, because in this method of openForTraffic

this.expectedNumberOfRenewsPerMin = count * 2;

Count=1 here calculates the heartbeat of one instance more, so it is as follows:

1 eureka, not registered, factor=2,N=1

leaseRenewalIntervalInSeconds: 30  --> 每分钟2次
this.expectedNumberOfRenewsPerMin = N * 2 = 2 ;
this.numberOfRenewsPerMinThreshold = (int) (2 * 0.85) = (int)1.7 = 1;

实际显示是3,应该是按N=2算
this.expectedNumberOfRenewsPerMin = N * 2 = 4 ;
this.numberOfRenewsPerMinThreshold = (int) (4 * 0.85) = (int)3.4 = 3;

1 eureka, not registered, factor=2,N=2

leaseRenewalIntervalInSeconds: 30  --> 每分钟2次
this.expectedNumberOfRenewsPerMin = N * 2 = 4 ;
this.numberOfRenewsPerMinThreshold = (int) (4 * 0.85) = (int)3.4 = 3;
实际显示是5,应该是按N=3算
this.expectedNumberOfRenewsPerMin = N * 2 = 6 ;
this.numberOfRenewsPerMinThreshold = (int) (6 * 0.85) = (int)5.1 = 5;

Reflect on the source code

Because in the eureka-core-1.4.12 version, if you adjust eureka.instance.leaserenewalintervalinsends, there is no corresponding adjustment factor in the code, that is, the code is still 60/30=2, so it will destroy the design idea built into Eureka. However, for small projects, if there is no cross-computer room and the network is not so bad, in order to avoid the problem that the service registration list cannot be modified due to self-protection, one of the following methods can be chosen to try:

  • Turn off self-protection eureka.server.enableselfpreservation = false

  • Turn down eureka.instance.leaserenewalintervalinsends, for example, to 10 seconds.

  • Decrease renewalPercentThreshold, for example, to 0.49.

In addition, there is another parameter that can be adjusted, that is, the cycle of recalculation of heartbeat threshold:
eureka.server.renewalThresholdUpdateIntervalMs = 15601000 # 15mins
The default is 15 minutes can be changed to a little smaller, such as 5 minutes =5601000

doc


For the latest content, please pay attention to WeChat public number

图片描述