微服務組件--註冊中心Spring Cloud Eureka分析

来源:https://www.cnblogs.com/chafry/archive/2022/10/22/16810064.html
-Advertisement-
Play Games

Spring Cloud Eureka源碼分析的筆記,針對多個方法都有記錄,主要是方便自己對源碼部分的追溯,包括裡面的亮點設計的總結與源碼分析 ...


Eureka核心功能點

【1】服務註冊(register):Eureka Client會通過發送REST請求的方式向Eureka Server註冊自己的服務,提供自身的元數據,比如ip地址、埠、運行狀況指標的url、主頁地址等信息。Eureka Server接收到註冊請求後,就會把這些元數據信息存儲在一個雙層的Map中。

【2】服務續約(renew):在服務註冊後,Eureka Client會維護一個心跳來持續通知Eureka Server,說明服務一直處於可用狀態,防止被剔除。Eureka Client在預設的情況下會每隔30秒(eureka.instance.leaseRenewalIntervalInSeconds)發送一次心跳來進行服務續約。

【3】服務同步(replicate):Eureka Server之間會互相進行註冊,構建Eureka Server集群,不同Eureka Server之間會進行服務同步,用來保證服務信息的一致性。

【4】獲取服務(get registry):服務消費者(Eureka Client)在啟動的時候,會發送一個REST請求給Eureka Server,獲取上面註冊的服務清單,並且緩存在Eureka Client本地,預設緩存30秒(eureka.client.registryFetchIntervalSeconds)。同時,為了性能考慮,EurekaServer也會維護一份只讀的服務清單緩存,該緩存每隔30秒更新一次。

【5】服務調用:服務消費者在獲取到服務清單後,就可以根據清單中的服務列表信息,查找到其他服務的地址,從而進行遠程調用。Eureka有Region和Zone的概念,一個Region可以包含多個Zone,在進行服務調用時,優先訪問處於同一個Zone中的服務提供者。

【6】服務下線(cancel):當Eureka Client需要關閉或重啟時,就不希望在這個時間段內再有請求進來,所以,就需要提前先發送REST請求給Eureka Server,告訴Eureka Server自己要下線了,Eureka Server在收到請求後,就會把該服務狀態置為下線(DOWN),並把該下線事件傳播出去。

【7】服務剔除(evict):有時候,服務實例可能會因為網路故障等原因導致不能提供服務,而此時該實例也沒有發送請求給Eureka Server來進行服務下線,所以,還需要有服務剔除的機制。Eureka Server在啟動的時候會創建一個定時任務,每隔一段時間(預設60秒),從當前服務清單中把超時沒有續約(預設90秒,eureka.instance.leaseExpirationDurationInSeconds)的服務剔除。180s被剔除

【8】自我保護:既然Eureka Server會定時剔除超時沒有續約的服務,那就有可能出現一種場景,網路一段時間內發生了異常,所有的服務都沒能夠進行續約,Eureka Server就把所有的服務都剔除了,這樣顯然不太合理。所以,就有了自我保護機制,當短時間內,統計續約失敗的比例,如果達到一定閾值,則會觸發自我保護的機制,在該機制下,Eureka Server不會剔除任何的微服務,等到正常後,再退出自我保護機制。自我保護開關(eureka.server.enable-self-preservation: false)

 

常見的問題

  【1】當eureka服務實例有註冊或下線或有實例發生故障,記憶體註冊表雖然會及時更新數據,但是客戶端不一定能及時感知到,可能會過30秒才能感知到,因為客戶端拉取註冊表實例這裡面有一個多級緩存機制。【實現的是最終一致性

  【2】還有服務剔除的不是預設90秒沒心跳的實例,剔除的是180秒沒心跳的實例(eureka的bug導致,註解有說明是因為加了兩次過期時間,但是很小的BUG所有不修複了【在Lease結構里說明】)

  【3】分析eureka服務下線的情況

    1)圖示

      

 

    2)說明

