Eureka源碼解析:java
搭建Eureka服務的時候,咱們會再SpringBoot啓動類加上@EnableEurekaServer的註解,這個註解作了一些什麼,咱們一塊兒來看。
算法
點進@EnableEurekaServer這個註解就會看到下面代碼:spring
/* * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.netflix.eureka.server; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.Import; /** * Annotation to activate Eureka Server related configuration {@link EurekaServerAutoConfiguration} * * @author Dave Syer * @author Biju Kunjummen * */ @EnableDiscoveryClient @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EurekaServerMarkerConfiguration.class) public @interface EnableEurekaServer { }
你們能夠清楚的看到@EnableEurekaServer引用了@EnableDiscoveryClient這個註解,源碼以下:express
/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.client.discovery; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /** * Annotation to enable a DiscoveryClient implementation. * @author Spencer Gibb */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(EnableDiscoveryClientImportSelector.class) public @interface EnableDiscoveryClient { /** * If true, the ServiceRegistry will automatically register the local server. */ boolean autoRegister() default true; }
從這個註解咱們能夠知道,它主要是用來開啓DiscoveryClient實例的,經過搜索DiscoveryClient咱們能夠看到一個類和一個接口,獲得下圖關係:apache
其中,1 是 Spring Cloud 的接口,它定義了用來發現服務的經常使用抽象方法,經過該接口能夠有效的屏蔽服務治理的實現細節,因此使用 Spring Cloud 構建的微服務應用能夠方便的切換不一樣服務治理框架,而不改動程序代碼,只須要另外添加一些針對服務治理框架的配置便可。2 是對 1 接口的實現,從命名判斷。它實現的是對 Eureka 發現服務的封裝。因此 EurekaDiscoveryClient 依賴了 Netflix Eureka 的 EurekaClient 接口,EurekaClient 接口繼承了 LookupService 接口,它們都是 Netflix 開源包中的內容,主要定義了針對 Eureka 的發現服務的抽象發放,而真正實現發現服務的則Netflix包中的 DiscoveryClient (5)類。json
接下來,咱們就詳細看看DiscoveryClient類。先看下該類的頭部註釋,大體內容以下:安全
在具體研究Eureka Client 負責完成的任務以前,咱們先看看在哪裏對Eureka Server 的URL列表進行配置。根據配置的屬性名 eureka.client.service-url.defaultZone,經過 ServiceURL 能夠找到該屬性相關的加載屬性,可是在SR5 版本中它們都被 @Deprecated 標註爲再也不建議使用,並 @link 到了替代類 EndpointUtils,因此能夠在該類中找到下面這個函數:app
public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) { LinkedHashMap orderedUrls = new LinkedHashMap(); String region = getRegion(clientConfig); String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion()); if(availZones == null || availZones.length == 0) { availZones = new String[]{"default"}; } logger.debug("The availability zone for the given region {} are {}", region, Arrays.toString(availZones)); int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); String zone = availZones[myZoneOffset]; List serviceUrls = clientConfig.getEurekaServerServiceUrls(zone); if(serviceUrls != null) { orderedUrls.put(zone, serviceUrls); } int currentOffset = myZoneOffset == availZones.length - 1?0:myZoneOffset + 1; while(currentOffset != myZoneOffset) { zone = availZones[currentOffset]; serviceUrls = clientConfig.getEurekaServerServiceUrls(zone); if(serviceUrls != null) { orderedUrls.put(zone, serviceUrls); } if(currentOffset == availZones.length - 1) { currentOffset = 0; } else { ++currentOffset; } } if(orderedUrls.size() < 1) { throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!"); } else { return orderedUrls; } }
從上面的函數中能夠發現,客戶端依次加載了兩個內容,第一個是Region,第二個是Zone,從其加載邏輯上能夠判斷它們之間的關係:負載均衡
public static String getRegion(EurekaClientConfig clientConfig) { String region = clientConfig.getRegion(); if(region == null) { region = "default"; } region = region.trim().toLowerCase(); return region; }
public String[] getAvailabilityZones(String region) { String value = (String)this.availabilityZones.get(region); if(value == null) { value = "defaultZone"; } return value.split(","); }
在獲取了Region 和 Zone 的信息以後,纔開始真正加載 Eureka Server 的具體地址。它根據傳入的參數按必定算法肯定加載位於哪個Zone配置的serviceUrls。框架
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); String zone = availZones[myZoneOffset]; List serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
具體獲取serviceUrls 的實現,能夠詳細查看getEurekaServerServiceUrls 函數的具體實現類 EurekaClientConfigBean,用來加載配置文件中的內容,經過搜索defaultZone,咱們能夠很容易找到下面這個函數,它具體實現瞭如何解析該參數的過程,經過此內容,咱們能夠知道,eureka.client.service-url.defaultZone 屬性能夠配置多個,而且須要經過逗號分隔。
public List<String> getEurekaServerServiceUrls(String myZone) { String serviceUrls = (String)this.serviceUrl.get(myZone); if(serviceUrls == null || serviceUrls.isEmpty()) { serviceUrls = (String)this.serviceUrl.get("defaultZone"); } if(!StringUtils.isEmpty(serviceUrls)) { String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls); ArrayList eurekaServiceUrls = new ArrayList(serviceUrlsSplit.length); String[] var5 = serviceUrlsSplit; int var6 = serviceUrlsSplit.length; for(int var7 = 0; var7 < var6; ++var7) { String eurekaServiceUrl = var5[var7]; if(!this.endsWithSlash(eurekaServiceUrl)) { eurekaServiceUrl = eurekaServiceUrl + "/"; } eurekaServiceUrls.add(eurekaServiceUrl); } return eurekaServiceUrls; } else { return new ArrayList(); } }
當咱們在微服務應用中使用Ribbon來實現服務調用時,對於Zone的設置能夠在負載均衡時實現區域親和特性:Ribbon的默認策略會優先訪問同客戶端處於一個Zone中的服務端實例,只有當同一個Zone 中沒有可用服務端實例的時候纔會訪問其餘Zone中的實例。因此經過Zone屬性的定義,配合實際部署的物理結構,咱們就能夠有效地設計出對區域性故障的容錯集羣。
在理解了多個服務註冊中心信息的加載後,咱們再回頭看看DiscoveryClient類是如何實現「服務註冊」行爲的,經過查看它的構造類,能夠找到調用了下面這個函數:
private void initScheduledTasks() { int renewalIntervalInSecs; int expBackOffBound; if(this.clientConfig.shouldFetchRegistry()) { renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds(); expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS); } if(this.clientConfig.shouldRegisterWithEureka()) { renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: renew interval is: " + renewalIntervalInSecs); this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread(null)), (long)renewalIntervalInSecs, TimeUnit.SECONDS); this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); this.statusChangeListener = new StatusChangeListener() { public String getId() { return "statusChangeListener"; } public void notify(StatusChangeEvent statusChangeEvent) { if(InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) { DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent); } else { DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent); } DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate(); } }; if(this.clientConfig.shouldOnDemandUpdateStatusChange()) { this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener); } this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()); } else { logger.info("Not registering with Eureka server per configuration"); } }
在上面的函數中,能夠看到一個與服務註冊相關的判斷語句 if(this.clientConfig.shouldRegisterWithEureka())。在該分支內,建立了一個 InstanceInfoReplicator 類的實例,他會執行一個定時任務,而這個定時任務的具體工做能夠查看該類的run() 函數,具體以下所示:
public void run() { boolean var6 = false; ScheduledFuture next2; label53: { try { var6 = true; this.discoveryClient.refreshInstanceInfo(); Long next = this.instanceInfo.isDirtyWithTime(); if(next != null) { this.discoveryClient.register(); this.instanceInfo.unsetIsDirty(next.longValue()); var6 = false; } else { var6 = false; } break label53; } catch (Throwable var7) { logger.warn("There was a problem with the instance info replicator", var7); var6 = false; } finally { if(var6) { ScheduledFuture next1 = this.scheduler.schedule(this, (long)this.replicationIntervalSeconds, TimeUnit.SECONDS); this.scheduledPeriodicRef.set(next1); } } next2 = this.scheduler.schedule(this, (long)this.replicationIntervalSeconds, TimeUnit.SECONDS); this.scheduledPeriodicRef.set(next2); return; } next2 = this.scheduler.schedule(this, (long)this.replicationIntervalSeconds, TimeUnit.SECONDS); this.scheduledPeriodicRef.set(next2); }
這裏有個 this.discoveryClient.register(); 這一行,真正觸發調用註冊的地方就在這裏,繼續查看register() 的實現內容,以下:
boolean register() throws Throwable { logger.info("DiscoveryClient_" + this.appPathIdentifier + ": registering service..."); EurekaHttpResponse httpResponse; try { httpResponse = this.eurekaTransport.registrationClient.register(this.instanceInfo); } catch (Exception var3) { logger.warn("{} - registration failed {}", new Object[]{"DiscoveryClient_" + this.appPathIdentifier, var3.getMessage(), var3}); throw var3; } if(logger.isInfoEnabled()) { logger.info("{} - registration status: {}", "DiscoveryClient_" + this.appPathIdentifier, Integer.valueOf(httpResponse.getStatusCode())); } return httpResponse.getStatusCode() == 204; }
能夠看出,註冊操做也是經過REST請求的方式進行的。同時,咱們能看到發起註冊請求的時候,傳入了一個 instanceInfo 對象,該對象就是註冊時客戶端給服務端的服務的元數據。
順着上面的思路,繼續看 DiscoveryClient 的 initScheduledTasks 函數,不難發如今其中還有兩個定時任務,分別是 「服務獲取」 和 「服務續約」 :
private void initScheduledTasks() { int renewalIntervalInSecs; int expBackOffBound; if(this.clientConfig.shouldFetchRegistry()) { renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds(); expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS); } if(this.clientConfig.shouldRegisterWithEureka()) { renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: renew interval is: " + renewalIntervalInSecs); this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread(null)), (long)renewalIntervalInSecs, TimeUnit.SECONDS); ………… } else { logger.info("Not registering with Eureka server per configuration"); } }
從源碼中能夠看出,「服務獲取」 任務相對於 「服務續約」 和 「服務註冊」 任務更爲獨立。「服務續約」 與 「服務註冊」 在同一個 if 邏輯中,這個不難理解,服務註冊到Eureka Server 後,須要一個心跳去續約,防止被剔除,因此它們確定是成對出現的。
而 「服務獲取」 的邏輯在一個獨立的 if 判斷中,並且是由eureka.client.fetch-registry=true 參數控制,它默認爲true,大部分狀況下不需關心。
繼續往下能夠發現 「服務獲取」 和 「服務續約」 的具體方法,其中 「服務續約」 的實現比較簡單,直接以REST請求的方式進行續約:
boolean renew() { try { EurekaHttpResponse httpResponse = this.eurekaTransport.registrationClient.sendHeartBeat(this.instanceInfo.getAppName(), this.instanceInfo.getId(), this.instanceInfo, (InstanceStatus)null); logger.debug("{} - Heartbeat status: {}", "DiscoveryClient_" + this.appPathIdentifier, Integer.valueOf(httpResponse.getStatusCode())); if(httpResponse.getStatusCode() == 404) { this.REREGISTER_COUNTER.increment(); logger.info("{} - Re-registering apps/{}", "DiscoveryClient_" + this.appPathIdentifier, this.instanceInfo.getAppName()); return this.register(); } else { return httpResponse.getStatusCode() == 200; } } catch (Throwable var3) { logger.error("{} - was unable to send heartbeat!", "DiscoveryClient_" + this.appPathIdentifier, var3); return false; } }
而 「服務獲取」 則複雜一些,會根據是不是第一次獲取發起不一樣的 REST 請求和相應的處理。
經過上面的源碼分析,能夠看到全部的交互都是經過 REST 請求發起的。下面看看服務註冊中心對這些請求的處理。Eureka Server 對於各種 REST 請求的定義都位於 com.netflix.eureka.resources 包下。
以 「服務註冊」 請求爲例(在ApplicationResource類中):
@POST @Consumes({"application/json", "application/xml"}) public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-replication") String isReplication) { logger.debug("Registering instance {} (replication={})", info.getId(), isReplication); if(this.isBlank(info.getId())) { return Response.status(400).entity("Missing instanceId").build(); } else if(this.isBlank(info.getHostName())) { return Response.status(400).entity("Missing hostname").build(); } else if(this.isBlank(info.getAppName())) { return Response.status(400).entity("Missing appName").build(); } else if(!this.appName.equals(info.getAppName())) { return Response.status(400).entity("Mismatched appName, expecting " + this.appName + " but was " + info.getAppName()).build(); } else if(info.getDataCenterInfo() == null) { return Response.status(400).entity("Missing dataCenterInfo").build(); } else if(info.getDataCenterInfo().getName() == null) { return Response.status(400).entity("Missing dataCenterInfo Name").build(); } else { DataCenterInfo dataCenterInfo = info.getDataCenterInfo(); if(dataCenterInfo instanceof UniqueIdentifier) { String dataCenterInfoId = ((UniqueIdentifier)dataCenterInfo).getId(); if(this.isBlank(dataCenterInfoId)) { boolean experimental = "true".equalsIgnoreCase(this.serverConfig.getExperimental("registration.validation.dataCenterInfoId")); if(experimental) { String amazonInfo1 = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id"; return Response.status(400).entity(amazonInfo1).build(); } if(dataCenterInfo instanceof AmazonInfo) { AmazonInfo amazonInfo = (AmazonInfo)dataCenterInfo; String effectiveId = amazonInfo.get(MetaDataKey.instanceId); if(effectiveId == null) { amazonInfo.getMetadata().put(MetaDataKey.instanceId.getName(), info.getId()); } } else { logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass()); } } } this.registry.register(info, "true".equals(isReplication)); return Response.status(204).build(); } }
在對註冊信息進行了一堆校驗以後,會調用 org.springframework.cloud.netflix.eureka.server.InstanceRegister 對象中的 register( InstanceInfo info, int leaseDuration, boolean isReplication) 函數來進行服務註冊:
public void register(InstanceInfo info, int leaseDuration, boolean isReplication) { this.handleRegistration(info, leaseDuration, isReplication); super.register(info, leaseDuration, isReplication); }
private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) { this.log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration + ", isReplication " + isReplication); this.publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication)); }
在註冊函數中,先調用publishEvent 函數,將該新服務註冊的事件傳播出去,而後調用 com.netflix.eureka.registry.AbstractInstanceRegistry 父類中的註冊實現,將 InstanceInfo 中的元數據信息存儲在一個 ConcurrentHashMap 對象中。正如以前所說,註冊中心存儲了兩層 Map 結構,第一層的key 存儲服務名: InstanceInfo 中的APPName 屬性,第二層的 key 存儲實例名:InstanceInfo中的 instanceId 屬性。
在 Eureka 的服務治理體系中,主要分爲服務端和客戶端兩個不一樣的角色,服務端爲服務註冊中心,而客戶端爲各個提供接口的微服務應用。當咱們構建了高可用的註冊中心以後,該集羣中全部的微服務應用和後續將要介紹的一些基礎類應用(如配置中心、API網關等)均可以視爲該體系下的一個微服務(Eureka客戶端)。服務註冊中心也同樣,只是高可用環境下的服務註冊中心除了服務端以外,還爲集羣中的其餘客戶端提供了服務註冊的特殊功能。因此,Eureka 客戶端的配置對象存在於全部 Eureka 服務治理體系下的應用實例中。在使用使用 Spring cloud Eureka 的過程當中, 咱們所作的配置內容幾乎都是對 Eureka 客戶端配置進行的操做,因此瞭解這部分的配置內容,對於用好 Eureka 很是有幫助。
Eureka 客戶端的配置主要分爲如下兩個方面:
關於服務註冊類的配置信息,咱們能夠經過查看 org.springframework.cloud.netflix.eureka.EurekaClientConfigBean 的源碼來得到比官方文檔中更爲詳盡的內容,這些配置信息都已 eureka.client 爲前綴。下面針對一些經常使用的配置信息作進一步的介紹和說明。
在配置文件中經過 eureka.client.service-url 實現。該參數定義以下所示,它的配置值存儲在HashMap類型中,而且設置有一組默認值,默認值的key爲 defaultZone、value 爲 http://localhost:8761/eureka/,類名爲 EurekaClientConfigBean。
private Map<String, String> serviceUrl = new HashMap(); this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/"); public static final String DEFAULT_URL = "http://localhost:8761/eureka/"; public static final String DEFAULT_ZONE = "defaultZone";
因爲以前的服務註冊中心使用了 8082 端口,因此咱們作了以下配置,來說應用註冊到對應的 Eureka 服務端中。
eureka.client.service-url.defaultZone=http://localhost:8082/eureka/
當構建了高可用的服務註冊中心集羣時,能夠爲參數的value 值配置多個註冊中心的地址(逗號分隔):
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
另外,爲了服務註冊中心的安全考慮,不少時候會爲服務註冊中心加入安全校驗。這個時候,在配置serviceUrl時,須要在value 值的 URL 中加入響應的安全校驗信息,好比: http://<username>:<password>@localhost:1111/eureka。其中<username>爲安全校驗信息的用戶名,<password>爲該用戶的密碼。
這些參數均以 eureka.client 爲前綴。
關於服務實例類的配置信息,能夠經過查看 org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 的源碼來獲取詳細內容,這些配置均以 eureka.instance 爲前綴。
在 org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 的配置信息中,有一大部份內容都是對服務實例元數據的配置,元數據是 Eureka 客戶端在向註冊中心發送註冊請求時,用來描述自身服務信息的對象,其中包含了一些標準化的元數據,好比服務名稱、實例名稱、實例IP、實例端口等用於服務治理的重要信息;以及一些用於負載均衡策略或是其餘特殊用途的自定義元數據信息。
在使用 Spring Cloud Eureka 的時候,全部的配置信息都經過 org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 進行加載,但在真正進行服務註冊時,仍是會包裝成 com.netflix.appinfo.InstanceInfo 對象發送給 Eureka 客戶端。這兩個類的定義很是類似,能夠直接查看 com.netflix.appinfo.InstanceInfo 類中的詳細定義來了解原聲的 Eureka 對元數據的定義。其中,Map<String, String> metaData = new ConcurrentHashMap<String, String>(); 是自定義的元數據信息,而其餘成員變量則是標準化的元數據信息。Spring Cloud 的EurekaInstanceConfigBean 對原生元數據對象作了一些配置優化處理,在後續的介紹中會提到這些內容。
咱們能夠經過 eureka.instance.<properties>=<value> 的格式對標準化元數據直接進行配置,<properties> 就是 EurekaInstanceConfigBean 對象中的成員變量名。對於自定義元數據,能夠經過 eureka.instance.metadataMap.<key>=<value> 的格式來進行配置。
接着,針對一些經常使用的元數據配置作進一步的介紹和說明。
實例名,即 InstanceInfo 中的 instanceId 參數,它是區分同一服務中不一樣實例的惟一標識。在NetflixEureka 的原生實現中,實例名採用主機名做爲默認值,這樣的設置使得在同一主機上沒法啓動多個相同的服務實例。因此,在 Spring Cloud Eureka 的配置中,針對同一主機中啓動多實例的狀況,對實例名的默認命名作了更爲合理的擴展,它採用了以下默認規則:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id}:${server.port}
對於實例名的命名規則,能夠經過eureka.instance.instanceId 參數來進行配置。好比,在本地進行客戶端負載均衡調試時,須要啓動同一服務的多個實例,若是咱們直接啓動同一個應用必然會發生端口衝突。雖然能夠在命令行中指定不一樣的server.port 來啓動,但這樣略顯麻煩。能夠直接經過設置 server.port=0 或者使用隨機數 server.port=${random.int[10000,19999]} 來讓Tomcat 啓動的時候採用隨機端口。可是這個時候會發現註冊到 Eureka Server的實例名都是相同的,這會使得只有一個服務實例可以正常提供服務。對於這個問題,就能夠經過設置實例名規則來輕鬆解決:
eureka.instance.instanceId=${spring.application.name}:${random.int}
經過上面的配置,利用應用名+隨機數的方式來區分不一樣的實例,從而實如今同一個主機上,不指定端就能輕鬆啓動多個實例的效果。