一、簡述
spring cloud三步走,一導包,二依賴,三配置為我們簡化了太多東西,以至於很多東西知其然不知其所以然,了解底層實現之后對於一些問題我們也可以快速的定位問題所在。
spring cloud很多東西都是基於注解實現的,最開始接觸的很迷,怎么一個注解就可以搞定這么多事情,那些配置是怎么加載到spring容器的?
了解springboot的都會知道在jar包里面一般都會都有一個spring.factories的配置文件,里面配置了很多配置類得路徑,這里就是加載相關配置類得入口。
其次就是針對springcloud里面注解,一般來說在某個注解同級目錄里面都會有xxxxxxxAutoConfiguration這樣的配置類,里面會去初始化相關的bean,比如在EnableEurekaServer注解的同級目錄就有EurekaServerAutoConfiguration這個玩意兒,而EurekaServerAutoConfiguration這個玩意兒就是配置在spring.factories里面的
二、、Eureka架構圖及描述
1.服務注冊(register):Eureka Client會通過發送REST請求的方式向Eureka Server注冊自己的服務,提供自身的元數據,比如ip地址、端口、運行狀況指標的url、主頁地址等信息。Eureka Server接收到注冊請求后,就會把這些元數據信息存儲在一個雙層的Map中。
2.服務續約(renew):在服務注冊后,Eureka Client會維護一個心跳來持續通知Eureka Server,說明服務一直處於可用狀態,防止被剔除。Eureka Client在默認的情況下會每隔30秒(eureka.instance.leaseRenewallIntervalInSeconds)發送一次心跳來進行服務續約。
3.服務同步(replicate):Eureka Server之間會互相進行注冊,構建Eureka Server集群,不同Eureka Server之間會進行服務同步,用來保證服務信息的一致性。
4.獲取服務(get registry):服務消費者(Eureka Client)在啟動的時候,會發送一個REST請求給Eureka Server,獲取上面注冊的服務清單,並且緩存在Eureka Client本地,默認緩存30秒(eureka.client.registryFetchIntervalSeconds)。同時,為了性能慮,Eureka Server也會維護一份只讀的服務清單緩存,該緩存每隔30秒更新一次。
5.服務調用:服務消費者在獲取到服務清單后,就可以根據清單中的服務列表信息,查找到其他服務的地址,從而進行遠程調用。Eureka有Region和Zone的概念,一個Region可以包含多個Zone,在進行服務調用時,優先訪問處於同一個Zone中的服務提供者。
6.服務下線(cancel):當Eureka Client需要關閉或重啟時,就不希望在這個時間段內再有請求進來,所以,就需要提前先發送REST請求給Eureka Server,告訴Eureka Server自己要下線了,Eureka Server在收到請求后,就會把該服務狀態置為下線(DOWN),並把該下線事件傳播出去。
7.服務剔除(evict):有時候,服務實例可能會因為網絡故障等原因導致不能提供服務,而此時該實例也沒有發送請求給Eureka Server來進行服務下線,所以,還需要有服務剔除的機制。Eureka Server在啟動的時候會創建一個定時任務,每隔一段時間(默認60秒),從當前服務清單中把超時沒有續約(默認90秒,eureka.instance.leaseExpirationDurationInSeconds)的服務剔除。
8.自我保護:既然Eureka Server會定時剔除超時沒有續約的服務,那就有可能出現一種場景,網絡一段時間內發生了異常,所有的服務都沒能夠進行續約,Eureka Server就把所有的服務都剔除了,這樣顯然不太合理。所以,就有了自我保護機制,當短時間內,統計續約失敗的比例,如果達到一定閾值,則會觸發自我保護的機制,在該機制下,Eureka Server不會剔除任何的微服務,等到正常后,再退出自我保護機制。自我保護開關(eureka.server.enable-self-preservation: false)
三、EurekaServer源碼流程梳理圖
首先推薦一個在線畫圖工具https://www.processon.com,按照流程圖里面的類和方法去和源碼對號入座。
四、服務端注冊接口和注冊列表獲取接口(2019.05.03補充)
多級緩存設計思想:盡可能保證了內存注冊表數據不會出現頻繁的讀寫沖突問題,進一步保證對Eureka Server的大量請求,都是快速從純內存走,性能極高
1.在拉取注冊表的時候:
首先從ReadOnlyCacheMap里查緩存的注冊表。
若沒有,就找ReadWriteCacheMap里緩存的注冊表。
如果還沒有,就從內存中獲取實際的注冊表數據。
2.在注冊表發生變更的時候:
會在內存中更新變更的注冊表數據,同時過期掉ReadWriteCacheMap。
此過程不會影響ReadOnlyCacheMap提供人家查詢注冊表。
默認每30秒Eureka Server會將ReadWriteCacheMap更新到
ReadOnlyCacheMap里
默認每180秒Eureka Server會將ReadWriteCacheMap里是數據失效
下次有服務拉取注冊表,又會從內存中獲取最新的數據了,同時填充 各級緩存。
存在的問題:
1.當我們eureka服務實例有注冊或下線或有實例發生故障,內存注冊表雖然會及時更新數據,但是客戶端不一定能及時感知到,可能會過30秒才能感知到,因為客戶端拉取注冊表實例這里面有一個多級緩存機制
2.服務剔除的不是默認90秒沒心跳的實例,剔除的是180秒沒心跳的實例(eureka的bug導致)
/** * Renew the lease, use renewal duration if it was specified by the * associated {@link T} during registration, otherwise default duration is * {@link #DEFAULT_DURATION_IN_SECS}. */ public void renew() { lastUpdateTimestamp = System.currentTimeMillis() + duration; }
加了兩次duration值,com.netflix.eureka.lease.Lease#isExpired(long)
/** * Checks if the lease of a given {@link com.netflix.appinfo.InstanceInfo} has expired or not. * * Note that due to renew() doing the 'wrong" thing and setting lastUpdateTimestamp to +duration more than * what it should be, the expiry will actually be 2 * duration. This is a minor bug and should only affect * instances that ungracefully shutdown. Due to possible wide ranging impact to existing usage, this will * not be fixed. * * @param additionalLeaseMs any additional lease time to add to the lease evaluation in ms. */ public boolean isExpired(long additionalLeaseMs) { return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs)); }
流程圖: