在使用Eureka做注冊中心時,我平時遇到的最不爽問題,就是無法做到實(shí)時上下線。比如,我服務(wù)已經(jīng)正常下線了,為什么上游還能調(diào)通?我服務(wù)已經(jīng)上線了,為什么還有等"很久"才能真正被其他服務(wù)所"發(fā)現(xiàn)"?其實(shí)這些都是從Eureka到Client再到Ribbion這條鏈路中的逐級緩存造成的。
Eureka為什么要使用緩存
比如,對于Eureka來說,Eureka Client獲取注冊更新信息時,Eureka Server返回的數(shù)據(jù)其實(shí)是從一個默認(rèn)每30s才更新的緩存獲取的。也就是說,如果你服務(wù)下了一個,即使client是在服務(wù)下線之后發(fā)請求查詢的注冊列表,那這次請求也不會包括服務(wù)下線的信息。那么Eureka為什么要搞cache而不是實(shí)時的返回注冊信息呢?我個人推測了一下,應(yīng)該是為了在訪問注冊數(shù)據(jù)結(jié)構(gòu)時盡量少加鎖,從而提升單次請求時的性能。如果沒有這層cache, 那么對于服務(wù)注冊表這個數(shù)據(jù)結(jié)構(gòu)來說,就會存在并發(fā)讀寫的情況,那就避免不了加鎖保護(hù)。如果有cache, 是可以做到完全不加鎖的。想一下,對于微服務(wù)架構(gòu)來說,特點(diǎn)就是每個服務(wù)較輕,但服務(wù)數(shù)量較多。如果每個服務(wù)都定時請求eureka更新注冊信息的話,對于Eureka Server來說,確實(shí)是可能會造成嚴(yán)重的鎖競爭的。除了這個responseCache外,Eureka對于無心跳服務(wù)的清理也是默認(rèn)每60s執(zhí)行一次的,也就是說對于異常下線的服務(wù)(沒有主動取消注冊,而是中斷了心跳),在Eureka Server端最大會有長達(dá) 60s + 30s * 3 = 150s的延遲。這里的30s * 3 是3個心跳的周期。
Eureka Client的緩存
Eureka Client默認(rèn)會對注冊信息做30s緩存 ,這個很好理解 ,因?yàn)槲覀儺?dāng)然不希望每次RPC都要重新查一次注冊表,這是很浪費(fèi)的。?這是常規(guī)操作,無可厚非。
Ribbon的緩存
這就有點(diǎn)詭異了。Ribbon向上對接Eureka Client, 向下對接實(shí)現(xiàn)發(fā)起HTTP請求的客戶端。ribbon自身也是有30s緩存的,即ribbon會每30s向Eureka Client那里索要注冊信息,注意是Eureka Client而不是Eureka Server,這樣就更加劇了整個系統(tǒng)對服務(wù)上下線感知的延遲。我認(rèn)為Ribbon這樣設(shè)計,原因是考慮到了其上游不僅僅是Eureka Client, 還可能是配置文件,或者以編程的方式輸入的注冊列表,而這些查詢是有可能比較耗時的。Ribbon為了兼容這些情況,不得不添加了緩存。
實(shí)時上下線思路
只能用消息中間件了。新做一個上下線管理服務(wù),提供HTTP接口來上下線指定服務(wù),當(dāng)指定要下線A服務(wù)時,先向Eureka發(fā)請求,將此服務(wù)下所有的實(shí)例都刪掉,然后再向kafka類似這樣的消息系統(tǒng)中發(fā)一條消息,所有的微服務(wù)都要監(jiān)聽此消息隊(duì)列。
服務(wù)收到下線消息時,將指定服務(wù)從本地Ribbon服務(wù)列表中刪除。這里Ribbon的 Loadbalancer提供了markServerDown()方法可以使用,還是容易實(shí)現(xiàn)的。
服務(wù)收到上線消息時,需要將服務(wù)信息添加到Ribbon中,Ribbon雖然也提供了對應(yīng)的方法,但是參數(shù)較為復(fù)雜,還需要研究一下。
這些功能可以直接打包成starter, 寫好以后各服務(wù)直接引用即可。
2、Eureka Server端優(yōu)秀的多級緩存機(jī)制
? ?eureka為了避免同時讀寫內(nèi)存數(shù)據(jù)結(jié)構(gòu)造成的并發(fā)沖突問題采用了多級緩存的機(jī)制:
在拉取注冊表時會首從readonlyCacheMap緩存里查找,如果沒有再從readWriteCacheMap查找,要是還沒有找到就直接從內(nèi)存找。 當(dāng)注冊表發(fā)生改變時,直接修改內(nèi)存中的注冊表同時會過濾掉readWriteCacheMap,但readonlyCacheMap不會進(jìn)行過濾還可以訪問,當(dāng)?shù)竭_(dá)30秒過后后臺線程發(fā)現(xiàn)readWriteCacheMap被清空了,同時也會清空readonlyCacheMap。當(dāng)下個請求進(jìn)行訪問readonlyCacheMap和readWriteCacheMap會重新從最新的內(nèi)存注冊表過更新數(shù)據(jù),又達(dá)到了一致性。
總結(jié):Eureka同時通過純內(nèi)存的注冊表,保證了所有的請求都可以在內(nèi)存處理,確保了極高的性能,另外多級緩存機(jī)制,確保了不會針對內(nèi)存數(shù)據(jù)結(jié)構(gòu)發(fā)生頻繁的讀寫并發(fā)沖突操作,進(jìn)一步提升性能。
原文鏈接:https://blog.csdn.net/neosmith/article/details/90213156