1.客戶端每個30s會發送心跳到服務端
2.ReadOnlyCacheMap和ReadWriteCacheMap每30s同步一次
3.客戶端每隔30s同步一次ReadOnlyCacheMap
4.ribbon緩存每隔30s同步一次【有負載均衡的情況】
所以正常下線需要120s
而非正常下線,外加上服務剔除的180s+60s的定時任務,也就是360s【6min】

如果出現時間太長容易出現問題
1.修改 ribbon 同步緩存的時間為 3 秒:ribbon.ServerListRefreshInterval = 3000
2.修改客戶端同步緩存時間為 3 秒 :eureka.client.registry-fetch-interval-seconds = 3
3.心跳間隔時間修改為 3 秒:eureka.instance.lease-renewal-interval-in-seconds = 3
4.超時剔除的時間改為 9 秒:eureka.instance.lease-expiration-duration-in-seconds = 9
5.清理線程定時時間改為 5 秒執行一次:eureka.server.eviction-interval-timer-in-ms = 5000
6.同步到只讀緩存的時間修改為 3 秒一次:eureka.server.response-cache-update-interval-ms = 3000
只讀緩存其實是可以關閉的,通過修改參數eureka.server.use-read-only-response-cache = false可以做到
正常下線就是 3+3+3+3=12 秒,非正常下線再加 18+5 秒為 35 秒。
因為本質上服務剔除的是超時過期的,而lease可知過期時間實際上是兩倍,也就是18s。考慮極端情況,18s剛好卡在定時任務的最後一刻,則是直接加上5s。
此外,這裡的極端情況,也就是從某一次心跳之後開始不正常的。

 

 

源碼精髓總結

【1】服務端多級緩存設計思想

  1)在拉取註冊表的時候:

    (1)首先從ReadOnlyCacheMap里查緩存的註冊表。

    (2)若沒有,就找ReadWriteCacheMap里緩存的註冊表。

    (3)如果還沒有,就從記憶體中獲取實際的註冊表數據。

  2)在註冊表發生變更的時候:

    (1)會在記憶體中更新變更的註冊表數據,同時過期掉ReadWriteCacheMap。

    (2)此過程不會影響ReadOnlyCacheMap提供人家查詢註冊表。

    (3)預設每30秒Eureka Server會將ReadWriteCacheMap更新到ReadOnlyCacheMap里

    (4)預設每180秒Eureka Server會將ReadWriteCacheMap里是數據失效

    (5)下次有服務拉取註冊表,又會從記憶體中獲取最新的數據了,同時填充 各級緩存

  3)多級緩存機制的優點:

    (1)儘可能保證了記憶體註冊表數據不會出現頻繁的讀寫衝突問題

    (2)並且進一步保證對Eureka Server的大量請求,都是快速從純記憶體走,性能極高(可以稍微估計下對於一線互聯網公司,內部上千個eureka client實例,每分鐘對eureka大幾千次的訪問,一天就是上千萬次的訪問)

 

【2】TimedSupervisorTask定時任務的設計

  1)從整體上看,TimedSupervisorTask是固定間隔的周期性任務,一旦遇到超時就會將下一個周期的間隔時間調大,如果連續超時,那麼每次間隔時間都會增大一倍,一直到達外部參數設定的上限為止,一旦新任務不再超時,間隔時間又會自動恢復為初始值,另外還有CAS來控制多線程同步。

 

【3】增量更新中哈希碼檢驗的設計

//裡面的一致性哈希碼,本質上就是校驗數據
//如:伺服器上全量塊存的是【ABCDEFG】,此時它的哈希碼便是全量塊存的數據的哈希值,增量塊存的是【FG】,
//而我們客戶端是【ABCD】,增量拉下來再合併,則為【ABCDFG】,得到的哈希值便會與全量哈希值不一致,代表了缺失一部分數據
//故檢驗不對就會全量拉取

 

【4】註冊表的結構說明(這個僅是記錄):

