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

news2024/10/2 5:58:45

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 {
   @Bean
   public 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 EurekaServerInitializerConfiguration
      implements ServletContextAware, SmartLifecycle, Ordered {


    @Override
    public 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 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");


   // Copy registry from neighboring eureka node
   int 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 node
    int 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);


    @Override
    public 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
    @ConditionalOnMissingBean
    public 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() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                    thread.setDaemon(true);
                    return thread;
                }
            }
    );
    try {
        updatePeerEurekaNodes(resolvePeerUrls());
        // 【2】初始化任务
        Runnable peersUpdateTask = new Runnable() {
            @Override
            public 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>() {
                        @Override
                        public 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>() {
                        @Override
                        public 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() {
        @Override
        public 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() {
                       @Override
                       public void run() {
                           updateRenewalThreshold();
                       }
                   }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
            serverConfig.getRenewalThresholdUpdateIntervalMs());
}


private void updateRenewalThreshold() {
    try {
        Applications apps = eurekaClient.getApplications();
        int count = 0;
        for (Application app : apps.getRegisteredApplications()) {
            for (InstanceInfo instance : app.getInstances()) {
                if (this.isRegisterable(instance)) {
                    ++count;
                }
            }
        }
        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.coloradmin.cn/o/2183885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

车辆种类分类识别数据集,可以识别7种汽车类型,已经按照7:2:1比 例划分数据集,训练集1488张、验证集507张,测试集31张, 共计2026张。

车 车辆种类分类识别数据集&#xff0c;可以识别7种汽车类型&#xff0c;已经按照7:2:1比 例划分数据集&#xff0c;训练集1488张、验证集507张,测试集31张&#xff0c; 共计2026张。 数据集分为一类客车(tinycar) &#xff0c;类客车(midcar) &#xff0c;三类 客车(bigcar) ,…

数据库重建索引的作用?

重建索引是数据库管理中的一个重要操作&#xff0c;主要用于优化数据库性能和提高查询效率。以下是重建索引的一些主要用途&#xff1a; 提高查询性能&#xff1a;随着时间的推移&#xff0c;数据的插入、更新和删除会导致索引碎片化&#xff0c;重建索引可以减少碎片&#xf…

C语言_内存函数

内存函数是 C 标准库中的一组函数&#xff0c;用于管理和操作内存。使用时需要包含头文件<string.h>。 1. memcpy的使用和模拟实现 函数形式如下&#xff1a; void* memcpy(void* destination, const void* source, size_tnum);函数解析和注意事项&#xff1a; memcp…

【有啥问啥】SimAM(Similarity-Aware Activation Module)注意力机制详解

SimAM&#xff08;Similarity-Aware Activation Module&#xff09;注意力机制详解 引言 在计算机视觉领域&#xff0c;注意力机制通过引导模型关注图像中的关键区域&#xff0c;显著提升了模型处理和理解图像的能力。SimAM&#xff08;Similarity-Aware Activation Module&a…

【网络安全 | 渗透工具】自动化 .env/.git文件检测

原创文章,禁止转载。 文章目录 1. 安装 DotGit2. 配置 DotGit3. 使用 DotGit 检测 .env / .git 文件1. 安装 DotGit 在谷歌应用商店中搜索 DotGit 并进行安装: 2. 配置 DotGit 安装完成后,可以在设置中开启或关闭相关功能: 3. 使用 DotGit 检测 .env / .git 文件 接下来…

音悦 1.5.1 完全免费,无广告,纯净听歌体验

音悦是一款完全免费的听歌应用&#xff0c;汇聚全网多平台曲库&#xff0c;拥有排行榜、MV、个性电台、我的歌单、收藏喜欢等功能。无需会员&#xff0c;没有广告&#xff0c;免费听歌下歌&#xff0c;是一款非常纯净小巧但功能齐全的听歌神器。 大小&#xff1a;27.6M 百度网…

【Linux 24】网络基础概念

文章目录 &#x1f308; 一、计算机网络的发展⭐ 1. 独立模式⭐ 2. 网络互联⭐ 3. 局域网 LAN⭐ 4. 广域网 WAN &#x1f308; 二、计算机网络的协议⭐ 1. 协议的概念⭐ 2. 协议分层⭐ 3. OSI 七层参考模型⭐ 4. TCP / IP 五层模型 &#x1f308; 三、网络传输基本流程⭐ 1. 同…

【计算机毕业设计】springboot企业客户信息反馈平台

摘 要 网络的广泛应用给生活带来了十分的便利。所以把企业客户信息反馈管理与现在网络相结合&#xff0c;利用java技术建设企业客户信息反馈平台&#xff0c;实现企业客户信息反馈的信息化。则对于进一步提高企业客户信息反馈管理发展&#xff0c;丰富企业客户信息反馈管理经验…

软考-高级系统分析师知识点合集记录

一、计算机基础知识 存储系统 计算机指令&#xff1a; 复杂指令&#xff0c;精简指令 指令的流水线周期计算方式 计算机系统体系结构&#xff1a; flynn方法 根据指令流和数据流的并行程度对计算机体系结构进行分类&#xff1a; 冯.诺依曼结构 &#xff0c;哈佛结构 根据指…

获取和解析JWT令牌

一、JWT令牌介绍 JWT&#xff08;JSON Web Tokens&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于在各方之间以JSON对象的形式安全地传输信息。每个JWT令牌由三部分组成&#xff1a;Header&#xff0…

U盘恢复数据工具全解析:助力找回丢失信息

不论是工作还是学习都需要一些电子设备来存储短期数据吧&#xff0c;优盘已经成为我们存储重要数据的常用工具之一。然而&#xff0c;由于各种原因&#xff0c;我们可能会遭遇优盘数据丢失的困扰。接下来&#xff0c;让我们一起深入了解u盘恢复数据软件的强大功能。 1.福晰恢复…

算法竞赛:Online Judge 工作原理 常见报错分析 如何判断算法是否超时 知己知彼 方能骗分

一、在线评测系统运行原理及各种报错常见原因 电脑只是处理数据验证最后答案与期望答案是否相同&#xff0c;大部分的思考都由人来完成。 OJ一般有一组或者多组input文件与output文件&#xff0c;比如1.in 1.out 2.in 2.out等 OJ先运行你的程序&#xff0c;同时在后台开始计时&…

废物利用,三百块电脑如何升级并安装双系统便携使用

文章目录 引言最初的配置开始改装更换内存升级硬盘2.5 英寸 sata 固态msata 加装 升级电池其他的升级娱乐大师跑分 双系统安装前提条件设置 Bios安装 win 10安装 Manjaro时间同步问题 屏幕问题黑屏难开 引言 最近浏览 b 站的二手笔记本信息&#xff0c;想要整个二手笔记本玩玩…

构建现代化社区医疗服务:SpringBoot平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理社区医院信息平台的相关信息成为必然。开发…

链表的实现(go语言)

摘要&#xff1a; 本文记录使用go语言对链表的实现。 链表的实现 节点结构 type Node struct {Data intNext *Node }新建节点 func NewNode(Data int) *Node {return &Node{Data: Data,Next: nil,} }尾部添加节点 // 尾部插入 func Append(head *Node, Data int) *Node…

信息安全工程师(28)机房安全分析与防护

前言 机房安全分析与防护是一个复杂而细致的过程&#xff0c;涉及到物理安全、环境控制、电力供应、数据安全、设备管理、人员管理以及紧急预案等多个方面。 一、机房安全分析 1. 物理安全威胁 非法入侵&#xff1a;未经授权的人员可能通过门窗、通风口等进入机房&#xff0c;…

【Java数据结构】 ArrayList 顺序表

一、什么是List 在集合框架中&#xff0c;List是一个接口&#xff0c;继承自Collection Collection也是一个接口&#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下所示&#xff1a; Iterable 也是一个接口&#xff0c;表示实现该接口的类是可以逐个元素…

Unity Asset Store的默认下载位置及更改下载路径的方法

修改Unity Asset Store的默认下载路径 Unity Asset Store默认下载位置 Unity Asset Store里下载资源&#xff0c;默认是下载到C盘里的&#xff0c;如果你不想做C盘战士的话&#xff0c;记得将下载的资源转移到其他盘。 Unity商城默认下载路径是C:\用户\用户名&#xff08;一般…

电脑扬声器无法识别,没有声音处理

原因感觉就是前几天安装Realtek Audio Control驱动的时候&#xff0c;没有关闭360&#xff0c;导致的问题。 音量那里一直是 解决方法 开始菜单搜索“设备管理器” 在“声音、视频和游戏控制器”找到识别错的设备 右键“卸载设备” 右键扫描一下设备 此时一般就好了 …

数据结构-LRU缓存(C语言实现)

遇到困难&#xff0c;不必慌张&#xff0c;正是成长的时候&#xff0c;耐心一点&#xff01; 目录 前言一、题目介绍二、实现过程2.1 实现原理2.2 实现思路2.2.1 双向链表2.2.2 散列表 2.3 代码实现2.3.1 结构定义2.3.2 双向链表操作实现2.3.3 实现散列表的操作2.3.4 内存释放代…