Eureka Service Governance Source Code Analysis

Eureka is a service discovery framework developed by Netflix. It is itself a REST-based service. It is mainly used to locate middle-tier services running in the AWS domain to achieve load balancing and middle-tier service failover. SpringCloud integrates it in its subproject spring-cloud-netflix to realize SpringCloud's service discovery function.

Eureka Server : Provide service registration service. After each node is started, it will be registered in Eureka Server. The service registry in EurekaServer will store the information of all available service nodes. The information of service nodes can be seen intuitively in the interface .

The eureka client mainly handles service registration and discovery. The client service is embedded in the code of the client application through registration and parameter configuration. When the application starts, the Eureka client registers the services it provides with the service registry, and periodically sends heartbeats to renew its service lease. At the same time, he can also query the currently registered service information from the server and cache them locally and refresh the service status periodically.

First you need to import the pom file

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka-server</artifactId>
		</dependency>

                //spring-cloud-starter-eureka-server中会引入
                <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>

In the spring.factories file in spring-cloud-starter-netflix-eureka-server, the EurekaServerAutoConfiguration class is loaded.

When we use it, we will add the @EnableEurekaServer annotation in the application application. The annotation will import the EurekaServerMarkerConfiguration class. The Marker Bean object will be created in this class.

@Configuration
public class EurekaServerMarkerConfiguration {
	@Bean
	public Marker eurekaServerMarkerBean() {
		return new Marker();
	}
	class Marker {
	}
}

Continue to look at the EurekaServerAutoConfiguration class.

@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
		InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter 

The condition for loading the class is the Marker Bean object created before. That is why @EnableEurekaServer must be added. The EurekaServerAutoConfiguration class also loads some beans

	@ConditionalOnMissingBean
	public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
		EurekaServerConfigBean server = new EurekaServerConfigBean();
		if (clientConfig.shouldRegisterWithEureka()) {
			// Set a sensible default if we are supposed to replicate
			server.setRegistrySyncRetries(5);
		}
		return server;
	}
	@Bean
	public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
			ServerCodecs serverCodecs) {
		this.eurekaClient.getApplications(); // force initialization
		return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
				serverCodecs, this.eurekaClient,
				this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
				this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
	}

	@Bean
	@ConditionalOnMissingBean
	public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
			ServerCodecs serverCodecs) {
		return new PeerEurekaNodes(registry, this.eurekaServerConfig,
				this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
	}

	@Bean
	public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
			PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
		return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
				registry, peerEurekaNodes, this.applicationInfoManager);
	}

	@Bean
	public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
			EurekaServerContext serverContext) {
		return new EurekaServerBootstrap(this.applicationInfoManager,
				this.eurekaClientConfig, this.eurekaServerConfig, registry,
				serverContext);
	}

1. EurekaServerConfig is mainly some configuration properties of the Eureka server, such as synchronization period, timeout, etc.

2. The implementation class of peerAwareInstanceRegistry is InstanceRegistry, inherited from PeerAwareInstanceRegistryImpl, mainly: PeerAwareInstanceRegistryImpl The impact of new Eureka Server nodes joining the cluster New service registration (Register) The impact of service heartbeat (renew) service offline and elimination and self-protection model. Execute the recovery task of service information every 60S, the method is postInit.

3. PeerEurekaNodes is mainly used to synchronize other node information of the service cluster.

4. EurekaServerBootstrap: Eureka service startup loading trigger class.

5. EurekaServerContext: Mainly responsible for the startup of peerEurekaNodes and PeerAwareInstanceRegistry.

The class implementation of EurekaServerContext:

@Singleton
public class DefaultEurekaServerContext implements EurekaServerContext {
    private static final Logger logger = LoggerFactory.getLogger(DefaultEurekaServerContext.class);
    private final EurekaServerConfig serverConfig;
    private final ServerCodecs serverCodecs;
    private final PeerAwareInstanceRegistry registry;
    private final PeerEurekaNodes peerEurekaNodes;
    private final ApplicationInfoManager applicationInfoManager;

    @Inject
    public DefaultEurekaServerContext(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes, ApplicationInfoManager applicationInfoManager) {
        this.serverConfig = serverConfig;
        this.serverCodecs = serverCodecs;
        this.registry = registry;
        this.peerEurekaNodes = peerEurekaNodes;
        this.applicationInfoManager = applicationInfoManager;
    }

