概述
著名的CAP理論指出,一個分布式系統(tǒng)不可能同時滿足C(一致性)、A(可用性)和P(分區(qū)容錯性)。由于分區(qū)容錯性在是分布式系統(tǒng)中必須要保證的,因此我們只能在A和C之間進行權(quán)衡。
Eureka Server提供服務(wù)注冊服務(wù),各個節(jié)點啟動后,會在Eureka Server中進行注冊,這樣Eureka Server中的服務(wù)注冊表中將會存儲所有可用服務(wù)節(jié)點的信息,服務(wù)節(jié)點的信息可以在界面中直觀的看到。
Eureka看明白了這一點,因此在設(shè)計時就優(yōu)先保證可用性。在此Zookeeper保證的是CP, 而Eureka則是AP。Eureka各個節(jié)點都是平等的,幾個節(jié)點掛掉不會影響正常節(jié)點的工作,剩余的節(jié)點依然可以提供注冊和查詢服務(wù)。
在應(yīng)用啟動后,將會向Eureka Server發(fā)送心跳,默認周期為30秒,如果Eureka Server在多個心跳周期內(nèi)沒有接收到某個節(jié)點的心跳,Eureka Server將會從服務(wù)注冊表中把這個服務(wù)節(jié)點移除(默認90秒)。
其次,Eureka Client對已經(jīng)獲取到的注冊信息也做了30s緩存。即服務(wù)通過eureka客戶端第一次查詢到可用服務(wù)地址后會將結(jié)果緩存,下次再調(diào)用時就不會真正向Eureka發(fā)起HTTP請求了。
再次, 負載均衡組件Ribbon也有30s緩存。**Ribbon會從上面提到的Eureka Client獲取服務(wù)列表,然后將結(jié)果緩存30s。
最后,如果你并不是在Spring Cloud環(huán)境下使用這些組件(Eureka, Ribbon),你的服務(wù)啟動后并不會馬上向Eureka注冊,而是需要等到第一次發(fā)送心跳請求時才會注冊。心跳請求的發(fā)送間隔也是30s。(Spring Cloud對此做了修改,服務(wù)啟動后會馬上注冊)
Eureka Server之間通過復(fù)制的方式完成數(shù)據(jù)的同步,Eureka還提供了客戶端緩存機制,即使所有的Eureka Server都掛掉,客戶端依然可以利用緩存中的信息消費其他服務(wù)的API。
以上這幾個30秒正是官方wiki上寫服務(wù)注冊最長需要2分鐘的原因。Eureka通過心跳檢查、客戶端緩存等機制,確保了系統(tǒng)的高可用性、靈活性和可伸縮性。
而Eureka的客戶端在向某個Eureka注冊或時如果發(fā)現(xiàn)連接失敗,則會自動切換至其它節(jié)點,只要有一臺Eureka還在,就能保證注冊服務(wù)可用(保證可用性),只不過查到的信息可能不是最新的(不保證強一致性)。
除此之外,Eureka還有一種自我保護機制,如果在15分鐘內(nèi)超過85%的節(jié)點都沒有正常的心跳,那么Eureka就認為客戶端與注冊中心出現(xiàn)了網(wǎng)絡(luò)故障,此時會出現(xiàn)以下幾種情況:
-
Eureka不再從注冊列表中移除因為長時間沒收到心跳而應(yīng)該過期的服務(wù) -
Eureka仍然能夠接受新服務(wù)的注冊和查詢請求,但是不會被同步到其它節(jié)點上(即保證當前節(jié)點依然可用) - 當網(wǎng)絡(luò)穩(wěn)定時,當前實例新的注冊信息會被同步到其它節(jié)點中
因此, Eureka可以很好的應(yīng)對因網(wǎng)絡(luò)故障導(dǎo)致部分節(jié)點失去聯(lián)系的情況,而不會像Zookeeper那樣使整個注冊服務(wù)癱瘓。
創(chuàng)建服務(wù)
創(chuàng)建一個Eureka服務(wù)中心十分簡單,只需要引入相關(guān)依賴,添加注解,稍作配置即可。
1.引入依賴
compile "org.springframework.cloud:spring-cloud-starter-eureka-server:${cloud_eureka}"
2.添加注解
@EnableEurekaServer
3.稍作配置
eureka:
instance:
#配置主機名
hostname: eureka.alpha
appname: reka-alpha
client:
# 、在默認設(shè)置下,Eureka服務(wù)注冊中心也會將自己作為客戶端來嘗試注冊它自己,
# 所以我們需要禁用它的客戶端注冊行為。
# 因為當注冊中心將自己作為客戶端注冊的時候,發(fā)現(xiàn)在server上的端口被自己占據(jù)了,然后就掛了
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka.alpha:9871/eureka
這里訪問的是http://eureka.alpha,需要修改hosts文件將eureka.alpha指向127.0.0.1,如果不做指向,這里也可以使用localhost訪問,當然,為了后面的高可用配置建議這里做指向.
4.啟動服務(wù),大功告成
高可用配置
主要是利用多個eureka相互注冊,下例用采用三臺服務(wù)器做集群部署,其中每臺eureka服務(wù)器向另外兩臺注冊自己的實例.
這里需要注意如下幾點
1.Eureka Server的同步遵循著一個非常簡單的原則:只要有一條邊將節(jié)點連接,就可以進行信息傳播與同步
2.如果Eureka A的peer指向了B, B的peer指向了C,那么當服務(wù)向A注冊時,B中會有該服務(wù)的注冊信息,但是C中沒有。也就是說,如果你希望只要向一臺Eureka注冊其它所有實例都能得到注冊信息,那么就必須把其它所有節(jié)點都配置到當前Eureka的peer屬性中。這一邏輯是在PeerAwareInstanceRegistryImpl#replicateToPeers()方法中實現(xiàn)的:
示例代碼中拆分了三個配置,可以根據(jù)這三個配置分別打成jar包運行即可,當然,這里建議配合docker-compose進行編排部署.
1.在Eureka中,一個instance通過一個eureka.instance.instanceId 來唯一標識,如果這個值沒有設(shè)置,就采用eureka.instance.metadataMap.instanceId來代替。instance之間通過eureka.instance.appName 來彼此訪問,在spring cloud中默認值是spring.application.name,如果沒有設(shè)置則為UNKNOWN。在實際使用中spring.application.name不可或缺,因為相同名字的應(yīng)用會被Eureka合并成一個群集。eureka.instance.instanceId也可以不設(shè)置,直接使用缺省值(client.hostname:application.name:port)
,同一個appName下InstanceId不能相同。
2.如果 eureka.client.registerWithEureka設(shè)置成true(默認值true),應(yīng)用啟動時,會利用指定的eureka.client.serviceUrl.defaultZone注冊到對應(yīng)的Eureka server中。之后每隔30s(通過eureka.instance.leaseRenewalIntervalInSeconds來配置)向Eureka server發(fā)送一次心跳,如果Eureka server在90s(通過eureka.instance.leaseExpirationDurationInSeconds配置)內(nèi)沒有收到某個instance發(fā)來的心跳就會把這個instance從注冊中心中移走。發(fā)送心跳的操作是一個異步任務(wù),如果發(fā)送失敗,則以2的指數(shù)形式延長重試的時間,直到達到eureka.instance.leaseRenewalIntervalInSeconds * eureka.client.heartbeatExecutorExponentialBackOffBound這個上限,之后一直以這個上限值作為重試間隔,直至重新連接到Eureka server,并且重新嘗試連接到Eureka server的次數(shù)是不受限制的。
3.在Eureka server中每一個instance都由一個包含大量這個instance信息的com.netflix.appinfo.InstanceInfo標識,client向Eureka server發(fā)送心跳和更新注冊信息是不相同的,InstanceInfo也以固定的頻率發(fā)送到Eureka server,這些信息在Eureka client啟動后的40s(通過eureka.client.initialInstanceInfoReplicationIntervalSeconds配置)首次發(fā)送,之后每隔30s(通過eureka.client.instanceInfoReplicationIntervalSeconds配置)發(fā)送一次。
4.如果eureka.client.fetchRegistry設(shè)置成true(默認值true),Eureka client在啟動時會從Eureka server獲取注冊信息并緩存到本地,之后只會增量獲取信息(可以把eureka.client.shouldDisableDelta設(shè)置成false來強制每次都全量獲?。+@取注冊信息的操作也是一個異步任務(wù),每隔30秒執(zhí)行一次(通過eureka.client.registryFetchIntervalSeconds配置),如果操作失敗,也是以2的指數(shù)形式延長重試時間,直到達到eureka.client.registryFetchIntervalSeconds * eureka.client.cacheRefreshExecutorExponentialBackOffBound 這個上限,之后一直以這個上限值作為重試間隔,直至重新獲取到注冊信息,并且重新嘗試獲取注冊信息的次數(shù)是不受限制的。
這些任務(wù)都是在com.netflix.discovery.DiscoveryClient中啟動,spring cloud用org.springframework.cloud.netflix.eureka.CloudEurekaClient對這個類進行了擴展。
[常見問題]
1.自我保護模式
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
默認情況下,如果Eureka Server在一定時間內(nèi)沒有接收到某個微服務(wù)實例的心跳,Eureka Server將會注銷該實例(默認90秒)。但是當網(wǎng)絡(luò)分區(qū)故障發(fā)生時,微服務(wù)與Eureka Server之間無法正常通信,以上行為可能變得非常危險了——因為微服務(wù)本身其實是健康的,此時本不應(yīng)該注銷這個微服務(wù)。
Eureka通過“自我保護模式”來解決這個問題——當Eureka Server節(jié)點在短時間內(nèi)丟失過多客戶端時(可能發(fā)生了網(wǎng)絡(luò)分區(qū)故障),那么這個節(jié)點就會進入自我保護模式。一旦進入該模式,Eureka Server就會保護服務(wù)注冊表中的信息,不再刪除服務(wù)注冊表中的數(shù)據(jù)(也就是不會注銷任何微服務(wù))。當網(wǎng)絡(luò)故障恢復(fù)后,該Eureka Server節(jié)點會自動退出自我保護模式。
綜上,自我保護模式是一種應(yīng)對網(wǎng)絡(luò)異常的安全保護措施。它的架構(gòu)哲學是寧可同時保留所有微服務(wù)(健康的微服務(wù)和不健康的微服務(wù)都會保留),也不盲目注銷任何健康的微服務(wù)。使用自我保護模式,可以讓Eureka集群更加的健壯、穩(wěn)定。
在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保護模式。
2.unavailable-replicas
在配置文件中如果不使用域名的方式,而指定localhost或者ip(127.0.0.1/外網(wǎng)ip),服務(wù)能夠正常啟動,但分片服務(wù)總顯示在unavailable-replicas中,因此在host中指定了相應(yīng)的域名做服務(wù)區(qū)分
** 參考 **