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