3.Spring Cloud 之服務(wù)治理 - 搭建高可用的Eureka服務(wù)注冊(cè)中心

概述

著名的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)以下幾種情況:

  1. Eureka不再?gòu)淖?cè)列表中移除因?yàn)殚L(zhǎng)時(shí)間沒收到心跳而應(yīng)該過期的服務(wù)
  2. Eureka仍然能夠接受新服務(wù)的注冊(cè)和查詢請(qǐng)求,但是不會(huì)被同步到其它節(jié)點(diǎn)上(即保證當(dāng)前節(jié)點(diǎn)依然可用)
  3. 當(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è)appNameInstanceId不能相同。

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í),clientEureka 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 cloudorg.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ū)分

** 參考 **

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容