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