轉載請標明出處:
blog.csdn.net/forezp/arti…
本文出自方誌朋的博客javascript
Register:服務註冊
當Eureka客戶端向Eureka Server註冊時,它提供自身的元數據,好比IP地址、端口,運行情況指示符URL,主頁等。java
Renew:服務續約
Eureka客戶會每隔30秒發送一次心跳來續約。 經過續約來告知Eureka Server該Eureka客戶仍然存在,沒有出現問題。 正常狀況下,若是Eureka Server在90秒沒有收到Eureka客戶的續約,它會將實例從其註冊表中刪除。 建議不要更改續約間隔。git
Fetch Registries:獲取註冊列表信息
Eureka客戶端從服務器獲取註冊表信息,並將其緩存在本地。客戶端會使用該信息查找其餘服務,從而進行遠程調用。該註冊列表信息按期(每30秒鐘)更新一次。每次返回註冊列表信息可能與Eureka客戶端的緩存信息不一樣, Eureka客戶端自動處理。若是因爲某種緣由致使註冊列表信息不能及時匹配,Eureka客戶端則會從新獲取整個註冊表信息。 Eureka服務器緩存註冊列表信息,整個註冊表以及每一個應用程序的信息進行了壓縮,壓縮內容和沒有壓縮的內容徹底相同。Eureka客戶端和Eureka 服務器可使用JSON / XML格式進行通信。在默認的狀況下Eureka客戶端使用壓縮JSON格式來獲取註冊列表的信息。github
Cancel:服務下線
Eureka客戶端在程序關閉時向Eureka服務器發送取消請求。 發送請求後,該客戶端實例信息將從服務器的實例註冊表中刪除。該下線請求不會自動完成,它須要調用如下內容:
DiscoveryManager.getInstance().shutdownComponent();spring
Eviction 服務剔除
在默認的狀況下,當Eureka客戶端連續90秒沒有向Eureka服務器發送服務續約,即心跳,Eureka服務器會將該服務實例從服務註冊列表刪除,即服務剔除。json
如圖爲Eureka的高級架構圖,該圖片來自於Eureka開源代碼的文檔,地址爲github.com/Netflix/eur… 。緩存
從圖能夠看出在這個體系中,有2個角色,即Eureka Server和Eureka Client。而Eureka Client又分爲Applicaton Service和Application Client,即服務提供者何服務消費者。 每一個區域有一個Eureka集羣,而且每一個區域至少有一個eureka服務器能夠處理區域故障,以防服務器癱瘓。服務器
Eureka Client向Eureka Serve註冊,並將本身的一些客戶端信息發送Eureka Serve。而後,Eureka Client經過向Eureka Serve發送心跳(每30秒)來續約服務的。 若是客戶端持續不能續約,那麼,它將在大約90秒內從服務器註冊表中刪除。 註冊信息和續訂被複制到集羣中的Eureka Serve全部節點。 來自任何區域的Eureka Client均可以查找註冊表信息(每30秒發生一次)。根據這些註冊表信息,Application Client能夠遠程調用Applicaton Service來消費服務。網絡
服務註冊,即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註冊。其代碼以下:
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
}複製代碼
服務續約和服務註冊很是相似,經過以前的分析能夠知道,服務註冊在Eureka Client程序啓動以後開啓,並同時開啓服務續約的定時任務。在eureka-client-1.6.2.jar的DiscoveryClient的類下有renew()方法,其代碼以下:
/** * Renew with the eureka service by making the appropriate REST call */
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);
... 代碼省略
}複製代碼
讀者能夠跟蹤registry.renew的代碼一直深刻研究。在這裏就再也不多講述。另外服務續約有2個參數是能夠配置,即Eureka Client發送續約心跳的時間參數和Eureka Server在多長時間內沒有收到心跳將實例剔除的時間參數,在默認的狀況下這兩個參數分別爲30秒和90秒,官方給的建議是不要修改,若是有特殊要求仍是能夠調整的,只須要分別在Eureka Client和Eureka Server修改如下參數:
eureka.instance.leaseRenewalIntervalInSeconds
eureka.instance.leaseExpirationDurationInSeconds複製代碼
最後,服務註冊列表的獲取、服務下線和服務剔除就不在這裏進行源碼跟蹤解讀,由於和服務註冊和續約相似,有興趣的朋友能夠本身看下源碼,深刻理解。總的來講,經過讀源碼,能夠發現,總體架構與前面小節的eureka 的高可用架構圖徹底一致。
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 Server刷新緩存
Eureka客戶端保留註冊表信息的緩存。 該緩存每30秒更新一次(如前所述)。 因 此,客戶端決定刷新其本地緩存並發現其餘新註冊的實例可能須要30秒。
LoadBalancer Refresh
Ribbon的負載平衡器從本地的Eureka Client獲取服務註冊列表信息。Ribbon自己還維護本地緩存,以免爲每一個請求調用本地客戶端。 此緩存每30秒刷新一次(可由ribbon.ServerListRefreshInterval配置)。 因此,可能須要30多秒才能使用新註冊的實例。
綜上幾個因素,一個新註冊的實例,特別是啓動較快的實例(默認延遲40秒註冊),不能立刻被Eureka Server發現。另外,剛註冊的Eureka Client也不能當即被其餘服務調用,由於調用方由於各類緩存沒有及時的獲取到新的註冊列表。
當一個新的Eureka Server出現時,它嘗試從相鄰節點獲取全部實例註冊表信息。若是從Peer節點獲取信息時出現問題,Eureka Serve會嘗試其餘的Peer節點。若是服務器可以成功獲取全部實例,則根據該信息設置應該接收的更新閾值。若是有任什麼時候間,Eureka Serve接收到的續約低於爲該值配置的百分比(默認爲15分鐘內低於85%),則服務器開啓自我保護模式,即再也不剔除註冊列表的信息。
這樣作的好處就是,若是是Eureka Server自身的網絡問題,致使Eureka Client的續約不上,Eureka Client的註冊列表信息再也不被刪除,也就是Eureka Client還能夠被其餘服務消費。