eureka同步原理_eureka原理剖析
Eureka是Netflix開源的一款提供服務(wù)注冊(cè)和發(fā)現(xiàn)的產(chǎn)品,github地址為 https://github.com/Netflix/eureka。注冊(cè)中心是分布式開發(fā)的核心組件之一,而eureka是spring cloud推薦的注冊(cè)中心實(shí)現(xiàn),因此對(duì)于Java開發(fā)同學(xué)來說,還是有必要學(xué)習(xí)eureka的,特別是其架構(gòu)及設(shè)計(jì)思想。
官方文檔定義是:Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers. We call this service, the Eureka Server. Eureka also comes with a Java-based client component,the Eureka Client, which makes interactions with the service much easier. The client also has a built-in load balancer that does basic round-robin load balancing.
Eureka是一個(gè)REST (Representational State Transfer)服務(wù),它主要用于AWS云,用于定位服務(wù),以實(shí)現(xiàn)中間層服務(wù)器的負(fù)載平衡和故障轉(zhuǎn)移,我們稱此服務(wù)為Eureka服務(wù)器。Eureka也有一個(gè)基于java的客戶端組件,Eureka客戶端,這使得與服務(wù)的交互更加容易,同時(shí)客戶端也有一個(gè)內(nèi)置的負(fù)載平衡器,它執(zhí)行基本的循環(huán)負(fù)載均衡。
Eureka提供了完整的Service Registry和Service Discovery實(shí)現(xiàn),并且也經(jīng)受住了Netflix自己的生產(chǎn)環(huán)境考驗(yàn),相對(duì)使用起來會(huì)比較省心(同時(shí)Spring Cloud還有一套非常完善的開源代碼來整合Eureka,所以使用起來非常方便)。
本文主要內(nèi)容有:eureka基礎(chǔ)概念及架構(gòu)、服務(wù)發(fā)現(xiàn)原理、eureka server/client流程分析及優(yōu)缺點(diǎn)分析,最后做個(gè)小結(jié)。由于本文側(cè)重于原理分析,因此eureka(結(jié)合spring cloud)的使用就不再贅述了,感興趣的小伙伴可以看下 程序猿DD 關(guān)于spring cloud的相關(guān)教程。
eureka基礎(chǔ)
eureka架構(gòu)圖
- Eureka Server:提供服務(wù)注冊(cè)和發(fā)現(xiàn),多個(gè)Eureka Server之間會(huì)同步數(shù)據(jù),做到狀態(tài)一致(最終一致性)
- Service Provider:服務(wù)提供方,將自身服務(wù)注冊(cè)到Eureka,從而使服務(wù)消費(fèi)方能夠找到
- Service Consumer:服務(wù)消費(fèi)方,從Eureka獲取注冊(cè)服務(wù)列表,從而能夠消費(fèi)服務(wù)
注意,上圖中的3個(gè)角色都是邏輯角色,在實(shí)際運(yùn)行中,這幾個(gè)角色甚至可以是同一個(gè)項(xiàng)目(JVM進(jìn)程)中。
自我保護(hù)機(jī)制
自我保護(hù)機(jī)制主要在Eureka Client和Eureka Server之間存在網(wǎng)絡(luò)分區(qū)的情況下發(fā)揮保護(hù)作用,在服務(wù)器端和客戶端都有對(duì)應(yīng)實(shí)現(xiàn)。假設(shè)在某種特定的情況下(如網(wǎng)絡(luò)故障), Eureka Client和Eureka Server無法進(jìn)行通信,此時(shí)Eureka Client無法向Eureka Server發(fā)起注冊(cè)和續(xù)約請(qǐng)求,Eureka Server中就可能因注冊(cè)表中的服務(wù)實(shí)例租約出現(xiàn)大量過期而面臨被剔除的危險(xiǎn),然而此時(shí)的Eureka Client可能是處于健康狀態(tài)的(可接受服務(wù)訪問),如果直接將注冊(cè)表中大量過期的服務(wù)實(shí)例租約剔除顯然是不合理的,自我保護(hù)機(jī)制提高了eureka的服務(wù)可用性。
當(dāng)自我保護(hù)機(jī)制觸發(fā)時(shí),Eureka不再從注冊(cè)列表中移除因?yàn)殚L時(shí)間沒收到心跳而應(yīng)該過期的服務(wù),仍能查詢服務(wù)信息并且接受新服務(wù)注冊(cè)請(qǐng)求,也就是其他功能是正常的。這里思考下,如果eureka節(jié)點(diǎn)A觸發(fā)自我保護(hù)機(jī)制過程中,有新服務(wù)注冊(cè)了然后網(wǎng)絡(luò)回復(fù)后,其他peer節(jié)點(diǎn)能收到A節(jié)點(diǎn)的新服務(wù)信息,數(shù)據(jù)同步到peer過程中是有網(wǎng)絡(luò)異常重試的,也就是說,是能保證最終一致性的。
服務(wù)發(fā)現(xiàn)原理
eureka server可以集群部署,多個(gè)節(jié)點(diǎn)之間會(huì)進(jìn)行(異步方式)數(shù)據(jù)同步,保證數(shù)據(jù)最終一致性,Eureka Server作為一個(gè)開箱即用的服務(wù)注冊(cè)中心,提供的功能包括:服務(wù)注冊(cè)、接收服務(wù)心跳、服務(wù)剔除、服務(wù)下線等。需要注意的是,Eureka Server同時(shí)也是一個(gè)Eureka Client,在不禁止Eureka Server的客戶端行為時(shí),它會(huì)向它配置文件中的其他Eureka Server進(jìn)行拉取注冊(cè)表、服務(wù)注冊(cè)和發(fā)送心跳等操作。
eureka server端通過appName和instanceInfoId來唯一區(qū)分一個(gè)服務(wù)實(shí)例,服務(wù)實(shí)例信息是保存在哪里呢?其實(shí)就是一個(gè)Map中:
// 第一層的key是appName,第二層的key是instanceInfoIdprivate final ConcurrentHashMap>> registry = new ConcurrentHashMap>>();服務(wù)注冊(cè)
Service Provider啟動(dòng)時(shí)會(huì)將服務(wù)信息(InstanceInfo)發(fā)送給eureka server,eureka server接收到之后會(huì)寫入registry中,服務(wù)注冊(cè)默認(rèn)過期時(shí)間DEFAULT_DURATION_IN_SECS = 90秒。InstanceInfo寫入到本地registry之后,然后同步給其他peer節(jié)點(diǎn),對(duì)應(yīng)方法com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#replicateToPeers。
寫入本地registry
服務(wù)信息(InstanceInfo)保存在Lease中,寫入本地registry對(duì)應(yīng)方法com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#register,Lease統(tǒng)一保存在內(nèi)存的ConcurrentHashMap中,在服務(wù)注冊(cè)過程中,首先加個(gè)讀鎖,然后從registry中判斷該Lease是否已存在,如果已存在則比較lastDirtyTimestamp時(shí)間戳,取二者最大的服務(wù)信息,避免發(fā)生數(shù)據(jù)覆蓋。使用InstanceInfo創(chuàng)建一個(gè)新的InstanceInfo:
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) { // 已存在Lease則比較時(shí)間戳,取二者最大值 registrant = existingLease.getHolder();}Lease lease = new Lease(registrant, leaseDuration);if (existingLease != null) { // 已存在Lease則取上次up時(shí)間戳 lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());}?public Lease(T r, int durationInSecs) { holder = r; registrationTimestamp = System.currentTimeMillis(); // 當(dāng)前時(shí)間 lastUpdateTimestamp = registrationTimestamp; duration = (durationInSecs * 1000);}不知道小伙伴看了上述方法的代碼有沒有這樣的疑問?
通過讀鎖并且 registry 的讀取和寫入不是原子的,那么在并發(fā)時(shí)其實(shí)是有可能發(fā)生數(shù)據(jù)覆蓋的,如果發(fā)生數(shù)據(jù)覆蓋豈不是有問題了!猛一看會(huì)以為臟數(shù)據(jù)不就是有問題么?換個(gè)角度想,臟數(shù)據(jù)就一定有問題么?
其實(shí)針對(duì)這個(gè)問題,eureka的處理方式是沒有問題的,該方法并發(fā)時(shí),針對(duì)InstanceInfo Lease的構(gòu)造,二者的信息是基本一致的,因?yàn)閞egistrationTimestamp取的就是當(dāng)前時(shí)間,所以并并發(fā)的數(shù)據(jù)不會(huì)產(chǎn)生問題。
同步給其他peer
InstanceInfo寫入到本地registry之后,然后同步給其他peer節(jié)點(diǎn),對(duì)應(yīng)方法com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#replicateToPeers。如果當(dāng)前節(jié)點(diǎn)接收到的InstanceInfo本身就是另一個(gè)節(jié)點(diǎn)同步來的,則不會(huì)繼續(xù)同步給其他節(jié)點(diǎn),避免形成“廣播效應(yīng)”;InstanceInfo同步時(shí)會(huì)排除當(dāng)前節(jié)點(diǎn)。
InstanceInfo的狀態(tài)有依以下幾種:Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride,默認(rèn)情況下同步操作時(shí)批量異步執(zhí)行的,同步請(qǐng)求首先緩存到Map中,key為requestType+appName+id,然后由發(fā)送線程將請(qǐng)求發(fā)送到peer節(jié)點(diǎn)。
Peer之間的狀態(tài)是采用異步的方式同步的,所以不保證節(jié)點(diǎn)間的狀態(tài)一定是一致的,不過基本能保證最終狀態(tài)是一致的。結(jié)合服務(wù)發(fā)現(xiàn)的場(chǎng)景,實(shí)際上也并不需要節(jié)點(diǎn)間的狀態(tài)強(qiáng)一致。在一段時(shí)間內(nèi)(比如30秒),節(jié)點(diǎn)A比節(jié)點(diǎn)B多一個(gè)服務(wù)實(shí)例或少一個(gè)服務(wù)實(shí)例,在業(yè)務(wù)上也是完全可以接受的(Service Consumer側(cè)一般也會(huì)實(shí)現(xiàn)錯(cuò)誤重試和負(fù)載均衡機(jī)制)。所以按照CAP理論,Eureka的選擇就是放棄C,選擇AP。
如果同步過程中,出現(xiàn)了異常怎么辦呢,這時(shí)會(huì)根據(jù)異常信息做對(duì)應(yīng)的處理,如果是讀取超時(shí)或者網(wǎng)絡(luò)連接異常,則稍后重試;如果其他異常則打印錯(cuò)誤日志不再后續(xù)處理。
服務(wù)續(xù)約
Renew(服務(wù)續(xù)約)操作由Service Provider定期調(diào)用,類似于heartbeat。主要是用來告訴Eureka Server Service Provider還活著,避免服務(wù)被剔除掉。renew接口實(shí)現(xiàn)方式和register基本一致:首先更新自身狀態(tài),再同步到其它Peer,服務(wù)續(xù)約也就是把過期時(shí)間設(shè)置為當(dāng)前時(shí)間加上duration的值。
注意:服務(wù)注冊(cè)如果InstanceInfo不存在則加入,存在則更新;而服務(wù)預(yù)約只是進(jìn)行更新,如果InstanceInfo不存在直接返回false。
服務(wù)下線
Cancel(服務(wù)下線)一般在Service Provider shutdown的時(shí)候調(diào)用,用來把自身的服務(wù)從Eureka Server中刪除,以防客戶端調(diào)用不存在的服務(wù),eureka從本地”刪除“(設(shè)置為刪除狀態(tài))之后會(huì)同步給其他peer,對(duì)應(yīng)方法com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#cancel。
服務(wù)失效剔除
Eureka Server中有一個(gè)EvictionTask,用于檢查服務(wù)是否失效。Eviction(失效服務(wù)剔除)用來定期(默認(rèn)為每60秒)在Eureka Server檢測(cè)失效的服務(wù),檢測(cè)標(biāo)準(zhǔn)就是超過一定時(shí)間沒有Renew的服務(wù)。默認(rèn)失效時(shí)間為90秒,也就是如果有服務(wù)超過90秒沒有向Eureka Server發(fā)起Renew請(qǐng)求的話,就會(huì)被當(dāng)做失效服務(wù)剔除掉。失效時(shí)間可以通過eureka.instance.leaseExpirationDurationInSeconds進(jìn)行配置,定期掃描時(shí)間可以通過eureka.server.evictionIntervalTimerInMs進(jìn)行配置。
服務(wù)剔除#evict方法中有很多限制,都是為了保證Eureka Server的可用性:比如自我保護(hù)時(shí)期不能進(jìn)行服務(wù)剔除操作、過期操作是分批進(jìn)行、服務(wù)剔除是隨機(jī)逐個(gè)剔除,剔除均勻分布在所有應(yīng)用中,防止在同一時(shí)間內(nèi)同一服務(wù)集群中的服務(wù)全部過期被剔除,以致大量剔除發(fā)生時(shí),在未進(jìn)行自我保護(hù)前促使了程序的崩潰。
eureka server/client流程
服務(wù)信息拉取
Eureka consumer服務(wù)信息的拉取分為全量式拉取和增量式拉取,eureka consumer啟動(dòng)時(shí)進(jìn)行全量拉取,運(yùn)行過程中由定時(shí)任務(wù)進(jìn)行增量式拉取,如果網(wǎng)絡(luò)出現(xiàn)異常,可能導(dǎo)致先拉取的數(shù)據(jù)被舊數(shù)據(jù)覆蓋(比如上一次拉取線程獲取結(jié)果較慢,數(shù)據(jù)已更新情況下使用返回結(jié)果再次更新,導(dǎo)致數(shù)據(jù)版本落后),產(chǎn)生臟數(shù)據(jù)。對(duì)此,eureka通過類型AtomicLong的fetchRegistryGeneration對(duì)數(shù)據(jù)版本進(jìn)行跟蹤,版本不一致則表示此次拉取到的數(shù)據(jù)已過期。
fetchRegistryGeneration過程是在拉取數(shù)據(jù)之前,執(zhí)行fetchRegistryGeneration.get獲取當(dāng)前版本號(hào),獲取到數(shù)據(jù)之后,通過fetchRegistryGeneration.compareAndSet來判斷當(dāng)前版本號(hào)是否已更新。
注意:如果增量式更新出現(xiàn)意外,會(huì)再次進(jìn)行一次全量拉取更新。
Eureka server的伸縮容
Eureka Server是怎么知道有多少Peer的呢?Eureka Server在啟動(dòng)后會(huì)調(diào)用EurekaClientConfig.getEurekaServerServiceUrls來獲取所有的Peer節(jié)點(diǎn),并且會(huì)定期更新。定期更新頻率可以通過eureka.server.peerEurekaNodesUpdateIntervalMs配置。
這個(gè)方法的默認(rèn)實(shí)現(xiàn)是從配置文件讀取,所以如果Eureka Server節(jié)點(diǎn)相對(duì)固定的話,可以通過在配置文件中配置來實(shí)現(xiàn)。如果希望能更靈活的控制Eureka Server節(jié)點(diǎn),比如動(dòng)態(tài)擴(kuò)容/縮容,那么可以override getEurekaServerServiceUrls方法,提供自己的實(shí)現(xiàn),比如我們的項(xiàng)目中會(huì)通過數(shù)據(jù)庫讀取Eureka Server列表。
eureka server啟動(dòng)時(shí)把自己當(dāng)做是Service Consumer從其它Peer Eureka獲取所有服務(wù)的注冊(cè)信息。然后對(duì)每個(gè)服務(wù)信息,在自己這里執(zhí)行Register,isReplication=true,從而完成初始化。
Service Provider
Service Provider啟動(dòng)時(shí)首先時(shí)注冊(cè)到Eureka Service上,這樣其他消費(fèi)者才能進(jìn)行服務(wù)調(diào)用,除了在啟動(dòng)時(shí)之外,只要實(shí)例狀態(tài)信息有變化,也會(huì)注冊(cè)到Eureka Service。需要注意的是,需要確保配置eureka.client.registerWithEureka=true。register邏輯在方法AbstractJerseyEurekaHttpClient.register中,Service Provider會(huì)依次注冊(cè)到配置的Eureka Server Url上,如果注冊(cè)出現(xiàn)異常,則會(huì)繼續(xù)注冊(cè)其他的url。
Renew操作會(huì)在Service Provider端定期發(fā)起,用來通知Eureka Server自己還活著。 這里instance.leaseRenewalIntervalInSeconds屬性表示Renew頻率。默認(rèn)是30秒,也就是每30秒會(huì)向Eureka Server發(fā)起Renew操作。這部分邏輯在HeartbeatThread類中。在Service Provider服務(wù)shutdown的時(shí)候,需要及時(shí)通知Eureka Server把自己剔除,從而避免客戶端調(diào)用已經(jīng)下線的服務(wù),邏輯本身比較簡單,通過對(duì)方法標(biāo)記@PreDestroy,從而在服務(wù)shutdown的時(shí)候會(huì)被觸發(fā)。
Service Consumer
Service Consumer這塊的實(shí)現(xiàn)相對(duì)就簡單一些,因?yàn)樗簧婕暗綇腅ureka Server獲取服務(wù)列表和更新服務(wù)列表。Service Consumer在啟動(dòng)時(shí)會(huì)從Eureka Server獲取所有服務(wù)列表,并在本地緩存。需要注意的是,需要確保配置eureka.client.shouldFetchRegistry=true。由于在本地有一份Service Registries緩存,所以需要定期更新,定期更新頻率可以通過eureka.client.registryFetchIntervalSeconds配置。
小結(jié)
為什么要用eureka呢,因?yàn)榉植际介_發(fā)架構(gòu)中,任何單點(diǎn)的服務(wù)都不能保證不會(huì)中斷,因此需要服務(wù)發(fā)現(xiàn)機(jī)制,某個(gè)節(jié)點(diǎn)中斷后,服務(wù)消費(fèi)者能及時(shí)感知到保證服務(wù)高可用。從eureka的設(shè)計(jì)與實(shí)現(xiàn)上來說還是容易理解的,SpringCloud將它集成在自己的子項(xiàng)目spring-cloud-netflix中,實(shí)現(xiàn)SpringCloud的服務(wù)發(fā)現(xiàn)功能。
注冊(cè)中心除了用eureka之外,還有zookeeper、consul、nacos等解決方案,他們實(shí)現(xiàn)原理不同,各自適用于不同的場(chǎng)景,可按需使用。
Eureka比ZooKeeper相比優(yōu)勢(shì)是什么
Zookeeper保證CP 當(dāng)向注冊(cè)中心查詢服務(wù)列表時(shí),我們可以容忍注冊(cè)中心返回的是幾分鐘以前的注冊(cè)信息,但不能接受服務(wù)直接down掉不可用。也就是說,服務(wù)注冊(cè)功能對(duì)可用性的要求要高于一致性。但是zk會(huì)出現(xiàn)這樣一種情況,當(dāng)master節(jié)點(diǎn)因?yàn)榫W(wǎng)絡(luò)故障與其他節(jié)點(diǎn)失去聯(lián)系時(shí),剩余節(jié)點(diǎn)會(huì)重新進(jìn)行l(wèi)eader選舉。問題在于,選舉leader的時(shí)間太長,30 ~ 120s, 且選舉期間整個(gè)zk集群都是不可用的,這就導(dǎo)致在選舉期間注冊(cè)服務(wù)癱瘓。在云部署的環(huán)境下,因網(wǎng)絡(luò)問題使得zk集群失去master節(jié)點(diǎn)是較大概率會(huì)發(fā)生的事,雖然服務(wù)能夠最終恢復(fù),但是漫長的選舉時(shí)間導(dǎo)致的注冊(cè)長期不可用是不能容忍的。
Eureka保證AP Eureka看明白了這一點(diǎn),因此在設(shè)計(jì)時(shí)就優(yōu)先保證可用性。Eureka各個(gè)節(jié)點(diǎn)都是平等的,幾個(gè)節(jié)點(diǎn)掛掉不會(huì)影響正常節(jié)點(diǎn)的工作,剩余的節(jié)點(diǎn)依然可以提供注冊(cè)和查詢服務(wù)。而Eureka的客戶端在向某個(gè)Eureka注冊(cè)或時(shí)如果發(fā)現(xiàn)連接失敗,則會(huì)自動(dòng)切換至其它節(jié)點(diǎn),只要有一臺(tái)Eureka還在,就能保證注冊(cè)服務(wù)可用(保證可用性),只不過查到的信息可能不是最新的(不保證強(qiáng)一致性)。除此之外,Eureka還有一種自我保護(hù)機(jī)制,如果在15分鐘內(nèi)超過85%的節(jié)點(diǎn)都沒有正常的心跳,那么Eureka就認(rèn)為客戶端與注冊(cè)中心出現(xiàn)了網(wǎng)絡(luò)故障。
eureka有哪些不足: eureka consumer本身有緩存,服務(wù)狀態(tài)更新滯后,最常見的狀況就是,服務(wù)下線了但是服務(wù)消費(fèi)者還未及時(shí)感知,此時(shí)調(diào)用到已下線服務(wù)會(huì)導(dǎo)致請(qǐng)求失敗,只能依靠consumer端的容錯(cuò)機(jī)制來保證。
總結(jié)
以上是生活随笔為你收集整理的eureka同步原理_eureka原理剖析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 广州的11个辖区_广州上半年经济发展情况
- 下一篇: 仿苹果手机闹钟_原来iPhone自带的闹