SpringCloud源码:服务端分析(二)- EurekaServer分析

e42e9ec51b0070d1915074e0e5b8deaa.jpeg

背景

从昨日的两篇文章:SpringCloud源码:客户端分析(一)- SpringBootApplication注解类加载流程、SpringCloud源码:客户端分析(二)- 客户端源码分析。

我们理解了客户端的初始化,其实跟SpringBootApplication初始化机制息息相关,也和自动化配置类有关。

现在我们一起来分析下服务端的初始化流程,开始之前,我们先梳理下几个常用的框架注解。

@Import注解

作用:

  • 导入一个或多个Bean

  • 导入@Configuration类

  • 导入ImportSelector的实现类

  • 导入ImportBeanDefinitionRegistrar的实现类

使用前提:@Import必须要在@controller、@Service、@Component、@Configuration、@Repository修饰的类下,并且要在springboot扫描范围内,这些注解修饰的类默认是必须和springboot启动类同包才会被扫描到,当然也可以手动指定扫描包。

@EnableConfigurationProperties注解

后面跟着的,就是一个活多个配置类了。

参考:https://www.cnblogs.com/JavaYuYin/p/18060520

@ConditionalOnBean注解

跟@ConditionalOnMissingBean相反,@ConditionalOnBean注解是,如果有容器中有类,就注入备用类,如果没有类就不注入。

其他个性化加载配置

76e49a1d49013465fced3d377a4971ef.png

源码分析

@EnableEurekaServer注解:修饰启动类

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {public static void main(String[] args) {SpringApplication.run(EurekaApplication.class, args);}
}

@EnableEurekaServer是一个启动类注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {}

分析:

  • @Import(EurekaServerMarkerConfiguration.class):引入了 EurekaServerMarkerConfiguration资源类

  • EurekaServerMarkerConfiguration是一个标识配置类

EurekaServerMarkerConfiguration

