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