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 {
   @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的注入&拦截应用

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python画笔案例-071 绘制闪闪的红星

1、绘制通闪闪的红星 通过 python 的turtle 库绘制 闪闪的红星,如下图: 2、实现代码 绘制闪闪的红星,以下为实现代码: """闪闪的红星.py """ import time import turtledef xsleep(n):"""防

通信工程学习:什么是MAC媒体接入控制

MAC&#xff1a;媒体接入控制 MAC&#xff08;Medium Access Control&#xff09;&#xff0c;即媒体接入控制&#xff0c;是计算机网络中数据链路层的一个重要组成部分&#xff0c;负责协调多个发送和接收站点对一个共享传输媒体的占用。以下是关于MAC的详细解释&#xff1a; …

系统架构设计师-知识产权与标准化

目录 一、保护范围与对象 二、保护期限 三、知识产权人确定 四、侵权判断 五、标准化 一、保护范围与对象 知识产权是权利人依法就下列课题享有的专有权利&#xff1a; &#xff08;一&#xff09;作品&#xff08;著作&#xff09; &#xff08;二&#xff09;发明、实用…

泰勒图 ——基于相关性与标准差的多模型评价指标可视化比较-XGBoost、sklearn

1、基于相关性与标准差的多模型评价指标可视化比较 # 数据读取并分割 import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split plt.rcParams[font.family] = Times New Roman plt.rcParams[axes.unic…

针对考研的C语言学习(2019链表大题)

题目解析&#xff1a; 【考】双指针算法&#xff0c;逆置法&#xff0c;归并法。 解析&#xff1a;因为题目要求空间复杂度为O(1)&#xff0c;即不能再开辟一条链表&#xff0c;因此我们只能用变量来整体挪动原链表。 第一步先找出中间节点 typedef NODE* Node; Node find_m…

Linux-基础篇-磁盘分区,挂载

Linux 分区 原理介绍 Linux 来说无论有几个分区&#xff0c;分给哪一目录使用&#xff0c;它归根结底就只有一个根目录&#xff0c;一个独立且唯一的文件结构 , Linux 中每个分区都是用来组成整个文件系统的一部分。 Linux 采用了一种叫 “ 载入 ” 的处理方法&#xff0c;…

为什么有必要由母语人士翻译应用程序界面

在当今技术已成为我们生活不可或缺的一部分的世界中&#xff0c;移动应用接口在我们与数字空间的互动中发挥着关键作用。然而&#xff0c;无论应用程序本身多么完美&#xff0c;它的有效性可能会因糟糕地翻译而大大降低。这就是为什么&#xff0c;为了翻译应用程序界面&#xf…

在线css像素px到Em的转换器

具体请前往&#xff1a;在线Px转Em工具--将绝对像素(px)长度单位转换为相对长度em

Android SystemUI组件(09)唤醒亮屏 锁屏处理流程

该系列文章总纲链接&#xff1a;专题分纲目录 Android SystemUI组件 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节持续迭代之前章节的思维导图&#xff0c;主要关注左侧上方锁屏分析部分 唤醒亮屏 即可。 Power按键的处理逻辑最终是由PhoneWindowManager来…

VMware ESXi Centos7网卡名称 ens192 变更eth0

1.在 /etc/sysconfig/network-scirpts/ 文件夹下 创建一个ifcfg-eth0的文件&#xff0c; 最简单的方式是 mv ifcfg-ens192 ifcfg-eth0 然后 vi ifcfg-eth0 把DEVICE改成 DEVICEeth0 wq! 保存 2. vi /etc/sysconfig/grub # 在位置添加 net.ifnames0 biosdevname0 参数 完…

了解芯片光刻与OPC

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 参考资料&#xff1a; 光刻技术与基本流程 https://www.bilibili.com/video/BV1tP4y1j7BA OPC https://www.bilibili.com/video/BV1o94y1U7Td 论文&#xff1a;计算…

macOS安装Redis教程, 通过brew命令, 时间是2024年9月26日, redis版本是0.7.2

搜索: brew search redis安装Redis: brew install redis关于启动命令的提示: To start redis now and restart at login:brew services start redis Or, if you dont want/need a background service you can just run:/opt/homebrew/opt/redis/bin/redis-server /opt/home…

Python数据分析篇--NumPy--进阶

人有一种天生的、难以遏制的欲望&#xff0c;那就是在理解之前就评判。 -- 米兰昆德拉 多维数组 1. 一维数组只有行&#xff0c;二维数组相比一维数组多了列这个维度&#xff0c;而三维数组则类似多个二维数组堆叠在一起&#xff0c;形如一个立方体。 二维数组的创建 1. 二…

.scl文件导入

.SCL的文件怎么导入博图-SIMATICS7-1200系列-找答案-西门子中国 从源生成块

MongoDB微服务部署

一、安装MongoDB 1.在linux中拉去MongoDB镜像文件 docker pull mongo:4.4.18 2. 2.创建数据挂载目录 linux命令创建 命令创建目录: mkdir -p /usr/local/docker/mongodb/data 可以在sshclient工具查看是否创建成功。 进入moogodb目录&#xff0c;给data赋予权限777 cd …

IT新秀系列:Erlang语言的兴起原因分析和前景观望

Erlang语言的兴起原因 Erlang 是一种通用并发编程语言和运行环境&#xff0c;最早由瑞典电信公司爱立信&#xff08;Ericsson&#xff09;在1986年开发&#xff0c;旨在处理高度并发、分布式和容错系统。Erlang 的主要设计目标是创建一个能够在电信系统中实现高可用性和实时性能…

Linux:LCD驱动开发

目录 1.不同接口的LCD硬件操作原理 应用工程师眼中看到的LCD 1.1像素的颜色怎么表示 ​编辑 1.2怎么把颜色发给LCD 驱动工程师眼中看到的LCD 统一的LCD硬件模型 8080接口 TFTRGB接口 什么是MIPI Framebuffer驱动程序框架 怎么编写Framebuffer驱动框架 硬件LCD时序分析…

【经典机器学习算法】谱聚类算法及其实现(python)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;深度学习_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. 前…

图文深入理解Oracle Network配置管理(一)

List item 本篇图文深入介绍Oracle Network配置管理。 Oracle Network概述 Oracle Net 服务 Oracle Net 监听程序 <oracle_home>/network/admin/listener.ora <oracle_home>/network/admin/sqlnet.ora建立网络连接 要建立客户机或中间层连接&#xff0c;Oracle…

【C++】C++基础

目录 一. C关键字(C98) 二、C的第一个程序 三、命名空间 3.1.namespace的价值 3.2.namespace的定义 3.2.命名空间使用 总结&#xff1a;在项目当中第一、第二种方法搭配使用&#xff0c;第三种冲突风险非常大&#xff0c;仅适合练习使用。 四、C输入&输出 五、缺省…