    @PostConstruct
    public void initialize() throws Exception {
        logger.info("Initializing ...");
        this.peerEurekaNodes.start();
        this.registry.init(this.peerEurekaNodes);
        logger.info("Initialized");
    }
}

The peerEurekaNodes.start() method will regularly update other Server nodes in the cluster

    public void start() {
		......
		// 初始化 集群节点信息
		updatePeerEurekaNodes(resolvePeerUrls());
		// 初始化 初始化固定周期更新集群节点信息的任务
		Runnable peersUpdateTask = new Runnable() {
			@Override
			public void run() {
				try {
					updatePeerEurekaNodes(resolvePeerUrls());
				} catch (Throwable e) {
					logger.error("Cannot update the replica Nodes", e);
				}

			}
		};
		// 每隔10分钟更新集群节点
		taskExecutor.scheduleWithFixedDelay(
			peersUpdateTask,
			serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
			serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
			TimeUnit.MILLISECONDS
		);
		......
	}
	
	/**
     * Resolve peer URLs. 获取Server集群的所有serviceUrl,不包括自身
     *
     * @return peer URLs with node's own URL filtered out
     */
    protected List<String> resolvePeerUrls() {
        // 获得 Eureka-Server 集群服务地址数组
        InstanceInfo myInfo = applicationInfoManager.getInfo();
        String zone = InstanceInfo.getZone(clientConfig.getAvailabilityZones(clientConfig.getRegion()), myInfo);
        // 获取相同Region下的所有serviceUrl
        List<String> replicaUrls = EndpointUtils.getDiscoveryServiceUrls(clientConfig, zone, new EndpointUtils.InstanceInfoBasedUrlRandomizer(myInfo));

        // 移除自己(避免向自己同步)
        int idx = 0;
        while (idx < replicaUrls.size()) {
            if (isThisMyUrl(replicaUrls.get(idx))) {
                replicaUrls.remove(idx);
            } else {
                idx++;
            }
        }
        return replicaUrls;
    }

The registry.init(this.peerEurekaNodes) method is mainly responsible for counting the number of renewals and the renewal threshold. Every 15 minutes.

    public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
        this.numberOfReplicationsLastMin.start();
        this.peerEurekaNodes = peerEurekaNodes;
        this.initializedResponseCache();
        this.scheduleRenewalThresholdUpdateTask();
        this.initRemoteRegionRegistry();
        try {
            Monitors.registerObject(this);
        } catch (Throwable var3) {
            logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", var3);
        }
    }
    //默认每隔15分钟进行一次检查
    private void scheduleRenewalThresholdUpdateTask() {
        this.timer.schedule(new TimerTask() {
            public void run() {
                PeerAwareInstanceRegistryImpl.this.updateRenewalThreshold();
            }
        }, (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs(), (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs());
    }
    private void updateRenewalThreshold() {
        try {
            Applications apps = this.eurekaClient.getApplications();
            int count = 0;
            Iterator var3 = apps.getRegisteredApplications().iterator();

            while(var3.hasNext()) {
                Application app = (Application)var3.next();
                Iterator var5 = app.getInstances().iterator();

                while(var5.hasNext()) {
                    InstanceInfo instance = (InstanceInfo)var5.next();
                    if (this.isRegisterable(instance)) {
                        ++count;
                    }
                }
            }

            Object var10 = this.lock;
            synchronized(this.lock) {
                if ((double)(count * 2) > this.serverConfig.getRenewalPercentThreshold() * (double)this.numberOfRenewsPerMinThreshold || !this.isSelfPreservationModeEnabled()) {
                    //设置期望续约数
                    this.expectedNumberOfRenewsPerMin = count * 2;
                    //设置续约阀值数量 默认比例是 cout * 0.85
                    this.numberOfRenewsPerMinThreshold = (int)((double)(count * 2) * this.serverConfig.getRenewalPercentThreshold());
                }
            }

            logger.info("Current renewal threshold is : {}", this.numberOfRenewsPerMinThreshold);
        } catch (Throwable var9) {
            logger.error("Cannot update renewal threshold", var9);
        }

    }

This method is executed every 15 minutes to count the number of all current instances, and then set the expected renewal number and the renewal threshold number. The renewal threshold quantity is expected renewal number * 0.85. The number of renewals will not be reduced below 85% at one time, that is, it can only be renewed when the number of surviving application information exceeds 85% of the total, so that the self-protection threshold of renewal will not be modified.

When we visit http://localhost:8761/ locally , we will call the PeerAwareInstanceRegistryImpl.isBelowRenewThresold method to determine whether to prompt to enable protection.

    public int isBelowRenewThresold() {
        return this.getNumOfRenewsInLastMin() <= (long)this.numberOfRenewsPerMinThreshold && this.startupTime > 0L && 
System.currentTimeMillis() > this.startupTime + (long)this.serverConfig.getWaitTimeInMsWhenSyncEmpty() ? 1 : 0;
    }

It is to judge that if the number of renewals in the last minute is less than or equal to the renewal threshold, and the current time > the set statistics start time (5 minutes by default), a prompt will appear on the page.

Set the parameter to start counting time as waitTimeInMsWhenSyncEmpty, you can directly set it to 0 to see the effect.

eureka:
  server:
    waitTimeInMsWhenSyncEmpty: 0

As mentioned earlier, EurekaServerBootstrap is the trigger class for Eureka service startup and loading. The EurekaServerBootstrap.contextInitialized method will be executed through the EurekaServerInitializerConfiguration.start method

	public void contextInitialized(ServletContext context) {
		try {
			initEurekaEnvironment();
			initEurekaServerContext();

			context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
		}
		catch (Throwable e) {
			log.error("Cannot bootstrap eureka server :", e);
			throw new RuntimeException("Cannot bootstrap eureka server :", e);
		}
	}

Mainly look at the initEurekaServerContext method

	protected void initEurekaServerContext() throws Exception {
		// For backward compatibility
		JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);
		XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);

		if (isAws(this.applicationInfoManager.getInfo())) {
			this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
					this.eurekaClientConfig, this.registry, this.applicationInfoManager);
			this.awsBinder.start();
		}

		EurekaServerContextHolder.initialize(this.serverContext);

		log.info("Initialized server context");

		// 注册数量
		int registryCount = this.registry.syncUp();
		this.registry.openForTraffic(this.applicationInfoManager, registryCount);

		// Register all monitoring statistics.
		EurekaMonitors.registerAllStats();
	}

