Eureka: 字面意思是"找到了",Netflix技術棧中,做爲服務註冊中心對整個微服務架構起着最核心的整合做用spring
Register:服務註冊,Eureka客戶端向Eureka Server註冊時,它提供自身的元數據,好比IP地址、端口,運行情況指示符URL,主頁等json
Renew:服務續約,Eureka客戶端會每隔30秒發送一次心跳來續約。 經過續約來告知Eureka Server該Eureka客戶仍然存在,沒有出現問題。 正常狀況下,若是Eureka Server在90秒沒有收到Eureka客戶的續約,它會將實例從其註冊表中刪除。 建議不要更改續約間隔。緩存
Fetch Registries:獲取註冊列表信息, Eureka客戶端從服務器獲取註冊表信息,並將其緩存在本地。客戶端會使用該信息查找其餘服務,從而進行遠程調用。 該註冊列表信息按期(每30秒鐘)更新一次。每次返回註冊列表信息可能與Eureka客戶端的緩存信息不一樣, Eureka客戶端自動處理。 若是因爲某種緣由致使註冊列表信息不能及時匹配,Eureka客戶端則會從新獲取整個註冊表信息。 Eureka服務器緩存註冊列表信息,整個註冊表以及每一個應用程序的信息進行了壓縮,壓縮內容和沒有壓縮的內容徹底相同。 Eureka客戶端和Eureka 服務器可使用JSON / XML格式進行通信。在默認的狀況下Eureka客戶端使用壓縮JSON格式來獲取註冊列表的信息。服務器
Cancel:服務下線 ,Eureka客戶端在程序關閉時向Eureka服務器發送取消請求。 發送請求後,該客戶端實例信息將從服務器的實例註冊表中刪除。該下線請求不會自動完成,它須要調用如下內容:網絡
DiscoveryManager.getInstance().shutdownComponent();
Eviction 服務剔除 ,在默認的狀況下,當Eureka客戶端連續90秒沒有向Eureka服務器發送服務續約,即心跳,Eureka服務器會將該服務實例從服務註冊列表刪除,即服務剔除。架構
Eureka的高可用架構併發
能夠看出在這個體系中,有2個角色,即Eureka Server和Eureka Client。 Eureka Client又分爲Applicaton Service和Application Client,即服務提供者和服務消費者。每一個區域有一個Eureka集羣,而且每一個區域至少有一個eureka服務器能夠處理區域故障,以防服務器癱瘓。app
Eureka Client向Eureka Serve註冊,並將本身的一些客戶端信息發送Eureka Serve。而後,Eureka Client經過向Eureka Serve發送心跳(每30秒)來續約服務的。 若是客戶端持續不能續約,那麼,它將在大約90秒內從服務器註冊表中刪除。 註冊信息和續訂被複制到集羣中的Eureka Serve全部節點。 來自任何區域的Eureka Client均可以查找註冊表信息(每30秒發生一次)。根據這些註冊表信息,Application Client能夠遠程調用Applicaton Service來消費服務。ide
***一個eureka client註冊多個指定的eureka server *** eureka.client.serviceUrl.defaultZone=http://10.201.200.72:8761/eureka/,http://10.201.200.76:8761/eureka/微服務
Register服務註冊 服務註冊,即Eureka Client向Eureka Server提交本身的服務信息,包括IP地址、端口、service ID等信息。若是Eureka Client沒有寫service ID,則默認爲 ${spring.application.name}。
服務註冊其實很簡單,在Eureka Client啓動的時候,將自身的服務的信息發送到Eureka Server。如今來簡單的閱讀下源碼。在Maven的依賴包下,找到eureka-client-1.6.2.jar包。在com.netflix.discovery包下有個DiscoveryClient類,該類包含了Eureka Client向Eureka Server的相關方法。其中DiscoveryClient實現了EurekaClient接口,而且它是一個單例模式,而EurekaClient繼承了LookupService接口。它們之間的關係如圖所示。
在DiscoveryClient類有一個服務註冊的方法register(),該方法是經過Http請求向Eureka Client註冊。其代碼以下:
/** ](https://static.oschina.net/uploads/img/201802/08132817_xC3Z.png "在這裏輸入圖片標題") * Register with the eureka service by making the appropriate REST call. */ boolean register() throws Throwable { logger.info(PREFIX + appPathIdentifier + ": registering service..."); EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == 204; }
在DiscoveryClient類繼續追蹤register()方法,它被InstanceInfoReplicator 類的run()方法調用,其中InstanceInfoReplicator實現了Runnable接口,run()方法代碼以下:
public void run() { try { discoveryClient.refreshInstanceInfo(); Long dirtyTimestamp = instanceInfo.isDirtyWithTime(); if (dirtyTimestamp != null) { discoveryClient.register(); instanceInfo.unsetIsDirty(dirtyTimestamp); } } catch (Throwable t) { logger.warn("There was a problem with the instance info replicator", t); } finally { Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); } }
而InstanceInfoReplicator類是在DiscoveryClient初始化過程當中使用的,其中有一個initScheduledTasks()方法。該方法主要開啓了獲取服務註冊列表的信息,若是須要向Eureka Server註冊,則開啓註冊,同時開啓了定時向Eureka Server服務續約的定時任務,具體代碼以下:
private void initScheduledTasks() { ...//省略了任務調度獲取註冊列表的代碼 if (clientConfig.shouldRegisterWithEureka()) { ... // Heartbeat timer scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); // InstanceInfo replicator instanceInfoReplicator = new InstanceInfoReplicator( this, instanceInfo, clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); // burstSize statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { instanceInfoReplicator.onDemandUpdate(); } }; ... }
而後在來看Eureka server端的代碼,在Maven的eureka-core:1.6.2的jar包下。打開com.netflix.eureka包,很輕鬆的就發現了又一個EurekaBootStrap的類,BootStrapContext具備最早初始化的權限,因此先看這個類。
protected void initEurekaServerContext() throws Exception { ...//省略代碼 PeerAwareInstanceRegistry registry; if (isAws(applicationInfoManager.getInfo())) { ...//省略代碼,若是是AWS的代碼 } else { registry = new PeerAwareInstanceRegistryImpl( eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, eurekaClient ); } PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes( registry, eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, applicationInfoManager ); }
其中PeerAwareInstanceRegistryImpl和PeerEurekaNodes兩個類看其命名,應該和服務註冊以及Eureka Server高可用有關。先追蹤PeerAwareInstanceRegistryImpl類,在該類有個register()方法,該方法提供了註冊,而且將註冊後信息同步到其餘的Eureka Server服務。代碼以下:
public void register(final InstanceInfo info, final boolean isReplication) { int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS; if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) { leaseDuration = info.getLeaseInfo().getDurationInSecs(); } super.register(info, leaseDuration, isReplication); replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); }
其中 super.register(info, leaseDuration, isReplication)方法,點擊進去到子類AbstractInstanceRegistry能夠發現更多細節,其中註冊列表的信息被保存在一個Map中。replicateToPeers()方法,即同步到其餘Eureka Server的其餘Peers節點,追蹤代碼,發現它會遍歷循環向全部的Peers節點註冊,最終執行類PeerEurekaNodes的register()方法,該方法經過執行一個任務向其餘節點同步該註冊信息,代碼以下:
public void register(final InstanceInfo info) throws Exception { long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info); batchingDispatcher.process( taskId("register", info), new InstanceReplicationTask(targetHost, Action.Register, info, null, true) { public EurekaHttpResponse<Void> execute() { return replicationClient.register(info); } }, expiryTime ); }
通過一系列的源碼追蹤,能夠發現PeerAwareInstanceRegistryImpl的register()方法實現了服務的註冊,而且向其餘Eureka Server的Peer節點同步了該註冊信息,那麼register()方法被誰調用了呢?以前在Eureka Client的分析能夠知道,Eureka Client是經過 http來向Eureka Server註冊的,那麼Eureka Server確定會提供一個註冊的接口給Eureka Client調用,那麼PeerAwareInstanceRegistryImpl的register()方法確定最終會被暴露的Http接口所調用。在Idea開發工具,按住alt+鼠標左鍵,能夠很快定位到ApplicationResource類的addInstance ()方法,即服務註冊的接口,其代碼以下:
@POST @Consumes({"application/json", "application/xml"}) public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) { ...//省略代碼 registry.register(info, "true".equals(isReplication)); return Response.status(204).build(); // 204 to be backwards compatible }
Renew服務續約
服務續約和服務註冊很是相似,經過以前的分析能夠知道,服務註冊在Eureka Client程序啓動以後開啓,並同時開啓服務續約的定時任務。在eureka-client-1.6.2.jar的DiscoveryClient的類下有renew()方法,其代碼以下:
boolean renew() { EurekaHttpResponse<InstanceInfo> httpResponse; try { httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null); logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); if (httpResponse.getStatusCode() == 404) { REREGISTER_COUNTER.increment(); logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName()); return register(); } return httpResponse.getStatusCode() == 200; } catch (Throwable e) { logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e); return false; } }
另外服務端的續約接口在eureka-core:1.6.2.jar的 com.netflix.eureka包下的InstanceResource類下,接口方法爲renewLease(),它是REST接口。爲了減小類篇幅,省略了大部分代碼的展現。其中有個registry.renew()方法,即服務續約,代碼以下:
@PUT public Response renewLease(...參數省略){ ... 代碼省略 boolean isSuccess=registry.renew(app.getName(),id, isFromReplicaNode); ... 代碼省略 }
另外服務續約有2個參數是能夠配置,即Eureka Client發送續約心跳的時間參數和Eureka Server在多長時間內沒有收到心跳將實例剔除的時間參數,在默認的狀況下這兩個參數分別爲30秒和90秒,官方給的建議是不要修改,若是有特殊要求仍是能夠調整的,只須要分別在Eureka Client和Eureka Server修改如下參數: eureka.instance.leaseRenewalIntervalInSeconds eureka.instance.leaseExpirationDurationInSeconds
Eureka Client註冊一個實例爲何這麼慢 Eureka Client一啓動(不是啓動完成),不是當即向Eureka Server註冊,它有一個延遲向服務端註冊的時間,經過跟蹤源碼,能夠發現默認的延遲時間爲40秒,源碼在eureka-client-1.6.2.jar的DefaultEurekaClientConfig類下,代碼以下:
public int getInitialInstanceInfoReplicationIntervalSeconds() { return configInstance.getIntProperty( namespace + INITIAL_REGISTRATION_REPLICATION_DELAY_KEY, 40).get(); }
Eureka Server的響應緩存 Eureka Server維護每30秒更新的響應緩存,可經過更改配置eureka.server.responseCacheUpdateIntervalMs來修改。 因此即便實例剛剛註冊,它也不會出如今調用/ eureka / apps REST端點的結果中。
Eureka Client刷新緩存 Eureka客戶端保留註冊表信息的緩存。 該緩存每30秒更新一次(如前所述)。 因 此,客戶端決定刷新其本地緩存並發現其餘新註冊的實例可能須要30秒。
LoadBalancer Refresh Ribbon的負載平衡器從本地的Eureka Client獲取服務註冊列表信息。Ribbon自己還維護本地緩存,以免爲每一個請求調用本地客戶端。 此緩存每30秒刷新一次(可由ribbon.ServerListRefreshInterval配置)。 因此,可能須要30多秒才能使用新註冊的實例。
綜上幾個因素,一個新註冊的實例,特別是啓動較快的實例(默認延遲40秒註冊),不能立刻被Eureka Server發現。另外,剛註冊的Eureka Client也不能當即被其餘服務調用,由於調用方由於各類緩存沒有及時的獲取到新的註冊列表。
Eureka 的自我保護模式 當一個新的Eureka Server出現時,它嘗試從相鄰節點獲取全部實例註冊表信息。 若是從Peer節點獲取信息時出現問題,Eureka Serve會嘗試其餘的Peer節點。若是服務器可以成功獲取全部實例,則根據該信息設置應該接收的更新閾值。
若是有任什麼時候間,Eureka Serve接收到的續約低於爲該值配置的百分比(默認爲15分鐘內低於85%),則服務器開啓自我保護模式,即再也不剔除註冊列表的信息。
這樣作的好處就是,若是是Eureka Server自身的網絡問題,致使Eureka Client的續約不上,Eureka Client的註冊列表信息再也不被刪除,也就是Eureka Client還能夠被其餘服務消費。