柚子快報(bào)邀請(qǐng)碼778899分享:云原生 Eureka處理流程
柚子快報(bào)邀請(qǐng)碼778899分享:云原生 Eureka處理流程
1、Eureka Server服務(wù)端會(huì)做什么
1、服務(wù)注冊(cè) Client服務(wù)提供者可以向Server注冊(cè)服務(wù),并且內(nèi)部有二層緩存機(jī)制來(lái)維護(hù)整個(gè)注冊(cè)表,注冊(cè)表是Eureka Client的服務(wù)提供者注冊(cè)進(jìn)來(lái)的。 2、提供注冊(cè)表 服務(wù)消費(fèi)者用來(lái)獲取注冊(cè)表 3、同步狀態(tài) 通過(guò)注冊(cè)、心跳機(jī)制和 Eureka Server同步當(dāng)前客戶端的狀態(tài),這里就包括服務(wù)提供者和服務(wù)消費(fèi)者。
2、Eureka Server的問(wèn)題
問(wèn)題: 1、Eureka Server的自我保護(hù)機(jī)制是怎么實(shí)現(xiàn),怎么做到15分鐘內(nèi),服務(wù)心跳失敗比例高于85%。
2、自我保護(hù)機(jī)制觸發(fā)后,有哪些功能會(huì)被開(kāi)啟
1、不再?gòu)淖?cè)列表中移除因?yàn)殚L(zhǎng)時(shí)間沒(méi)收到心跳而應(yīng)該過(guò)期的服務(wù),冷卻時(shí)間是多久? 2、仍然能夠接受新服務(wù)的注冊(cè)和查詢注冊(cè)表請(qǐng)求,但是不會(huì)被同步到其它節(jié)點(diǎn)上 3、當(dāng)網(wǎng)絡(luò)穩(wěn)定時(shí),當(dāng)前實(shí)例新的注冊(cè)信息會(huì)被同步到其它節(jié)點(diǎn)中,怎么判斷網(wǎng)絡(luò)恢復(fù)
集群?jiǎn)栴}:
Eureka Server集群中,Eureka Server節(jié)點(diǎn)A和Eureka Server節(jié)點(diǎn)B是怎么通過(guò)P2P的方式完成服務(wù)注冊(cè)表的同步?
Eureka Server集群中,同一個(gè)區(qū)域的Eureka Client,怎么做到優(yōu)先和同區(qū)域內(nèi)的Eureka Server進(jìn)行通信的?
Eureka Client服務(wù)提供者,向Eureka Server注冊(cè),如果某個(gè)節(jié)點(diǎn)失敗,自動(dòng)切換到其他節(jié)點(diǎn),是怎么做到的?
Eureka Server什么時(shí)候會(huì)自動(dòng)退出自我保護(hù)模式?
二、源碼概述
1、EurekaServer啟動(dòng)
@EnableEurekaServer->
import(EurekaServerMarkerConfiguration)->
注冊(cè)Bean(EurekaServerMarkerConfiguration.Marker)->
Marker激活了EurekaServerAutoConfiguration這個(gè)配置類(lèi)
2、EurekaServerAutoConfiguration主要包含以下內(nèi)容
1、創(chuàng)建Bean:【EurekaServerConfigBean】是一個(gè)配置類(lèi),EurekaServer的所有配置項(xiàng)都是EurekaServerConfigBean這個(gè)類(lèi)里面。 2、創(chuàng)建Bean:【EurekaController】也就是我們通過(guò)url,可以訪問(wèn)EurekaServer后臺(tái)。 3、創(chuàng)建Bean:【PeerAwareInstanceRegistry】處理注冊(cè)表的類(lèi),這個(gè)類(lèi)也會(huì)發(fā)布事件,發(fā)布了注冊(cè)事件和取消事件(默認(rèn)沒(méi)有監(jiān)聽(tīng)者需要自己實(shí)現(xiàn)) 4、創(chuàng)建Bean:【PeerEurekaNodes】初始化了集群節(jié)點(diǎn)集合 5、創(chuàng)建Bean:【EurekaServerContext】專(zhuān)業(yè)名稱叫【EurekaServer上下文】,這個(gè)Bean的生成是基于上面創(chuàng)建的Bean: eureka server配置,注冊(cè)表,集群節(jié)點(diǎn)集合來(lái)生成。而EurekaServerContext的作用就是初始化eurekaServer上下文,里面會(huì)做很多事情。 6、創(chuàng)建Bean:【EurekaServerBootstrap】Eureka Server的啟動(dòng)類(lèi) 7、創(chuàng)建Bean:【FilterRegistrationBean】主要是對(duì)Jersey過(guò)濾器的包裝,那么這個(gè)過(guò)濾器干嘛用的 ?
到此EurekaServerAutoConfiguration的創(chuàng)建Bean的任務(wù)完成了,但是EurekaServerAutoConfiguration里面還有一@Import(EurekaServerInitializerConfiguration)注解
3、EurekaServerInitializerConfiguration
EurekaServerInitializerConfiguration里面有個(gè)start方法,里面會(huì)拿到上面注冊(cè)Bean:【EurekaServerBootstrap啟動(dòng)類(lèi)】,來(lái)啟動(dòng)Eureak。 然后在通過(guò)生成的【EurekaServer上下文】開(kāi)始初始化,初始化的時(shí)候會(huì)調(diào)用registry.syncUp方法,從相鄰的eureka節(jié)點(diǎn)復(fù)制注冊(cè)表,通過(guò)http調(diào)用相鄰節(jié)點(diǎn)獲取所有服務(wù)實(shí)例。
在通過(guò)上面的【PeerAwareInstanceRegistry】把實(shí)例注冊(cè)到本地,這里的實(shí)例是指EurekaClient的服務(wù)提供者,同時(shí)PeerAwareInstanceRegistry里面還有一個(gè)【Timer】,這個(gè)是定時(shí)任務(wù),清理30s沒(méi)有續(xù)約的任務(wù)、服務(wù)剔除超過(guò)90s沒(méi)過(guò)來(lái)續(xù)約的服務(wù)。
原文地址:跳轉(zhuǎn)
三、下面通過(guò)代碼原理說(shuō)下實(shí)現(xiàn)邏輯
1、服務(wù)注冊(cè) 2、服務(wù)續(xù)約 3、服務(wù)剔除 4、服務(wù)下線 5、服務(wù)發(fā)現(xiàn) 6、集群信息同步
1、服務(wù)注冊(cè)
流程:服務(wù)提供者,請(qǐng)求EurekaServer端某個(gè)節(jié)點(diǎn)注冊(cè)服務(wù)
EurekaClient端
Bean =【EurekaAutoServiceRegistration】
通過(guò)com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register向EurekaServer注冊(cè)服務(wù)。
EurekaServer端
EurekaServer收到請(qǐng)求,請(qǐng)求進(jìn)到ApplicationResource#addInstance方法,里面調(diào)用【Bean=PeerAwareInstanceRegistry】#register方法,
里面先發(fā)布一個(gè)Event事件,但是Spring沒(méi)有監(jiān)聽(tīng)這個(gè)事件,這個(gè)是留給我們自己拓展用的,在register里面還有一個(gè)很重要的事做,就是注冊(cè):
1、首先是設(shè)置服務(wù)的過(guò)期時(shí)間90s
2、調(diào)用父類(lèi)完成服務(wù)注冊(cè),
3、在完成集群信息同步,同步給其他節(jié)點(diǎn)。
重點(diǎn)看調(diào)用【父類(lèi)完成注冊(cè)】,先從注冊(cè)表的集合中獲取服務(wù)注冊(cè)信息:
1、如果注冊(cè)表存在,那么說(shuō)明沖突了,就判斷哪2個(gè)節(jié)點(diǎn)的活躍時(shí)間比較靠前,保留節(jié)點(diǎn)時(shí)間最新的節(jié)點(diǎn)
2、如果不存在就新建,將EurekaClient的服務(wù)提供者封裝成InstanceInfo對(duì)象, InstanceInfo存放了注冊(cè)信息,最后操作時(shí)間,注冊(cè)時(shí)間,過(guò)期時(shí)間,
剔除時(shí)間等信息,再把這個(gè)InstanceInfo對(duì)象存到注冊(cè)表中去。至此一個(gè)服務(wù)注冊(cè)的流程就完成了
注意:注冊(cè)表是一個(gè)Map
1、最外層的key是AppName,注冊(cè)進(jìn)來(lái)的服務(wù)的服務(wù)名 , value = Map
2、Map
2、服務(wù)續(xù)約:
服務(wù)續(xù)約由Eureka-client端主動(dòng)發(fā)起請(qǐng)求Eureka服務(wù)端,間隔時(shí)間30s,Eureka服務(wù)端收到請(qǐng)求,刷新節(jié)點(diǎn)的活躍時(shí)間。因?yàn)镋ureka服務(wù)端,有定時(shí)任務(wù),就是基于這個(gè)活躍時(shí)間來(lái)考慮是否剔除服務(wù)。
Eureka-client端
請(qǐng)求Eureka服務(wù)端,由DiscoveryClient#renew方法完成,主要是發(fā)送http請(qǐng)求,每隔30秒進(jìn)行一次續(xù)約,
里面調(diào)用AbstractJerseyEurekaHttpClient#sendHeartBeat方法
Eureka-server端
在Eureka-server端服務(wù)續(xù)約的調(diào)用鏈與服務(wù)注冊(cè)基本相同
InstanceRegistry#renew() ->
PeerAwareInstanceRegistry#renew()->
AbstractInstanceRegistry # renew() 主要邏輯還是AbstractInstanceRegistry的renew方法
renew的方法邏輯操作非常簡(jiǎn)單,它的本質(zhì)就是修改服務(wù)的【最后更新時(shí)間】。將最后更新時(shí)間改為:【系統(tǒng)當(dāng)前時(shí)間】+【服務(wù)的過(guò)期時(shí)間】
3、服務(wù)剔除
服務(wù)主要是Eureka服務(wù)端,通過(guò)定時(shí)任務(wù)檢測(cè)注冊(cè)節(jié)點(diǎn)的【活躍時(shí)間】,如果超過(guò)90s就會(huì)剔除。
Eureka-server端
當(dāng)Eureka-server發(fā)現(xiàn)有的實(shí)例沒(méi)有續(xù)約超過(guò)一定時(shí)間,則將該服務(wù)從注冊(cè)列表剔除,該項(xiàng)工作由一個(gè)定時(shí)任務(wù)完成的。由下面方法完成
AbstractInstanceRegistry # postInit()
定時(shí)任務(wù)前面說(shuō)了在【EurekaServer上下文】初始化的時(shí)候,添加了一個(gè)Timer定時(shí)器,定時(shí)器關(guān)聯(lián)的任務(wù)是EvictionTask的run方法,在執(zhí)行任務(wù)中
調(diào)用剔除方法 evict(), 主要是拿到注冊(cè)表的所有實(shí)例挨個(gè)遍歷,判斷【系統(tǒng)當(dāng)前時(shí)間 > 最后更新時(shí)間+過(guò)期時(shí)間+預(yù)留時(shí)間】,
并且新建實(shí)例列表expiredLeases,用來(lái)存放過(guò)期的實(shí)例。
當(dāng)該條件成立時(shí),認(rèn)為服務(wù)過(guò)期(在Eureka中過(guò)期時(shí)間默認(rèn)定義為3個(gè)心跳的時(shí)間,一個(gè)心跳是30秒,因此過(guò)期時(shí)間是90秒)。
將該過(guò)期實(shí)例放入上面創(chuàng)建的expiredLeases列表中。注意這里僅僅是將實(shí)例放入List中,并沒(méi)有實(shí)際剔除。
因?yàn)橐袛嗍欠癯^(guò)閾值了,如果超過(guò)就從里面取隨機(jī)數(shù),隨機(jī)剔除實(shí)例ID,注意expiredLeases里面存的是多個(gè)服務(wù)的實(shí)
例,不是某一個(gè)服務(wù)的所有實(shí)例。下線的時(shí)候從里面取隨機(jī)數(shù),所以有可能某個(gè)服務(wù)的所有實(shí)例全部被剔除都有可能。
在實(shí)際剔除任務(wù)前,需要提一下eureka的自我保護(hù)機(jī)制:
當(dāng)1分鐘內(nèi),心跳失敗的服務(wù)大于一定比例時(shí),會(huì)觸發(fā)自我保護(hù)機(jī)制。這個(gè)值在Eureka中被定義為85%,一旦觸發(fā)自我保護(hù)機(jī)制,
Eureka會(huì)嘗試保護(hù)其服務(wù)注冊(cè)表中的信息,不再刪除服務(wù)注冊(cè)表中的數(shù)據(jù)。1分鐘是怎么統(tǒng)計(jì)數(shù)量的?哪些節(jié)點(diǎn)保留? 哪些節(jié)點(diǎn)刪除?
答:使用了隨機(jī)算法進(jìn)行剔除,
舉個(gè)例子,假如當(dāng)前共有100個(gè)服務(wù),那么剔除閾值為85%,也就是最多剔除15個(gè),如果list中有60個(gè)服務(wù),
那么就會(huì)從60個(gè)服務(wù)里面取15個(gè)。有可能一個(gè)服務(wù)的所有節(jié)點(diǎn)全部被剔除。剔除的節(jié)點(diǎn)被放到一個(gè)queue里面,
這個(gè)里面存的是最近剔除的節(jié)點(diǎn),在集群同步,或者拉取注冊(cè)表的時(shí)候,要用到。
關(guān)于自我保護(hù)
首先是閾值是85%,比如100個(gè),閾值是85%,那么一次最多剔除15個(gè),當(dāng)定時(shí)任務(wù)進(jìn)來(lái),發(fā)現(xiàn)100個(gè)里面有10個(gè)失效,那么10小于【最大閾值】,那就剔除10個(gè),如果20個(gè)失效,20個(gè)大于15,所以最多剔除15個(gè),這15個(gè)怎么選?
通過(guò)for循環(huán)15次,每次生成一個(gè)隨機(jī)數(shù),這個(gè)隨機(jī)數(shù)是從20里面取,
1、自我保護(hù)時(shí)期不能進(jìn)行服務(wù)剔除操作
2、過(guò)期操作是分批進(jìn)行
3、服務(wù)剔除是隨機(jī)逐個(gè)剔除,均勻分布在所有應(yīng)用中,其實(shí)也不算均勻,是隨機(jī)抽
4、服務(wù)剔除是一個(gè)定時(shí)任務(wù),默認(rèn)60秒一次
問(wèn)題:自我保護(hù)時(shí)期不能進(jìn)行服務(wù)剔除操作:這個(gè)是怎么做到的?
首先是定義了2個(gè)變量,一個(gè)是【期望續(xù)約數(shù)】,一個(gè)是【前一分鐘實(shí)際的續(xù)約數(shù)】。
這個(gè)【期望續(xù)約數(shù)】是通過(guò)公式算出來(lái)了,比如20個(gè)實(shí)例,正常情況下1分鐘的話會(huì)續(xù)約40次,
那么期望的續(xù)約數(shù)應(yīng)該是40*85%=34個(gè),而如果實(shí)際契約數(shù)超過(guò)這個(gè)數(shù)量,比如35,
那么EurekaServer認(rèn)為,服務(wù)恢復(fù)正常了,應(yīng)該關(guān)閉自我保護(hù)機(jī)制。
注意:期望續(xù)約數(shù)是一個(gè)動(dòng)態(tài)值,每次會(huì)重行計(jì)算的。比如服務(wù)下線或者上線,期望的數(shù)量是會(huì)加1或者減1的。
問(wèn)題:實(shí)際續(xù)約數(shù)是怎么算出來(lái)的?
因?yàn)樘蕹亩〞r(shí)任務(wù)是1分鐘一次,所以有個(gè)定時(shí)任務(wù)專(zhuān)門(mén)設(shè)置【前一分鐘實(shí)際續(xù)約數(shù)量】,MeasuredRate也是60秒一次,他里面定義了2個(gè)變量,一個(gè)是【一分鐘內(nèi)的續(xù)約】數(shù),一個(gè)是【上一分鐘的續(xù)約數(shù)】,服務(wù)每次注冊(cè)就會(huì)加1,服務(wù)下線就減1,當(dāng)定時(shí)任務(wù)跑的時(shí)候,就會(huì)把一分鐘的續(xù)約數(shù)賦值給【上一分鐘的續(xù)約數(shù)】,然后再把【一分鐘內(nèi)的續(xù)約】置0
自我保護(hù)機(jī)制,詳細(xì)解讀:跳轉(zhuǎn) 代碼流程:跳轉(zhuǎn)
4、服務(wù)下線
當(dāng)eureka-client關(guān)閉時(shí),不會(huì)立刻關(guān)閉,需要先發(fā)請(qǐng)求給eureka-server服務(wù)端,告知自己要下線了。
Eureka-client端:
Eureka客戶端請(qǐng)求EurekaServer服務(wù)端,通過(guò)DiscoveryClient#shutdown方法調(diào)用EurekaServer服務(wù)端
Eureka-server端
收到請(qǐng)求,進(jìn)到AbstractInstanceRegistry#cancel方法, 最終還是調(diào)用了和服務(wù)剔除中一樣的方法,remove掉了注冊(cè)表中的實(shí)例
5、服務(wù)發(fā)現(xiàn)
是指EurekaClient 消費(fèi)者,通過(guò)Http調(diào)用EurekaServer服務(wù)端接口,獲取注冊(cè)表信息
Eureka-client端:
DiscoveryClient#getInstances方法,可以根據(jù)服務(wù)id獲取服務(wù)實(shí)例列表。那么這里就有一個(gè)問(wèn)題了,我們還沒(méi)有去調(diào)用微服務(wù),那么服務(wù)列表是什么時(shí)候被拉取或緩存到本地的服務(wù)列表的呢?
EurekaDiscoveryClient # getInstances() ->
DiscoveryClient # getInstancesByVipAddress() ->
DiscoveryClient #getInstancesByVipAddress2() ->
Applications # getInstancesByVirtualHostName() 這里居然不是走的http,是讀的本地緩存。
Applications中的getInstancesByVirtualHostName方法里面,有一個(gè)virtualHostNameAppMap的Map集合中已經(jīng)保存了當(dāng)前所有注冊(cè)到eureka的服務(wù)列表。
private final Map
也就是說(shuō),在我們沒(méi)有手動(dòng)去調(diào)用服務(wù)的時(shí)候,該集合里面已經(jīng)有值了,說(shuō)明在Eureka-server項(xiàng)目啟動(dòng)后,會(huì)自動(dòng)去拉取服務(wù),并將拉取的服務(wù)緩存起來(lái)。
那么追根溯源,來(lái)查找一下服務(wù)的發(fā)現(xiàn)究竟是什么時(shí)候完成的?;氐紻iscoveryClient這個(gè)類(lèi),
在它的構(gòu)造方法中定義了任務(wù)調(diào)度線程池cacheRefreshExecutor,定義完成后,調(diào)用initScheduledTask方法,
通過(guò)fetchRegistry方法來(lái)拉取,不過(guò)分2種情況【增量拉取】還是【全量拉取】
【全量拉取】:當(dāng)緩存為null,或里面的數(shù)據(jù)為空,或強(qiáng)制時(shí),進(jìn)行全量拉取,執(zhí)行g(shù)etAndStoreFullRegistry方法
【增量拉取】: 只拉取修改的。執(zhí)行g(shù)etAndUpdateDelta方法,雖然這里是拉增量,但是如果沒(méi)拉到數(shù)據(jù),
還是會(huì)拉全量的數(shù)據(jù),然后就是更新操作,更新也有類(lèi)型,是delete還是Modify,added,這里有個(gè)細(xì)節(jié)校驗(yàn),
就是拿hashCode和緩存的HashCode對(duì)比是否一致,如果一致,說(shuō)明數(shù)據(jù)沒(méi)有變動(dòng),如果不一致,
那就說(shuō)明本地和遠(yuǎn)程數(shù)據(jù)不一樣,需要重新再拉一次,
對(duì)服務(wù)發(fā)現(xiàn)過(guò)程進(jìn)行一下重點(diǎn)總結(jié):
1、服務(wù)列表的拉取并不是在服務(wù)調(diào)用的時(shí)候才拉取,而是在項(xiàng)目啟動(dòng)的時(shí)候就有定時(shí)任務(wù)去拉取了,這點(diǎn)在DiscoveryClient的構(gòu)造方法中能夠體現(xiàn); 2、服務(wù)的實(shí)例并不是實(shí)時(shí)的Eureka-server中的數(shù)據(jù),而是一個(gè)本地緩存的數(shù)據(jù); 3、緩存更新根據(jù)實(shí)際需求分為全量拉取與增量拉取。
6、集群信息同步
Eureka-server端:
集群信息同步發(fā)生在Eureka-server之間,之前提到在PeerAwareInstanceRegistryImpl類(lèi)中,在執(zhí)行register方法注冊(cè)微服務(wù)實(shí)例完成后,
執(zhí)行了集群信息同步方法replicateToPeers
首先,遍歷集群節(jié)點(diǎn),用以給各個(gè)集群信息節(jié)點(diǎn)進(jìn)行信息同步。最終發(fā)送http請(qǐng)求,請(qǐng)求各個(gè)EureakServer節(jié)點(diǎn)。
調(diào)用EurekaServer的ApplicationResource類(lèi)里面的addInstance,注意EurekaClient注冊(cè)的時(shí)候,也是調(diào)的這個(gè)方法,
單獨(dú)注冊(cè)時(shí)isReplication的值為false,集群同步時(shí)為true
Eureka三級(jí)緩存
服務(wù)端的緩存機(jī)制
服務(wù)端采用三級(jí)緩存(registry,readWriteCacheMap,readOnlyCacheMap)來(lái)存儲(chǔ)注冊(cè)表信息。
三級(jí)緩存的目的是為了將注冊(cè)服務(wù)和獲取服務(wù)區(qū)分開(kāi),避免了高并發(fā)的同時(shí)對(duì)一個(gè)緩存的讀寫(xiě)操作,有效避免讀寫(xiě)沖突。保證性能。
一級(jí)緩存 = ConcurrentHashMap
設(shè)置緩存
(1)、客戶端將服務(wù)信息注冊(cè)在一級(jí)緩存registry中。(每30s一次心跳續(xù)約)
(2)、一級(jí)緩存registry收到注冊(cè)信息后,先清空二級(jí)緩存readWriteCacheMap中的注冊(cè)信息,然后在同步新數(shù)據(jù)給readWriteCacheMap二級(jí)緩存。
(3)、二級(jí)緩存按照30s一次的頻率給三級(jí)緩存readOnlyCacheMap同步數(shù)據(jù)
緩存獲取
(4)、其他的客戶端連接注冊(cè)中心Server 30s一次的頻率從三級(jí)緩存readOnlyCacheMap中獲取,如果readOnlyCacheMap中獲取不到,則直接去一級(jí)緩存registry中獲取。
緩存更新
(5)、一級(jí)緩存中默認(rèn)每隔60s檢查服務(wù)續(xù)期,如果90秒內(nèi)服務(wù)還沒(méi)有續(xù)期,則刪除注冊(cè)信息。同時(shí)同步給二級(jí)三級(jí)緩存。
(6)、服務(wù)下線時(shí),一級(jí)緩存registry中的注冊(cè)信息刪除,同時(shí)刪除二級(jí)緩存的數(shù)據(jù)。30s后二級(jí)同步三級(jí)緩存時(shí)發(fā)現(xiàn)二級(jí)緩存已失效,則刪除三級(jí)緩存的注冊(cè)表信息。則會(huì)期間會(huì)有時(shí)間的延遲。
(7)、二級(jí)緩存的默認(rèn)有效期是180s(3min),3min后數(shù)據(jù)會(huì)失效,然后二級(jí)緩存數(shù)據(jù)清空。
三級(jí)緩存的弊端:
三級(jí)緩存的問(wèn)題很明顯,就是服務(wù)下線之后,不能及時(shí)通知到三級(jí)緩存中,注冊(cè)信息的獲取者(客戶端)拿到的注冊(cè)信息不是實(shí)時(shí)的。(當(dāng)讓客戶端的獲取也不是實(shí)時(shí)的,要間隔30s才會(huì)去主動(dòng)獲?。?/p>
柚子快報(bào)邀請(qǐng)碼778899分享:云原生 Eureka處理流程
推薦文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。