跟进registry.openForTraffic(this.applicationInfoManager, registryCount)

    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        //设置当前期望续约数量
        this.expectedNumberOfRenewsPerMin = count * 2;
        //设置续约阀值
        this.numberOfRenewsPerMinThreshold = (int)((double)this.expectedNumberOfRenewsPerMin * this.serverConfig.getRenewalPercentThreshold());
        logger.info("Got " + count + " instances from neighboring DS node");
        logger.info("Renew threshold is: " + this.numberOfRenewsPerMinThreshold);
        //设置启动时间
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }

        Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        boolean isAws = Name.Amazon == selfName;
        if (isAws && this.serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            this.primeAwsReplicas(applicationInfoManager);
        }

        logger.info("Changing status to UP");
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
        super.postInit();
    }

The main thing is to calculate the expected renewal number at startup (that is, synchronized from other clusters, if it is a single machine, the value is 1), as well as the renewal threshold and startup time, and then enter super. postInit()

    protected void postInit() {
        //统计前一分钟收到的续约次数,register、renew都会修改里面的count
        this.renewsLastMin.start();
        if (this.evictionTaskRef.get() != null) {
            ((AbstractInstanceRegistry.EvictionTask)this.evictionTaskRef.get()).cancel();
        }

        this.evictionTaskRef.set(new AbstractInstanceRegistry.EvictionTask());
        this.evictionTimer.schedule((TimerTask)this.evictionTaskRef.get(), this.serverConfig.getEvictionIntervalTimerInMs(), this.serverConfig.getEvictionIntervalTimerInMs());
    }

    //每隔一分钟统计最近一分钟内所有Client的续约次数,也就是接收到的心跳次数,以此来作为是否触发服务信息回收的依据之一
    public synchronized void start() {
        if (!this.isActive) {
            this.timer.schedule(new TimerTask() {
                public void run() {
                    try {
                        //每分钟清0一次
                       MeasuredRate.this.lastBucket.set(MeasuredRate.this.currentBucket.getAndSet(0L));
                    } catch (Throwable var2) {
                        MeasuredRate.logger.error("Cannot reset the Measured Rate", var2);
                    }

                }
            }, this.sampleInterval, this.sampleInterval);
            this.isActive = true;
        }

    }

