SpringCloud源码:服务端分析(二)- EurekaServer分析
背景
从昨日的两篇文章: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注解是,如果有容器中有类,就注入备用类,如果没有类就不注入。
其他个性化加载配置
源码分析
@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);
下面我将按照流程图来讲解两条路线的源码
路线一:@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,即剔除任务
路线二:@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的注入&拦截应用
相关文章:
SpringCloud源码:服务端分析(二)- EurekaServer分析
背景 从昨日的两篇文章:SpringCloud源码:客户端分析(一)- SpringBootApplication注解类加载流程、SpringCloud源码:客户端分析(二)- 客户端源码分析。 我们理解了客户端的初始化,其实…...
插槽slot在vue中的使用
介绍 在 Vue.js 中,插槽(slot)是一种用于实现组件内容分发的功能。通过插槽,可以让父组件在使用子组件时自定义子组件内部的内容。插槽提供了一种灵活的方式来组合和复用组件。 项目中有很多地方需要调用一个组件,比…...
针对考研的C语言学习(定制化快速掌握重点2)
1.C语言中字符与字符串的比较方法 在C语言中,单字符可以用进行比较也可以用 > , < ,但是字符串却不能用直接比较,需要用strcmp函数。 strcmp 函数的原型定义在 <string.h> 头文件中,其定义如下: int strcmp(const …...
[C++][IO流][流输入输出][截断理解]详细讲解
目录 1.流输入输出说明1.<<执行顺序2.>>执行顺序 2.截断(trunc)理解 1.流输入输出说明 1.<<执行顺序 链式操作的顺序:当使用多个<<操作符进行链式插入时,执行顺序是从左到右的 每个<<操作都将数据插入到前一个流的输出中…...
阿里云部署1Panel(失败版)
官网脚本部署不成功 这个不怪1panel,这个是阿里Linux 拉不到docker的下载源,懒得思考 正常部署直接打开官网 https://1panel.cn/docs/installation/online_installation/ 但是我使用的阿里云os(Alibaba Cloud Linux 3.2104 LTS 64位) 我执行不管用啊装不上docker 很烦 curl -s…...
九、设备的分配与回收
1.设备分配时应考虑的因素 ①设备的固有属性 设备的固有属性可分为三种:独占设备、共享设备、虚拟设备。 独占设备 一个时段只能分配给一个进程(如打印机) 共享设备 可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享使用设备而微观上交替使用。 …...
单片机的原理及应用
单片机的原理及应用 1. 单片机的基本原理 1.1. 组成部分 单片机主要由以下几个部分组成: 中央处理器(CPU):执行指令并控制整个系统的操作。 存储器: 程序存储器(Flash):存储用户…...
Python数据分析篇--NumPy--入门
我什么也没忘,但是有些事只适合收藏。不能说,也不能想,却又不能忘。 -- 史铁生 《我与地坛》 NumPy相关知识 1. NumPy,全称是 Numerical Python,它是目前 Python 数值计算中最重要的基础模块。 2. NumPy 是针对多…...
OJ在线评测系统 后端 判题机模块预开发 架构分析 使用工厂模式搭建
判题机模块预开发(架构师)(工厂模式) 判题机模块 是为了把代码交个代码沙箱去处理 得到结果返回 代码沙箱 梳理判题模块和代码沙箱的关系 判题模块:调用代码沙箱 把代码和输入交给代码沙箱去执行 代码沙箱:只负责接受代码和输入 返回编译的结果 不负…...
linux 目录文件夹操作
目录 查看文件夹大小: Linux统计文件个数 2.统计文件夹中文件个数ls -l ./|grep "^-"|wc -l 4.统计文件夹下文件个数,包括子文件ls -lR | grep "^-"| wc -l 统计文件个数 移动绝对目录: 移动相对目录 test.py报错…...
(Linux驱动学习 - 4).Linux 下 DHT11 温湿度传感器驱动编写
DHT11的通信协议是单总线协议,可以用之前学习的pinctl和gpio子系统完成某IO引脚上数据的读与写。 一.在设备树下添加dht11的设备结点 1.流程图 2.设备树代码 (1).在设备树的 iomuxc结点下添加 pinctl_dht11 (2).在根…...
前端登录页面验证码
首先,在el-form-item里有两个div,各占一半,左边填验证码,右边生成验证码 <el-form-item prop"code"><div style"display: flex " prop"code"><el-input placeholder"请输入验证…...
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(上)
系列文章目录 【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上) 【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下) 【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(上) 文…...
使用 Nginx 和 Gunicorn 部署 Flask 项目详细教程
使用 Nginx 和 Gunicorn 部署 Flask 项目详细教程 在这篇文章中,我们将介绍如何使用 Nginx 和 Gunicorn 来部署一个 Flask 项目。这种部署方式非常适合在生产环境中使用,因为它能够提供更好的性能和更高的稳定性。 目录 Flask 项目简介环境准备Gunico…...
linux中bashrc和profile环境变量在Shell编程变量的传递作用
在 Linux 系统中,.bashrc文件和.profile文件都是用于配置用户环境的重要文件,它们之间有以下关联: 一、作用相似性 环境设置:两者都用于设置用户的环境变量和启动应用程序的配置。例如,它们可以定义路径变量…...
数据结构-4.2.串的定义和基本操作
一.串的定义: 1.单/双引号不是字符串里的内容,他只是一个边界符,用来表示字符串的头和尾; 2.空串也是字符串的子串,空串长度为0; 3.字符的编号是从1开始,不是0; 4.空格也是字符&a…...
fastzdp_redis第一次开发, 2024年9月26日, Python操作Redis零基础快速入门
提供完整录播课 安装 pip install fastzdp_redisPython连接Redis import redis# 建立链接 r redis.Redis(hostlocalhost, port6379, db0)# 设置key r.set(foo, bar)# 获取key的值 print(r.get(foo))RESP3 支持 简单的理解: 支持更丰富的数据类型 参考文档: https://blog.c…...
文件名:\\?\C:\Windows\system32\inetsrv\config\applicationHost.config错误:无法写入配置文件
文件名: \\?\C:\Windows\system32\inetsrv\config\applicationHost.config 错误:无法写入配置文件 解决办法: 到C:\inetpub\history中找到最近一次的【CFGHISTORY_00000000XX】文件,点击进去找到applicationHost.config文件,用其覆盖C:\Win…...
Optiver股票大赛Top2开源!
Optiver股票大赛Top2开源! ↑↑↑关注后"星标"kaggle竞赛宝典 作者:杰少 Optiver第二名方案解读 简介 Optiver竞赛已经于今天结束了,竞赛也出现了极端情况,中间断崖式的情况,在Kaggle过往的竞赛中&#…...
Maven 实现依赖统一管理
Maven 实现依赖统一管理主要是通过两个关键机制:pom.xml 文件中的 <dependencies> 节点用于声明项目依赖,以及通过继承(Inheritance)和聚合(Aggregation)功能来统一管理和组织这些依赖。此外…...
【最新】微信小程序连接onenet——stm32+esp8266+onenet实现查看温湿度,控制单片机
微信小程序——stm32esp8266onenet实现查看温湿度,控制单片机 (最新已验证)stm32 新版 onenet dht11esp8266/01s mqtt物联网上报温湿度和控制单片机(保姆级教程) :↓↓👇 👇 👇 👇…...
差分(续前缀和)(含一维二维)
题目引入 开发商小 Q 买下了一条街,他想在这条街的一边盖房子。 街道可以抽象为一条数轴,而小 Q 只会在坐标在 1~n 的范围内盖房子。 首先,小 Q 将街上坐标在 1∼ 𝑛1∼ n 范围内的物体全部铲平。也就是说,在正式动工盖…...
【STM32-HAL库】自发电型风速传感器(使用STM32F407ZGT6)(附带工程下载链接)
一、自发电型风速传感器介绍 自发电型风速传感器,也称为风力发电型风速传感器或无源风速传感器,是一种不需要外部电源即可工作的风速测量设备。这种传感器通常利用风力来驱动内部的发电机构,从而产生电能来供电测量风速的传感器部分。以下是自…...
【计算机毕业设计】springboot就业信息管理系统
就业信息管理系统 摘 要 随着信息化时代的到来,管理系统都趋向于智能化、系统化,就业信息管理系统也不例外,但目前国内仍都使用人工管理,市场规模越来越大,同时信息量也越来越庞大,人工管理显然已无法应对时…...
实用工具推荐---- PDF 转换
直接上链接:爱PDF |面向 PDF 爱好者的在线 PDF 工具 (ilovepdf.com) 主要功能如下: 全免费!!!!...
安宝特案例 | 某知名日系汽车制造厂,借助AR实现智慧化转型
案例介绍 在全球制造业加速数字化的背景下,工厂的生产管理与设备维护效率愈发重要。 某知名日系汽车制造厂当前面临着设备的实时监控、故障维护,以及跨地域的管理协作等挑战,由于场地分散和突发状况的不可预知性,传统方式已无法…...
RabbitMQ基本原理
一、基本结构 所有中间件技术都是基于 TCP/IP 协议基础之上进行构建新的协议规范,RabbitMQ遵循的是AMQP协议(Advanced Message Queuing Protocol - 高级消息队列协议)。 生产者发送消息流程: 1、生产者和Broker建立TCP连接&#…...
【NodeJS】npm、yarn、pnpm当前项目设置国内镜像源
全局设置镜像源,可以参考下这篇文章,还挺详细:《npm、yarn、pnpm 最新国内镜像源设置和常见问题解决》 临时设置镜像源:《npm永久或临时切换源》 有时候可能要同时多个开发项目,又不想修改全局的镜像源(具体场景…自行…...
25考研咨询周开启,西安电子科技大学是否改考408??
学长这几天帮大家问了西安电子科技大学是否会从833、834、953改考为408? 西电老师回复:根据上级文件要求,招生简章以及专业目录会在网上报名开始前公布,专业课不会又大变动! 因为大家安心复习即可,保证今…...
git(1) -- 环境配置
1. 配置文件 编辑~/.gitconfig文件,内容如下。 [user]email xflming163.comname xflm [core]editor vim [color]diff autostatus autobranch autoui true [commit]template /home/xflm/configuser/git-commit.template [diff]tool bc4 [difftool]prompt …...
wordpress导入error 500/成人大专
在有些情况下死锁是可以避免的。本文将展示三种用于避免死锁的技术: 加锁顺序加锁时限死锁检测加锁顺序 当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。 如果能确保所有的线程都是按照相同的顺序获得锁,那…...
360度全景网站的公司/友情链接发布网
转自:https://blog.csdn.net/longfei_2010/article/details/79785320 1、编辑(Editing) Ctrl Space 基本的代码完成(类、方法、属性) Ctrl Alt Space 快速导入任意类 Ctrl Shift Enter 语句完成 Ctrl P 参数信息…...
北京网站优化托管/引流推广网站
配置kotlin的环境变量,添加到path 下载kotlin的编译器工具 下载后解压放到固定目录下,找到bin文件夹添加到path,配置完成后使用 路径如:D:\kotlinc\bin 命令: kotlinc -version 查看是否安装成功 安装插件 安装Kot…...
门户网站样式/seo任务平台
1.题目描述: 2.算法分析: 首先肯定是定义一个double类型数组存放数据,然后的一个问题是怎么判断浮点数最近的整数的差, 使用round函数即可 floor : 不大于自变量的最大整数 ceil :不小于自变量的最大整数 round:四舍五入到最邻近…...
网站登录界面模板/夸克搜索
参考网址1: iOS消息推送机制的实现 http://www.cnblogs.com/qq78292959/archive/2012/07/16/2593651.html 参考网址2: iOS 推送的服务端实现 http://www.dozer.cc/2013/03/push-notifications-server-side-implement/ 参考网址3: iOS 证书与推…...
医药企业网站建设/秒收录关键词代发
C语言中内存的管理主要是依据malloc和free实现的,其中malloc主要是实现内存的分配,而free则是实现内存的释放。虽然这是我们已经很熟悉的,但是还是存在一些问题。特别是当结构体中存在指针的情况下,各种问题也就会展现出来。 其中…...