實例信息存放的map,這是個兩層的ConcurrentHashMap<String, Map<String,Lease<InstanceInfo>>>,外層map的key是appName,也就是服務名,內層map的key是instanceId,也就是實例名
註冊表map數據示例如下:
{
    MICROSERVICE - PROVIDER - USER = {
        DESKTOP - 1 SLJLB7: microservice - provider - user: 8002 = com.netflix.eureka.lease.Lease @2cd36af6,
        DESKTOP - 1 SLJLB7: microservice - provider - user: 8001 = com.netflix.eureka.lease.Lease @600b7073
    },
    MICROSERVICE - PROVIDER - ORDER = {
        DESKTOP - 1 SLJLB7: microservice - provider - order: 8002 = com.netflix.eureka.lease.Lease @2cd36af6,
        DESKTOP - 1 SLJLB7: microservice - provider - order: 8001 = com.netflix.eureka.lease.Lease @600b7073
    }
}

 

Eureka服務端源碼分析

【1】分析註解@EnableEurekaServer是如何開啟eurekaServer服務註冊功能

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

//註釋有說:這個註解是為了激活Eureka相關的配置類EurekaServerAutoConfiguration類
//但是卻是導入了EurekaServerMarkerConfiguration類

【2】分析導入的EurekaServerMarkerConfiguration類

//註釋說明:採用Marker的bean去激活EurekaServerAutoConfiguration類
//但實際上並沒有做什麼,直接去EurekaServerAutoConfiguration類看他是怎麼處理的
@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {
    @Bean
    public Marker eurekaServerMarkerBean() {
        return new Marker();
    }

    class Marker {}
}

【3】分析EurekaServerAutoConfiguration類

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
//當發現了這裡,便明白了,這個配置類要生效是必須要有Marker類的存在
//而且EurekaServerAutoConfiguration類本身是基於SpringBoot的SPI機制,自動導入的
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {...}

【4】分析EurekaServerAutoConfiguration類中的方法

//初始化集群節點集合
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs, ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
    return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
            this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
            replicationClientAdditionalFilters);
}

//初始化EurekaServer的相關配置
@Configuration(proxyBeanMethods = false)
protected static class EurekaServerConfigBeanConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
        EurekaServerConfigBean server = new EurekaServerConfigBean();
        if (clientConfig.shouldRegisterWithEureka()) {
            // Set a sensible default if we are supposed to replicate
            server.setRegistrySyncRetries(5);
        }
        return server;
    }
}

//初始化一些介面,用於獲取EurekaServer的信息
@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
    return new EurekaController(this.applicationInfoManager);
}

//基於EurekaServer的配置,註冊表,集群節點集合,以及服務實例初始化EurekaServer上下文
@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
    return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager);
}

//初始化經過包裝的Eureka原生啟動類
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, EurekaServerContext serverContext) {
    return new EurekaServerBootstrap(this.applicationInfoManager,
            this.eurekaClientConfig, this.eurekaServerConfig, registry,
            serverContext);
}

//初始化集群註冊表
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
    this.eurekaClient.getApplications(); // force initialization
    return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
            serverCodecs, this.eurekaClient,
            this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
            this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}

【5】分析EurekaServerAutoConfiguration類導入的EurekaServerInitializerConfiguration

//因為實現了SmartLifecycle介面,會在初始化完成後根據isAutoStartup()的返回值確認是否調用start()方法
//故查看EurekaServerInitializerConfiguration類#start()方法
@Override
public void start() {
    new Thread(() -> {
        try {
            //初始化EurekaServer,同時啟動Eureka Server
            eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
            log.info("Started Eureka Server");
            //發送Eureka註冊事件
            publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
            // 設置啟動的狀態為true
            EurekaServerInitializerConfiguration.this.running = true;
            // 發送Eureka Start事件,其他還有各種事件,我們可以監聽這種時間,然後做一些特定的業務需求
            publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
        }
        catch (Exception ex) {...}
    }).start();
}

//初始化EurekaServer的運行環境和上下文
//EurekaServerBootstrap類#contextInitialized方法
public void contextInitialized(ServletContext context) {
    try {
        //初始化運行環境
        initEurekaEnvironment();
        //初始化上下文
        initEurekaServerContext();

        context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
    }
    catch (Throwable e) {
        throw new RuntimeException(...);
    }
}