Continue to look at evictionTaskRef.set(new AbstractInstanceRegistry.EvictionTask()), and the set evictionTimer.schedule((TimerTask)this.evictionTaskRef.get(), this.serverConfig.getEvictionIntervalTimerInMs(), this.serverConfig.getEvictionIntervalTimerInMs( ))method.

        EvictionTask() {
        }

        public void run() {
            try {
                long compensationTimeMs = this.getCompensationTimeMs();
                AbstractInstanceRegistry.logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
                AbstractInstanceRegistry.this.evict(compensationTimeMs);
            } catch (Throwable var3) {
                AbstractInstanceRegistry.logger.error("Could not run the evict task", var3);
            }
        }

public void evict(long additionalLeaseMs) {
    logger.debug("Running the evict task");
    // 判断是否开启自我保护机制
    if (!isLeaseExpirationEnabled()) {
        logger.debug("DS: lease expiration is currently disabled.");
        return;
    }
    
    List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
    // 循环遍历本地CurrentHashMap中的实例信息
    for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
        Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
        if (leaseMap != null) {
            for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
                Lease<InstanceInfo> lease = leaseEntry.getValue();
                // 判断是否过期,此处为重点,里面有判断实例过期的依据
                if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
                    expiredLeases.add(lease);
                }
            }
        }
    }
    // 获取注册的实例数量
    int registrySize = (int) getLocalRegistrySize();
    // serverConfig.getRenewalPercentThreshold() 为0.85 , 主要是为了避免开启自动保护机制。 所以会逐步过期
    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<InstanceInfo> 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 performs a recycling task of service information every 60S, and removes those services that have not updated lease information for more than 90S. Of course, the premise that it can be recycled is that self-protection is not triggered. The so-called self-protection mechanism means that the actual number of contract renewals in the last minute exceeds 85% of the expected total number. If it does not exceed, then it is considered that there is a problem with the server, and the service recovery will not be performed.

Lease.isExpire()

Judging whether it has expired


public boolean isExpired(long additionalLeaseMs) {
    return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
}

 When renewing the contract, the last update time will be updated


public void renew() {
    lastUpdateTimestamp = System.currentTimeMillis() + duration;
}

duration : Expiration interval, the default is 90 seconds.

lastUpdateTimestamp: The last update time, current time + 90s.

But in the final judgment, lastUpdateTimestamp + duration + additionalLeaseMs is added again, which leads to the fact that the current time must be greater than the actual last update time by 180 seconds before it is considered expired.

The timing analysis of the server ends here

==================================== Dividing line ================ ===============================================

EurekaClient client

After the client starts, the main implementation class is DiscoveryClient. There are several key points in the construction method of this class.

 DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {
        .................
        //如果作为EurekaServer 是无需注册和拉取的
       if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            logger.info("Client configured to neither register nor query for data.");
            scheduler = null;
            heartbeatExecutor = null;
            cacheRefreshExecutor = null;
            eurekaTransport = null;
            instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());

            // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
            // to work with DI'd DiscoveryClient
            DiscoveryManager.getInstance().setDiscoveryClient(this);
            DiscoveryManager.getInstance().setEurekaClientConfig(config);

            initTimestampMs = System.currentTimeMillis();
            logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                    initTimestampMs, this.getApplications().size());

            return;  // no need to setup up an network tasks and we are done
        }
       //如果不是,创建三个线程池
      try {
            // default size of 2 - 1 each for heartbeat and cacheRefresh
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
            ....................
            initScheduledTasks();
            .....................
}

If it is used as EurekaServer, there is no need to register and pull.

If it is used as EurekaClient, create three thread pools, one is to create a thread pool with a number of 2 for timing scheduling, the other is to execute the thread pool for sending heartbeat, and the other is to get the thread pool for the list. In the initScheduledTasks method.

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

        if (clientConfig.shouldRegisterWithEureka()) {
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);

            // Heartbeat timer
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);
          ................
}

You can see the list of CacheRefreshThread() delayed execution refresh is the registryFetchIntervalSeconds parameter, which defaults to 30s, that is, refreshes every 30s. HeartbeatThread() delays the execution of sending heartbeats with the renewalIntervalInSecs parameter, which defaults to 30s.

   class CacheRefreshThread implements Runnable {
        public void run() {
            refreshRegistry();
        }
    }

    @VisibleForTesting
    void refreshRegistry() {
        try {
            boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries();

            boolean remoteRegionsModified = false;
            // This makes sure that a dynamic change to remote regions to fetch is honored.
            String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions();
            if (null != latestRemoteRegions) {
                String currentRemoteRegions = remoteRegionsToFetch.get();
                if (!latestRemoteRegions.equals(currentRemoteRegions)) {
                    // Both remoteRegionsToFetch and AzToRegionMapper.regionsToFetch need to be in sync
                    synchronized (instanceRegionChecker.getAzToRegionMapper()) {
                        if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) {
                            String[] remoteRegions = latestRemoteRegions.split(",");
                            remoteRegionsRef.set(remoteRegions);
                            instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions);
                            remoteRegionsModified = true;
                        } else {
                            logger.info("Remote regions to fetch modified concurrently," +
                                    " ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions);
                        }
                    }
                } else {
                    // Just refresh mapping to reflect any DNS/Property change
                    instanceRegionChecker.getAzToRegionMapper().refreshMapping();
                }
            }

            boolean success = fetchRegistry(remoteRegionsModified);
            if (success) {
                registrySize = localRegionApps.get().size();
                lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
            }

            if (logger.isDebugEnabled()) {
                StringBuilder allAppsHashCodes = new StringBuilder();
                allAppsHashCodes.append("Local region apps hashcode: ");
                allAppsHashCodes.append(localRegionApps.get().getAppsHashCode());
                allAppsHashCodes.append(", is fetching remote regions? ");
                allAppsHashCodes.append(isFetchingRemoteRegionRegistries);
                for (Map.Entry<String, Applications> entry : remoteRegionVsApps.entrySet()) {
                    allAppsHashCodes.append(", Remote region: ");
                    allAppsHashCodes.append(entry.getKey());
                    allAppsHashCodes.append(" , apps hashcode: ");
                    allAppsHashCodes.append(entry.getValue().getAppsHashCode());
                }
                logger.debug("Completed cache refresh task for discovery. All Apps hash code is {} ",
                        allAppsHashCodes.toString());
            }
        } catch (Throwable e) {
            logger.error("Cannot fetch registry from server", e);
        }        
    }

Continue to see the pull process

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

 Full pull processing

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

So the question is, if you start a EurekaClient service, how long can another Eureka Client be found?

First, Eureka caches HTTP responses. In line 109 of Eureka's "controller" class ApplicationResource, you can see that there is a call of String payLoad = responseCache.get(cacheKey);
the function of the getApplication() method where the code is located is to respond to the client querying a service information HTTP request:

String payLoad = responseCache.get(cacheKey); // 从cache中拿响应数据

if (payLoad != null) {
       logger.debug("Found: {}", appName);
       return Response.ok(payLoad).build();
} else {
       logger.debug("Not Found: {}", appName);
       return Response.status(Status.NOT_FOUND).build();
}

In the above code, responseCache refers to the ResponseCache type, which is an interface, and its get() method will first query the data in the cache, and if not, generate data return (that is, actually query the registration list), and the cache is valid The time is 30s. In other words, the client's response to Eureka is not necessarily instant, and most of the time it is just cached information.

Secondly, Eureka Client also caches the obtained registration information for 30s. That is, after the service finds the available service address for the first time through the eureka client, the result will be cached and pulled every 30s.

Again, the load balancing component Ribbon also has a 30s cache. Ribbon will get the service list from the Eureka Client mentioned above, and then cache the result for 30s.

Finally, if you are not using these components (Eureka, Ribbon) in the Spring Cloud environment, your service will not register with Eureka immediately after starting, but will not register until the first heartbeat request is sent. The sending interval of the heartbeat request is also 30s. (Spring Cloud has modified this, and the service will be registered immediately after it starts)

So if the last condition is removed, it will take up to 90s to discover a service.

Summarize:

1. EurekaServer counts the number of renewals and the renewal threshold every 15 minutes.

2. EurekaServer executes the recycling task of service information every 60S, and removes those services that have not updated the lease information for more than 90S. But the actual removal exceeds 180s.

3. EurekaServer updates the responseCache every 30s to return the Eureka Client request list information.

4. Eureka Client sends a heartbeat renewal to EurekaServer every 30s.

5. Eureka Client pulls service list information from EurekaServer every 30s.

 

 

Guess you like

Origin blog.csdn.net/yytree123/article/details/109018545