@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {@Beanpublic Marker eurekaServerMarkerBean() {return new Marker();}class Marker {// 空类,没有任何定义}
}

分析:

  • @Configuration 和 @Bean 结合使用,添加了Marker对象到容器里

  • 而Marker是一个空类,它的作用如其名,只是起到标识作用;

留下问题:那么Marker类在哪里起作用呢?通过全局搜索,知道在EurekaServerAutoConfiguration 注册中心的自动配置类有匹配到。

EurekaServerAutoConfiguration:自动装配类

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {// ...
}

分析:

  • @ConditionalOnBean:容器存在EurekaServerMarkerConfiguration.Marker时,则进行 EurekaServerAutoConfiguration 的自动初始化装配

  • @EnableConfigurationProperties:加载特定的@Configuration类(EurekaDashboardProperties 和 InstanceRegistryProperties)

    • EurekaDashboardProperties:

    • InstanceRegistryProperties:

  • @Configuration + @Import:资源加载EurekaServerInitializerConfiguration配置类

  • @PropertySource:读取资源配置文件

从EurekaServerAutoConfiguration,我们发现有两条初始化EurekaServer相关组件的路线:@Import资源加载 和 @Bean初始化。

两条初始化路线

  • @Import资源加载:加载EurekaServerInitializerConfiguration.class,并执行start方法

    • 执行eurekaServerBootstrap.contextInitialized

    • 新建了一个剔除无效服务任务,并给线程池周期性执行

  • @Bean初始化:新建DefaultEurekaServerContext,并在初始化bean之后,执行@PostConstruct修饰的initialize()方法:

    • scheduleRenewalThresholdUpdateTask(),新建了一个续约线程,并给线程池周期性执行

    • 新建了一个taskExecutor(单线程池),更新注册表线程updatePeerEurekaNodes,并给线程池周期性执行

    • peerEurekaNodes.start();

    • registry.init(peerEurekaNodes);

下面我将按照流程图来讲解两条路线的源码

cbfb816e5be17a5c3d6ceef12cb4a4f0.png


路线一:@Import资源加载

加载的资源是EurekaServerInitializerConfiguration。

初始化配置类的整体流程如下

EurekaServerInitializerConfiguration

->EurekaServerInitializerConfiguration#start

->EurekaServerBootstrap#contextInitialized

->EurekaServerBootstrap#initEurekaServerContext

->PeerAwareInstanceRegistryImpl#syncUp

->PeerAwareInstanceRegistryImpl#openForTraffic

->AbstractInstanceRegistry#postInit

1、EurekaServerInitializerConfiguration#start

由上面的资源加载@Import,可以知道实现了SmartLifecycle接口,即Lifecycle接口。

@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfigurationimplements ServletContextAware, SmartLifecycle, Ordered {@Overridepublic void start() {new Thread(() -> {try {eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);log.info("Started Eureka Server");publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));EurekaServerInitializerConfiguration.this.running = true;publish(new EurekaServerStartedEvent(getEurekaServerConfig()));}catch (Exception ex) {// Help!log.error("Could not initialize Eureka servlet context", ex);}}).start();}
}

分析:

  • EurekaServerInitializerConfiguration的start()方法,是start启动方法

    • 该方法会在,新建线程里,被触发执行

2、EurekaServerBootstrap#contextInitialized

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

分析:

  • initEurekaServerContext():

3、EurekaServerBootstrap#initEurekaServerContext

protected void initEurekaServerContext() throws Exception {// For backward compatibilityJsonXStream.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");// Copy registry from neighboring eureka nodeint registryCount = this.registry.syncUp();this.registry.openForTraffic(this.applicationInfoManager, registryCount);// Register all monitoring statistics.EurekaMonitors.registerAllStats();
}

分析:

  • 注册JSON和XML序列化转换器以保持向后兼容性。

  • 如果应用信息表明运行在AWS环境中,则初始化并启动AWS绑定代理。

  • 初始化Eureka服务器上下文。

  • 从邻近Eureka节点同步注册表数据,并打开流量:registry.syncUp():

  • 注册所有监控统计信息:registry.openForTraffic:

4、PeerAwareInstanceRegistryImpl.syncUp()

public int syncUp() {// Copy entire entry from neighboring DS nodeint count = 0;// 【1】serverConfig获取配置项(注册重试次数)for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {if (i > 0) {try {// 【2】serverConfig获取配置项(重试间隔),进行休眠Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());} catch (InterruptedException e) {logger.warn("Interrupted during registry transfer..");break;}}Applications apps = eurekaClient.getApplications();for (Application app : apps.getRegisteredApplications()) {for (InstanceInfo instance : app.getInstances()) {try {if (isRegisterable(instance)) {register(instance, instance.getLeaseInfo().getDurationInSecs(), true);count++;}} catch (Throwable t) {logger.error("During DS init copy", t);}}}}return count;
}

5、PeerAwareInstanceRegistryImpl#openForTraffic

@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.expectedNumberOfClientsSendingRenews = count;updateRenewsPerMinThreshold();logger.info("Got {} instances from neighboring DS node", count);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");// 将当前的EurekaServer上线applicationInfoManager.setInstanceStatus(InstanceStatus.UP);super.postInit();
}

6、AbstractInstanceRegistry#postInit

private Timer evictionTimer = new Timer("Eureka-EvictionTimer", true);private final AtomicReference<EvictionTask> evictionTaskRef = new AtomicReference<EvictionTask>();protected void postInit() {renewsLastMin.start();if (evictionTaskRef.get() != null) {evictionTaskRef.get().cancel();}evictionTaskRef.set(new EvictionTask());//【1】剔除任务执行evictionTimer.schedule(evictionTaskRef.get(),serverConfig.getEvictionIntervalTimerInMs(),serverConfig.getEvictionIntervalTimerInMs());
}class EvictionTask extends TimerTask {private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);@Overridepublic void run() {try {long compensationTimeMs = getCompensationTimeMs();logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);// 剔除任务evict(compensationTimeMs);} catch (Throwable e) {logger.error("Could not run the evict task", e);}}
}public void evict(long additionalLeaseMs) {logger.debug("Running the evict task");// 【1】判断是否进行剔除操作if (!isLeaseExpirationEnabled()) {logger.debug("DS: lease expiration is currently disabled.");return;}// 【2】遍历注册服务列表List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();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();int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());int evictionLimit = registrySize - registrySizeThreshold;// 【3】从失效租约数量,和失效最大限制里,取最小值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());// 【4】循环剔除for (int i = 0; i < toEvict; i++) {// 【5】随机从expiredLeases里剔除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);// 【6】剔除服务名internalCancel(appName, id, false);}}
}protected boolean internalCancel(String appName, String id, boolean isReplication) {read.lock();try {CANCEL.increment(isReplication);// 【7】找到注册服务信息Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);Lease<InstanceInfo> leaseToCancel = null;if (gMap != null) {// 【8】找到租约leaseToCancel = gMap.remove(id);}//....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 {// 【9】租约取消leaseToCancel.cancel();// 【10】找到取消的实例信息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();}// 【11】取消实例信息、vip区域、svip区域invalidateCache(appName, vip, svip);logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);}} finally {read.unlock();}synchronized (lock) {if (this.expectedNumberOfClientsSendingRenews > 0) {// Since the client wants to cancel it, reduce the number of clients to send renews.this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;updateRenewsPerMinThreshold();}}return true;}protected boolean internalCancel(String appName, String id, boolean isReplication) {read.lock();try {CANCEL.increment(isReplication);Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);Lease<InstanceInfo> leaseToCancel = null;if (gMap != null) {leaseToCancel = gMap.remove(id);}recentCanceledQueue.add(new Pair<Long, String>(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);}} finally {read.unlock();}synchronized (lock) {if (this.expectedNumberOfClientsSendingRenews > 0) {// Since the client wants to cancel it, reduce the number of clients to send renews.this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;updateRenewsPerMinThreshold();}}return true;
}

分析:

  • evictionTimer定时启动任务EvictionTask,即剔除任务

7842dba907c0b6865da01a39aef29571.png

路线二:@Bean 初始化 DefaultEurekaServerContext

@Bean初始化:new DefaultEurekaServerContext

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {@Bean@ConditionalOnMissingBeanpublic EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager);}
}
DefaultEurekaServerContext#initialize