【6】分析初始化上下文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();
    }
    //初始化eureka server上下文
    EurekaServerContextHolder.initialize(this.serverContext);

    log.info("Initialized server context");

    // Copy registry from neighboring eureka node
    // 從相鄰的eureka節點複製註冊表
    int registryCount = this.registry.syncUp();
    // 預設每30秒發送心跳,1分鐘就是2次
    // 修改eureka狀態為up
    // 同時,這裡面會開啟一個定時任務,用於清理60秒沒有心跳的客戶端。自動下線
    // 根據屬性值可知是PeerAwareInstanceRegistry類
    this.registry.openForTraffic(this.applicationInfoManager, registryCount);

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

//返回了一個EurekaServerContextHolder【其實就是將serverContext設置進入到裡面當做屬性值】
public static synchronized void initialize(EurekaServerContext serverContext) {
    holder = new EurekaServerContextHolder(serverContext);
}

【7】服務同步的邏輯

//進行服務同步
@Override
public int syncUp() {
    // Copy entire entry from neighboring DS node
    int count = 0;
    //從配置文件中拿到註冊的節點
    for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
        if (i > 0) {
            try {
                Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
            } catch (InterruptedException e) { break; }
        }
        //調用節點的http請求獲取所有的服務實例
        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) {...}
            }
        }
    }
    return count;
}

【8】服務剔除的邏輯

//進行服務剔除
@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();
    
    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()) {
        primeAwsReplicas(applicationInfoManager);
    }
    logger.info("Changing status to UP");
    // 設置實例的狀態為UP
    applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
    // 開啟定時任務,預設60秒執行一次,用於清理60秒之內沒有續約的實例
    super.postInit();
}

protected void updateRenewsPerMinThreshold() {
    this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
            * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
            * serverConfig.getRenewalPercentThreshold());
}

protected void postInit() {
    renewsLastMin.start();
    if (evictionTaskRef.get() != null) {
        evictionTaskRef.get().cancel();
    }
    evictionTaskRef.set(new EvictionTask());
    //服務剔除任務
    //evictionIntervalTimerInMs = 60 * 1000,即每60s執行一次,且延遲60s
    evictionTimer.schedule(evictionTaskRef.get(),
            serverConfig.getEvictionIntervalTimerInMs(),
            serverConfig.getEvictionIntervalTimerInMs());
}
//EvictionTask類#run方法
@Override
public void run() {
    try {
        long compensationTimeMs = getCompensationTimeMs();
        logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
        evict(compensationTimeMs);
    } catch (Throwable e) {...}
}

//剔除邏輯
public void evict(long additionalLeaseMs) {
    logger.debug("Running the evict task");

    if (!isLeaseExpirationEnabled()) {
        logger.debug("DS: lease expiration is currently disabled.");
        return;
    }

    // We collect first all expired items, to evict them in random order. For large eviction sets,
    // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
    // the impact should be evenly distributed across all applications.
    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);
                }
            }
        }
    }

    // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
    // triggering self-preservation. Without that we would wipe out full registry.
    int registrySize = (int) getLocalRegistrySize();
    int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
    int evictionLimit = registrySize - registrySizeThreshold;

    int toEvict = Math.min(expiredLeases.size(), evictionLimit);
    if (toEvict > 0) {
        logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);

        Random random = new Random(System.currentTimeMillis());
        for (int i = 0; i < toEvict; i++) {
            // Pick a random item (Knuth shuffle algorithm)
            int next = i + random.nextInt(expiredLeases.size() - i);
            Collections.swap(expiredLeases, i, next);
            Lease<InstanceInfo> lease = expiredLeases.get(i);

            String appName = lease.getHolder().getAppName();
            String id = lease.getHolder().getId();
            EXPIRED.increment();
            logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
            internalCancel(appName, id, false);
        }
    }
}

 

Eureka客戶端源碼分析

【1】根據SpringBoot自動裝配先找出所有會調用的類

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration

【2】找到對應的自動裝配類EurekaClientAutoConfiguration類

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
        CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
        "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
        "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
        "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
    //初始化EurekaClient的相關配置
    @Bean
    @ConditionalOnMissingBean(value = EurekaClientConfig.class,
            search = SearchStrategy.CURRENT)
    public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
        EurekaClientConfigBean client = new EurekaClientConfigBean();
        if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
            // We don't register during bootstrap by default, but there will be another
            // chance later.
            client.setRegisterWithEureka(false);
        }
        return client;
    }

    //Client啟動時的自動註冊Bean
    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
    public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(
            ApplicationContext context, EurekaServiceRegistry registry,
            EurekaRegistration registration) {
        return new EurekaAutoServiceRegistration(context, registry, registration);
    }

    //EurekaClient配置類
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingRefreshScope
    protected static class EurekaClientConfiguration {

        @Autowired
        private ApplicationContext context;

        @Autowired
        private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;

        @Bean(destroyMethod = "shutdown")
        @ConditionalOnMissingBean(value = EurekaClient.class,search = SearchStrategy.CURRENT)
        public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {
            return new CloudEurekaClient(manager, config, this.optionalArgs, this.context);
        }

        @Bean
        @ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
        public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
            InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
            return new ApplicationInfoManager(config, instanceInfo);
        }

        @Bean
        @ConditionalOnBean(AutoServiceRegistrationProperties.class)
        @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
        public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient, CloudEurekaInstanceConfig instanceConfig, ApplicationInfoManager applicationInfoManager, @Autowired( required = false) ObjectProvider<HealthCheckHandler> healthCheckHandler) {
            return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager)
                    .with(eurekaClient).with(healthCheckHandler).build();
        }

    }
....
}

【2.1】分析註解@AutoConfigureAfter導入的EurekaDiscoveryClientConfiguration類做了什麼

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
public class EurekaDiscoveryClientConfiguration {

    //基於EurekaClientAutoConfiguration的啟動標誌
    @Deprecated
    @Bean
    public Marker eurekaDiscoverClientMarker() {
        return new Marker();
    }

    //將EurekaClient包裝成EurekaDiscoveryClient
    @Bean
    @ConditionalOnMissingBean
    public EurekaDiscoveryClient discoveryClient(EurekaClient client,
            EurekaClientConfig clientConfig) {
        return new EurekaDiscoveryClient(client, clientConfig);
    }

    //心跳檢測的處理配置
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(value = "eureka.client.healthcheck.enabled",matchIfMissing = false)
    protected static class EurekaHealthCheckHandlerConfiguration {

        @Autowired(required = false)
        private StatusAggregator statusAggregator = new SimpleStatusAggregator();

        @Bean
        @ConditionalOnMissingBean(HealthCheckHandler.class)
        public EurekaHealthCheckHandler eurekaHealthCheckHandler() {
            return new EurekaHealthCheckHandler(this.statusAggregator);
        }

    }

    @Deprecated
    class Marker {

    }

    //定義了Client配置重刷的監聽器
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RefreshScopeRefreshedEvent.class)
    protected static class EurekaClientConfigurationRefresher implements ApplicationListener<RefreshScopeRefreshedEvent> {
        ....
    }

}

//看得出來包裝也只是將配置和客戶端放在了一起
public EurekaDiscoveryClient(EurekaClient eurekaClient,
        EurekaClientConfig clientConfig) {
    this.clientConfig = clientConfig;
    this.eurekaClient = eurekaClient;
}

 

【3】分析EurekaClient的相關配置EurekaClientConfigBean類

