Eureka是Netflix開源的、用於實現服務註冊和發現的服務。Spring Cloud Eureka基於Eureka進行二次封裝,增長了更人性化的UI,使用更爲方便。可是因爲Eureka自己存在較多緩存,服務狀態更新滯後,最多見的情況是:服務下線後狀態沒有及時更新,服務消費者調用到已下線的服務致使請求失敗。本文基於Spring Cloud Eureka 1.4.4.RELEASE,在默認region和zone的前提下,介紹Eureka的緩存機制。緩存
從CAP理論看,Eureka是一個AP系統,優先保證可用性(A)和分區容錯性(P),不保證強一致性(C),只保證最終一致性,所以在架構中設計了較多緩存。架構
Eureka服務狀態enum類:com.netflix.appinfo.InstanceInfo.InstanceStatus
app
狀態 | 說明 | 狀態 | 說明 |
---|---|---|---|
UP | 在線 | OUT_OF_SERVICE | 失效 |
DOWN | 下線 | UNKNOWN | 未知 |
STARTING | 正在啓動 |
在Eureka高可用架構中,Eureka Server也能夠做爲Client向其餘server註冊,多節點相互註冊組成Eureka集羣,集羣間相互視爲peer。Eureka Client向Server註冊、續約、更新狀態時,接受節點更新本身的服務註冊信息後,逐個同步至其餘peer節點。分佈式
【注意】若是server-A向server-B節點單向註冊,則server-A視server-B爲peer節點,server-A接受的數據會同步給server-B,但server-B接受的數據不會同步給server-A。spa
Eureka Server存在三個變量:(registry、readWriteCacheMap、readOnlyCacheMap)保存服務註冊信息,默認狀況下定時任務每30s將readWriteCacheMap同步至readOnlyCacheMap,每60s清理超過90s未續約的節點,Eureka Client每30s從readOnlyCacheMap更新服務註冊信息,而UI則從registry更新服務註冊信息。.net
三級緩存設計
緩存 | 類型 | 說明 |
---|---|---|
registry | ConcurrentHashMap | 實時更新,類AbstractInstanceRegistry成員變量,UI端請求的是這裏的服務註冊信息 |
readWriteCacheMap | Guava Cache/LoadingCache | 實時更新,類ResponseCacheImpl成員變量,緩存時間180秒 |
readOnlyCacheMap | ConcurrentHashMap | 週期更新,類ResponseCacheImpl成員變量,默認每30s從readWriteCacheMap更新,Eureka client默認從這裏更新服務註冊信息,可配置直接從readWriteCacheMap更新 |
緩存相關配置code
配置 | 默認 | 說明 |
---|---|---|
eureka.server.useReadOnlyResponseCache |
true | Client從readOnlyCacheMap更新數據,false則跳過readOnlyCacheMap直接從readWriteCacheMap更新 |
eureka.server.responsecCacheUpdateIntervalMs |
30000 | readWriteCacheMap更新至readOnlyCacheMap週期,默認30s |
eureka.server.evictionIntervalTimerInMs |
60000 | 清理未續約節點(evict)週期,默認60s |
eureka.instance.leaseExpirationDurationInSeconds |
90 | 清理未續約節點超時時間,默認90s |
關鍵類server
類名 | 說明 |
---|---|
com.netflix.eureka.registry.AbstractInstanceRegistry |
保存服務註冊信息,持有registry和responseCache成員變量 |
com.netflix.eureka.registry.ResponseCacheImpl |
持有readWriteCacheMap和readOnlyCacheMap成員變量 |
Eureka Client存在兩種角色:服務提供者和服務消費者,做爲服務消費者通常配合Ribbon或Feign(Feign內部使用Ribbon)使用。Eureka Client啓動後,做爲服務提供者當即向Server註冊,默認狀況下每30s續約(renew);做爲服務消費者當即向Server全量更新服務註冊信息,默認狀況下每30s增量更新服務註冊信息;Ribbon延時1s向Client獲取使用的服務註冊信息,默認每30s更新使用的服務註冊信息,只保存狀態爲UP的服務。blog
二級緩存
緩存 | 類型 | 說明 |
---|---|---|
localRegionApps | AtomicReference | 週期更新,類DiscoveryClient成員變量,Eureka Client保存服務註冊信息,啓動後當即向Server全量更新,默認每30s增量更新 |
upServerListZoneMap | ConcurrentHashMap | 週期更新,類LoadBalancerStats成員變量,Ribbon保存使用且狀態爲UP的服務註冊信息,啓動後延時1s向Client更新,默認每30s更新 |
緩存相關配置
配置 | 默認 | 說明 |
---|---|---|
eureka.instance.leaseRenewalIntervalInSeconds |
30 | Eureka Client 續約週期,默認30s |
eureka.client.registryFetchIntervalSeconds |
30 | Eureka Client 增量更新週期,默認30s(正常狀況下增量更新,超時或與Server端不一致等狀況則全量更新) |
ribbon.ServerListRefreshInterval |
30000 | Ribbon 更新週期,默認30s |
關鍵類
類名 | 說明 |
---|---|
com.netflix.discovery.DiscoveryClient |
Eureka Client 負責註冊、續約和更新,方法initScheduledTasks()分別初始化續約和更新定時任務 |
com.netflix.loadbalancer.PollingServerListUpdater |
Ribbon 更新使用的服務註冊信息,start初始化更新定時任務 |
com.netflix.loadbalancer.LoadBalancerStats |
Ribbon,保存使用且狀態爲UP的服務註冊信息 |
Eureka Client | 時間 | 說明 |
---|---|---|
上線 | 30(readOnly)+30(Client)+30(Ribbon)=90s | readWrite -> readOnly -> Client -> Ribbon 各30s |
正常下線 | 30(readonly)+30(Client)+30(Ribbon)=90s | 服務正常下線(kill或kill -15殺死進程)會給進程善後機會,DiscoveryClient.shutdown()將向Server更新自身狀態爲DOWN,而後發送DELETE請求註銷本身,registry和readWriteCacheMap實時更新,故UI將再也不顯示該服務實例 |
非正常下線 | 30+60(evict)*2+30+30+30= 240s | 服務非正常下線(kill -9殺死進程或進程崩潰)不會觸發DiscoveryClient.shutdown()方法,Eureka Server將依賴每60s清理超過90s未續約服務從registry和readWriteCacheMap中刪除該服務實例 |
考慮以下狀況
所以,極限狀況下服務消費者最長感知時間將無限趨近240s。
服務註冊中心在選擇使用Eureka時說明已經接受了其優先保證可用性(A)和分區容錯性(P)、不保證強一致性(C)的特色。若是須要優先保證強一致性(C),則應該考慮使用ZooKeeper等CP系統做爲服務註冊中心。分佈式系統中通常配置多節點,單個節點服務上線的狀態更新滯後並無什麼影響,這裏主要考慮服務下線後狀態更新滯後的應對措施。
1.縮短readOnlyCacheMap更新週期。縮短該定時任務週期可減小滯後時間。
eureka.server.responsecCacheUpdateIntervalMs: 10000 # Eureka Server readOnlyCacheMap更新週期
2.關閉readOnlyCacheMap。中小型系統能夠考慮該方案,Eureka Client直接從readWriteCacheMap更新服務註冊信息。
eureka.server.useReadOnlyResponseCache: false # 是否使用readOnlyCacheMap
1.服務消費者使用容錯機制。如Spring Cloud Retry和Hystrix,Ribbon、Feign、Zuul均可以配置Retry,服務消費者訪問某個已下線節點時通常報ConnectTimeout,這時能夠經過Retry機制重試下一個節點。
2.服務消費者縮短更新週期。Eureka Client和Ribbon二級緩存影響狀態更新,縮短這兩個定時任務週期可減小滯後時間,例如配置:
eureka.client.registryFetchIntervalSeconds: 5 # Eureka Client更新週期 ribbon.ServerListRefreshInterval: 2000 # Ribbon更新週期
3.服務提供者保證服務正常下線。服務下線時使用kill或kill -15命令,避免使用kill -9命令,kill或kill -15命令殺死進程時將觸發Eureka Client的shutdown()方法,主動刪除Server的registry和readWriteCacheMap中的註冊信息,沒必要依賴Server的evict清除。
4.服務提供者延遲下線。服務下線以前先調用接口使Eureka Server中保存的服務狀態爲DOWN或OUT_OF_SERVICE後再下線,兩者時間差根據緩存機制和配置決定,好比默認狀況下調用接口後延遲90s再下線服務便可保證服務消費者不會調用已下線服務實例。
在軟件工程中,沒有一個問題是中間層解決不了的,而網關是服務提供者和服務消費者的中間層。以Spring Cloud Zuul網關爲例,網關做爲Eureka Client保存了服務註冊信息,服務消費者經過網關將請求轉發給服務提供者,只須要作到服務提供者下線時通知網關在本身保存的服務列表中使該服務失效。爲了保持網關的獨立性,可實現一個獨立服務接收下線通知並協調網關集羣。下篇文章將詳細介紹網關如何實現服務下線實時感知,敬請期待!
做者:馮永彪
內容來源:宜信技術學院