在DefaultEurekaServerContext接口可以看到@PostConstruct修饰了initialize()方法,那么跟着初始化。

@PostConstruct
@Override
public void initialize() {logger.info("Initializing ...");peerEurekaNodes.start();try {registry.init(peerEurekaNodes);} catch (Exception e) {throw new RuntimeException(e);}logger.info("Initialized");
}

代码分析:

- 步骤【1】peerEurekaNodes.start():启动peerEurekaNodes。

- 步骤【2】registry.init(peerEurekaNodes):尝试初始化registry,参数为peerEurekaNodes。

 步骤【1】:PeerEurekaNodes#start

public void start() {// 【1】初始化单线程池taskExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");thread.setDaemon(true);return thread;}});try {updatePeerEurekaNodes(resolvePeerUrls());// 【2】初始化任务Runnable peersUpdateTask = new Runnable() {@Overridepublic void run() {try {// 【2.1】任务的详情,就是更新Eureka节点信息updatePeerEurekaNodes(resolvePeerUrls());} catch (Throwable e) {logger.error("Cannot update the replica Nodes", e);}}};// 【3】线程池执行任务taskExecutor.scheduleWithFixedDelay(peersUpdateTask,serverConfig.getPeerEurekaNodesUpdateIntervalMs(),serverConfig.getPeerEurekaNodesUpdateIntervalMs(),TimeUnit.MILLISECONDS);} catch (Exception e) {throw new IllegalStateException(e);}for (PeerEurekaNode node : peerEurekaNodes) {logger.info("Replica node URL:  {}", node.getServiceUrl());}
}

代码分析:

- 新建了一个定时任务,用单线程池周期性去执行。

 步骤【2】:PeerAwareInstanceRegistryImpl#init
@Override
public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {this.numberOfReplicationsLastMin.start();this.peerEurekaNodes = peerEurekaNodes;//【1】initializedResponseCache();//【2】scheduleRenewalThresholdUpdateTask();//【3】initRemoteRegionRegistry();try {Monitors.registerObject(this);} catch (Throwable e) {logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);}
}

分析:

  • 【1】缓存相关的初始化信息:   initializedResponseCache();

  • 【2】更新续约的阀值:scheduleRenewalThresholdUpdateTask();

  • 【3】初始化远程区域注册表:initRemoteRegionRegistry();

[1]缓存相关的初始化信息 initializedResponseCache
@Override
public synchronized void initializedResponseCache() {if (responseCache == null) {responseCache = new ResponseCacheImpl(serverConfig, serverCodecs, this);}
}

分析:

  • 最终new了一个ResponseCacheImpl类

查看一下ResponseCacheImpl的构造器