//僅列舉了部分
@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {
    //客戶端配置首碼
    public static final String PREFIX = "eureka.client";
    //public static final String DEFAULT_PREFIX = "/eureka";
    //預設的註冊地址
    public static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX + "/";
    //預設域
    public static final String DEFAULT_ZONE = "defaultZone";

    private static final int MINUTES = 60;

    //多長時間從註冊中心服務端拉取一次服務信息,單位秒;這個就是主動拉取註冊中心上所有服務的實例信息
    private int registryFetchIntervalSeconds = 30;
    //多長時間複製實例變化到eureka服務端,單位秒;這個配置是複製實例信息到註冊中心
    private int instanceInfoReplicationIntervalSeconds = 30;
    //實例初始化複製信息到eureka服務端的間隔時間,所以可以看到,其實實例的初始化階段不是立即複製實例信息到註冊中心的,單位秒
    private int initialInstanceInfoReplicationIntervalSeconds = 40;
    //eureka服務端的變化,多長時間,客戶端會獲取一次eureka服務的信息
    private int eurekaServiceUrlPollIntervalSeconds = 5 * MINUTES;
    //eureka server的代理埠
    private String proxyPort;
    //eureka server的代理host name
    private String proxyHost;
    //賬號
    private String proxyUserName;
    //密碼
    private String proxyPassword;
    //從server讀取所需的超時時間
    private int eurekaServerReadTimeoutSeconds = 8;
    //連接server的超時時間
    private int eurekaServerConnectTimeoutSeconds = 5;
    //被允許連接到所有server host的總連接數
    private int eurekaServerTotalConnections = 200;
    // 被允許連接到每一個server host的總連接數
    private 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 首先看看原生Promise有哪些功能: 支持同步和非同步 一個實例的狀態只能變更一次 支持鏈式調用 如果回調函數返回了新的實例,後續的then的執行權應該交給這個實例 提供快速解決和拒絕的實例 提供批量和優先 實例內部的報錯是可控的 接下來嘗試實現 註釋挺明瞭的,就不過分贅述啦,說下思路: js是同步 ...
  • 在vue3中的$attrs的變化 $listeners已被刪除合併到$attrs中。 $attrs現在包括class和style屬性。 也就是說在vue3中$listeners不存在了。vue2中$listeners是單獨存在的。 在vue3 $attrs包括class和style屬性, vue2中 ...
  • break ,continue,retrun的區別 1:break 在迴圈體內結束整個迴圈過程 for (var i = 1; i <= 5; i++) { if(i == 3){ break; } //只會輸出到2結束迴圈 console.log(i); } 2:continue 結束本次的迴圈, ...
  • 一、同源政策 跨域問題其實就是瀏覽器的同源策略造成的。同源策略限制了從同一個源載入的文檔或腳本如何與另一個源的資源進行交互。這是瀏覽器的一個用於隔離潛在惡意文件的重要的安全機制。同源指的是:協議、埠號、功能變數名稱必須一致。同源策略:protocol(協議)、domain(功能變數名稱)、port(埠)三者必須 ...
  • 題目描述:判斷字元串中重覆次數最多的字元 // 解決思路: // 1.判斷字元重覆的方法 // 創建空數組,利用鍵值對形式對每個字元進行計數 // 用到 採用for迴圈結合if判斷 對象[鍵] 是否有值,無則undefined // arr.charAt(i)取得arr中i索引號下的元素 // 2. ...
  • 你需要知道的4個資料庫擴展解決方案 你已經用一個直觀的、用戶友好的用戶界面啟動了你的應用程式。但是,如果你的應用程式遇到負載問題,這將使你的終端客戶在使用它時感到沮喪。很有可能問題不在應用程式內部,而是在資料庫。根據一項調查,38%的資料庫專業人員報告說資料庫停機是讓他們夜不能寐的重要問題。停機可能 ...
  • 背景 相信大家看到這個文章對消息伺服器已經不陌生了,筆者也是在平日無聊想著自己編寫一套關於RockerMQ 的消息灰度框架的時候,準備本地搭建一個RockerMQ服務環境時遇到了一個頭疼的問題。在執行RockerMQ官網的Topic創建的時候(sh bin/mqadmin updatetopic - ...
  • 前言 記憶體木馬,就是在記憶體中運行的木馬病毒,沒有代碼實體。記憶體木馬有著強隱蔽性,排查困難,殺不死(俗稱不死馬)的特點。 網路安全行業,有著很強的木桶效應。系統對抗黑帽,勝負取決於安全最薄弱的環節。黑帽對抗白帽,勝負取決於攻擊水平和和毀屍滅跡隱蔽的水平。 正文 本文不討論是由於任意文件上傳還是近源攻擊 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...