private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();private final java.util.Timer timer = new java.util.Timer("Eureka-CacheFillTimer", true);private final LoadingCache<Key, Value> readWriteCacheMap;ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {// 【1】this.serverConfig = serverConfig;this.serverCodecs = serverCodecs;this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();// 【2】this.registry = registry;long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();// 【3】this.readWriteCacheMap =CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache()).expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS).removalListener(new RemovalListener<Key, Value>() {@Overridepublic void onRemoval(RemovalNotification<Key, Value> notification) {Key removedKey = notification.getKey();if (removedKey.hasRegions()) {Key cloneWithNoRegions = removedKey.cloneWithoutRegions();regionSpecificKeys.remove(cloneWithNoRegions, removedKey);}}}).build(new CacheLoader<Key, Value>() {@Overridepublic Value load(Key key) throws Exception {if (key.hasRegions()) {Key cloneWithNoRegions = key.cloneWithoutRegions();regionSpecificKeys.put(cloneWithNoRegions, key);}Value value = generatePayload(key);return value;}});// 【4】if (shouldUseReadOnlyResponseCache) {timer.schedule(getCacheUpdateTask(),new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)+ responseCacheUpdateIntervalMs),responseCacheUpdateIntervalMs);}try {Monitors.registerObject(this);} catch (Throwable e) {logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);}
}private TimerTask getCacheUpdateTask() {return new TimerTask() {@Overridepublic void run() {logger.debug("Updating the client cache from response cache");for (Key key : readOnlyCacheMap.keySet()) {if (logger.isDebugEnabled()) {logger.debug("Updating the client cache from response cache for key : {} {} {} {}",key.getEntityType(), key.getName(), key.getVersion(), key.getType());}try {CurrentRequestVersion.set(key.getVersion());Value cacheValue = readWriteCacheMap.get(key);Value currentCacheValue = readOnlyCacheMap.get(key);if (cacheValue != currentCacheValue) {readOnlyCacheMap.put(key, cacheValue);}} catch (Throwable th) {logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);} finally {CurrentRequestVersion.remove();}}}};
}

分析:

  • 【1】serverConfig:是配置类,专门读取“eureka.server”开头的配置项

    • useReadOnlyResponseCache:默认是true,是否开启缓存 配置参数:eureka.server.use-read-only-response-cache

    • responseCacheUpdateIntervalMs:默认30 * 1000ms,如果开启缓存,缓存多少时间同步一次。配置参数:eureka.server.response-cache-update-interval-ms

    • initialCapacityOfResponseCache:默认1000

    • responseCacheAutoExpirationInSeconds:默认180

  • 【2】注册器赋值

  • 【3】实例信息保存在LoadingCache里

  • 【4】新建了一个定时任务,启动了一个名为Eureka-CacheFillTimer的定时更新缓存任务

[2]更新续约的阈值 initializedResponseCache
private void scheduleRenewalThresholdUpdateTask() {timer.schedule(new TimerTask() {@Overridepublic 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;}}}synchronized (lock) {// Update threshold only if the threshold is greater than the// current expected threshold or if self preservation is disabled.if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)|| (!this.isSelfPreservationModeEnabled())) {this.expectedNumberOfClientsSendingRenews = count;updateRenewsPerMinThreshold();}}logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);} catch (Throwable e) {logger.error("Cannot update renewal threshold", e);}
}

代码分析

  • 新建了一个定时任务,并用定时器周期性去执行

    • ‍获取所有注册的应用实例信息。

    • 统计符合条件的可注册实例数量。

    • 在同步锁内,若统计数量大于当前预期阈值或自我保护模式未启用,则更新预期发送心跳的客户端数量,并重新计算心跳阈值。

    • 记录更新后的心跳阈值日志。

步骤【2】initializedResponseCache 最终启动了一个RenewalThresholdTask

小结

后台启动了三个Timer定时线程

  • EurekaServerInitializerConfiguration#start

    • 启动一个定时任务:周期执行EvictionTask

      • 功能:清理过期的注册信息。

  • DefaultEurekaServerContext的@Bean初始化过程

    • 启动了一个定时任务:CacheUpdateTask

      • 功能:自动过期缓存中的响应。(Eureka Server 维护了一个响应缓存,用于存储客户端请求的结果以提高性能。这个定时任务负责定期使缓存中的条目过期,以确保缓存数据的新鲜度)

    • 启动了一个定时任务:RenewalThresholdTask

      • 功能:更新续约阈值(这个阈值是基于最近一段时间内收到的续约请求数量动态计算的)

总结

Eureka Client的续约机制是确保服务注册信息准确性的关键,通过定时向Eureka Server发送续约请求,Eureka Client能够有效地维护其在服务注册表中的状态。

同时,Eureka Server通过监控续约情况,及时剔除不活跃的服务实例,保证了服务发现的可靠性。

其他文章

SpringCloud源码:客户端分析(一)- SpringBootApplication注解类加载流程

SpringCloud源码:客户端分析(二)- 客户端源码分析

Kafka消息堆积问题排查

基于SpringMVC的API灰度方案

理解到位:灾备和只读数据库

SQL治理经验谈:索引覆盖

Mybatis链路分析:JDK动态代理和责任链模式的应用

大模型安装部署、测试、接入SpringCloud应用体系

Mybatis插件-租户ID的注入&拦截应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/436917.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows 录音编码为flv格式时,pcm采样格式

这里使用的是0x3e&#xff0c;转换为二进制&#xff1a; 0 0 1 1 1 1 1 0 前四个字节为3&#xff0c;表示Linear Pcm, 后4个字节1 1 1 0 表示44100HZ采样&#xff0c; 16个bit&#xff0c;单声道。 故&#xff0c;windows 音频采样不支持48000HZ频率

c++11~c++20 内联命名空间

在工作&#xff0c;我们经常会引入第三方库&#xff0c;偶尔会碰到同名的函数和类型&#xff0c;造成编译冲突的问题。一般我们可以使用命名空间&#xff0c;例如 #include <iostream> #include <iostream> using namespace std;namespace S1 {void foo(){cout &l…

css3-----2D转换、动画

2D 转换&#xff08;transform&#xff09; 转换&#xff08;transform&#xff09;是CSS3中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果 移动&#xff1a;translate旋转&#xff1a;rotate缩放&#xff1a;scale 二维坐标系 2D 转换之移动 trans…

VR视频怎样进行加密和一机一码的使用?--加密(一)

在视频加密领域&#xff0c;我们常见接触的就是在普通设备上使用的加密视频&#xff0c;如电脑、手机、平板等。Vr的发展和兴起给人们带来最真实的体验感受&#xff0c;不仅在游戏行业应用较广&#xff0c;在一些影院或者元宇宙文旅、展厅等视频场景也备受青睐。 随着VR视频场景…

使用 Llama 3.1 和 Qdrant 构建多语言医疗保健聊天机器人的步骤

长话短说&#xff1a; 准备好深入研究&#xff1a; 矢量存储的复杂性以及如何利用 Qdrant 进行高效数据摄取。掌握 Qdrant 中的集合管理以获得最佳性能。释放上下文感知响应的相似性搜索的潜力。精心设计复杂的 LangChain 工作流程以增强聊天机器人的功能。将革命性的 Llama …

【LeetCode HOT 100】详细题解之二叉树篇

【LeetCode HOT 100】详细题解之二叉树篇 94 二叉树的中序遍历方法一&#xff1a;递归方法二&#xff1a;迭代 104 二叉树的最大深度方法一&#xff1a;递归方法二&#xff1a;迭代 226 翻转二叉树方法一&#xff1a;递归方法二&#xff1a;迭代 101 对称二叉树方法一&#xff…

【数据结构】---图

图 前言 本篇作为图的基础概念篇&#xff0c; 了解图的离散数学定义&#xff0c; 图的分类&#xff0c; 图模型解决的问题&#xff08;图的应用&#xff09;&#xff0c; 图的相关算法&#xff08;仅仅介绍&#xff0c;具体不在此篇展开&#xff09;。 学习基本路线&#xff…

Azkaban:大数据任务调度与编排工具的安装与使用

在当今大数据时代&#xff0c;数据处理和分析任务变得越来越复杂。一个完整的大数据分析系统通常由大量任务单元组成&#xff0c;如 shell 脚本程序、mapreduce 程序、hive 脚本、spark 程序等。这些任务单元之间存在时间先后及前后依赖关系&#xff0c;为了高效地组织和执行这…

NAL 网络提取层(Network Abstraction Layer)

1.NAL全称Network Abstract Layer, 即网络抽象层。 在H.264/AVC视频编码标准中&#xff0c;无论是存储还是网络传输&#xff0c;H264 原始码流是由一个接一个 NALU&#xff08;NAL Unit&#xff09; 组成&#xff0c;整个系统框架被分为两个层面&#xff1a;视频编码层面&#…

Linux 实用工具Axel安装及使用教程(支持多线程下载)

一、Axel 简介 Axel 是一个轻量级的命令行下载加速器&#xff0c;旨在提高文件下载速度。 多线程下载: Axel 可以同时使用多个连接来下载文件&#xff0c;从而加快下载速度。断点续传: 支持中断后继续下载&#xff0c;避免重新开始下载整个文件。轻量级: 资源占用少&#xff0c…

YOLOv1–v11: 版本演进及其关键技术解析

最新消息&#xff1a;Yolo11发布最新版本2024.10 YOLO (You Only Look Once) 是一种高效的目标检测算法&#xff0c;自其初代发布以来&#xff0c;经过多次迭代&#xff0c;逐步提升了检测速度和精度。本文将详细介绍 YOLO 从 v1 到 v11 的各个版本&#xff0c;涵盖每个版本的发…

谨防火灾!电瓶车检测算法助力城市/小区/园区多场景安全管理精细化、智能化

随着人工智能技术的快速发展&#xff0c;AI智能分析网关V4在电瓶车检测领域的应用日益广泛。这一技术通过深度学习、计算机视觉等先进算法&#xff0c;实现了对电瓶车及其相关行为的智能识别和分析&#xff0c;为电瓶车的管理和应用提供了强大的技术支持。 一、电瓶车检测算法…

测试用例的进阶二

1. 按开发阶段划分 1.1 测试金字塔 从上到下&#xff0c;对于测试人员代码就是要求越来越低&#xff1b; 从下到上&#xff0c;越来越靠近用户&#xff1b; 从下到上&#xff0c;定位问题的成本越来越高&#xff1b; 1.2 单元测试(Unit Testing) 单元测试是对软件组成单元进…

1.8 物理层下的传输媒体

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 1 导引型传输媒体1.1 双绞线1.2 同轴电缆1.3 光缆 2 非导引型传输媒体2.1 无线电微波通信2.2 多径效应2.3 卫星通信2.4 无线局域网 在数据通信系统中&#xff0c;传输媒体是发…

在线PDF怎么转换成JPG图片?分享14种转换操作!

作为一名社畜&#xff0c;俺也经常要将PDF转换为图片格式&#xff01; 如何进行快速转换&#xff0c;包括电脑端、在线端和手机端&#xff0c;今天俺就测评了50款工具&#xff0c;给你得出了下面这些渠道&#xff0c;不少也是免费的&#xff0c;相信对你有帮助哦&#xff01; …

25考研咨询周开启,西安电子科技大学是否改考408??

学长这几天帮大家问了西安电子科技大学是否会从833、834、953改考为408&#xff1f; 西电老师回复&#xff1a;根据上级文件要求&#xff0c;招生简章以及专业目录会在网上报名开始前公布&#xff0c;专业课不会又大变动&#xff01; 因为大家安心复习即可&#xff0c;保证今…

Python 基于 flask 的前程无忧招聘可视化系统,Python大数据招聘爬虫可视化分析

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

谷歌网站收录查询,帮助您快速准确地查询网站在谷歌的收录情况的3个方法

谷歌网站收录查询&#xff0c;帮助您快速准确地查询网站在谷歌的收录情况的3个方法。 一、使用GoogleSearchConsole&#xff08;谷歌搜索控制台&#xff09; 1.注册并验证网站 -首先&#xff0c;确保您的网站已注册并验证在GoogleSearchConsole中。这是一个免费的工具&…

2-109 基于matlab-GUI的BP神经网络

基于matlab-GUI的BP神经网络&#xff0c;10种不同分布的数据样本&#xff0c;9种不同的激活函数&#xff0c;可更改升级网络结构参数&#xff0c;对比各种方法参数下的训练测试效果&#xff0c;实时显示预测过程。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&…

C++ 游戏开发

C游戏开发 C 是一种高效、灵活且功能强大的编程语言&#xff0c;因其性能和控制能力而在游戏开发中被广泛应用。许多著名的游戏引擎&#xff0c;如 Unreal Engine、CryEngine 和 Godot 等&#xff0c;都依赖于 C 进行核心开发。本文将详细介绍 C 在游戏开发中的应用&#xff0…