Eureka 源碼分析

總覽

Eureka 分為 Server 和 Client。
而Eureka Server既作為server接受client的注冊,又作為client向集群中的其他server實例注冊自己未荒。所以后文以Eureka Server為線索進行源碼分析承耿,既覆蓋了server部分妈倔,也覆蓋了client部分博投。

配置相關(guān)

通過org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration類進行自動配置。
注意該類的一個注解启涯,
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
即贬堵,如果沒有配置eureka.client.enabled,那么默認(rèn)為true结洼,開啟eureka的client功能。
此類也會實例化org.springframework.cloud.netflix.eureka.EurekaClientConfigBeanorg.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean兩個配置實例叉跛。
分別對應(yīng)了eureka.clienteureka.instance的相關(guān)配置松忍。eureka.client內(nèi)的配置是作為client的一些行為相關(guān)的配置,如server地址筷厘,是否向server注冊本client等鸣峭。eureka.instance內(nèi)的配置是作為instance的一些相關(guān)配置,如本實例的instanceId酥艳,hostname等摊溶。

向其他server發(fā)送請求

  1. 程序啟動后颊埃,實例化bean的過程中
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();

這其中會實例化com.netflix.discovery.DiscoveryClient炼蛤。
DiscoveryClient的構(gòu)造函數(shù)執(zhí)行過程中會fetch registry。即

if (clientConfig.shouldFetchRegistry()) {
    boolean primaryFetchRegistryResult = fetchRegistry(false);

url為http://{host}:{port}/eureka/apps/响蕴。
然后initScheduledTasks();
這些定時任務(wù)包括了

  • if shouldFetchRegistry 骤铃,fetch registry (默認(rèn) 30秒后執(zhí)行一次拉岁,然后每隔30秒執(zhí)行一次)
    fetchRegistry(boolean forceFullRegistryFetch)方法內(nèi)根據(jù)applications.getRegisteredApplications().size() == 0來抉擇使用哪個url來fetch,http://{host}:{port}/eureka/apps/(全量更新)
    或是http://{host}:{port}/eureka/apps/delta(增量更新)
  • if shouldRegisterWithEureka
    • heartbeat (默認(rèn)30秒后執(zhí)行一次惰爬,然后每隔30秒執(zhí)行一次)
      url為http://{host}:{port}/eureka/apps/UNKNOWN/{instanceId}?status=UP&lastDirtyTimestamp=1610865104367
    • InstanceInfoReplicator(默認(rèn)40秒后執(zhí)行一次)
      com.netflix.discovery.InstanceInfoReplicator
    • 注冊狀態(tài)變化監(jiān)聽器
      applicationInfoManager.registerStatusChangeListener(statusChangeListener);
      監(jiān)聽器現(xiàn)在不會被調(diào)用喊暖,接到事件時,會調(diào)用instanceInfoReplicator.onDemandUpdate();
      (被調(diào)用的過程詳見2)
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();

1中的實例化bean之后撕瞧,會執(zhí)行finishRefresh()陵叽。該方法會調(diào)用所有實現(xiàn)了Lifecycle接口的bean的Lifecycle.start()方法狞尔。然后依次調(diào)用
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration#start
org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry#register
最后com.netflix.appinfo.ApplicationInfoManager#setInstanceStatusr中調(diào)用監(jiān)聽器。
1中注冊的響應(yīng)函數(shù)被調(diào)用com.netflix.discovery.InstanceInfoReplicator#onDemandUpdate
然后在新的線程中立即執(zhí)行一次InstanceInfoReplicator.this.run();巩掺,然后每隔30秒周期執(zhí)行一次本函數(shù)
函數(shù)中會執(zhí)行discoveryClient.register();
發(fā)送http請求沪么,url為http://{host}:{port}/eureka/apps/UNKNOWN,向server注冊本實例信息锌半。

注意
discoveryClient.register() 中的http請求發(fā)出后禽车,eureka注冊中心中就會注冊上本服務(wù),流量就會進入到服務(wù)器中
而springboot服務(wù)需要執(zhí)行完TomcatWebServer.start()刊殉,才會暴露http端口號殉摔,才能實際接收http請求。見SpringBoot中Tomcat的源碼分析一文
雖然二者是在不同的線程中執(zhí)行的记焊。但某些版本的springboot中逸月,這兩個函數(shù)觸發(fā)順序反了。有可能會先執(zhí)行discoveryClient.register()遍膜,再執(zhí)行TomcatWebServer.start()碗硬。即先注冊至服務(wù)中,而此時http端口還不可用瓢颅,最終導(dǎo)致服務(wù)的啟動不平滑

  1. 發(fā)送請求的http client默認(rèn)是com.netflix.discovery.shared.transport.jersey.JerseyApplicationClient
    繼承了com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient抽象類
    實現(xiàn)了 com.netflix.discovery.shared.transport.EurekaHttpClient接口

接受請求

  1. 啟動后EurekaController負(fù)責(zé)接收Eureka網(wǎng)頁請求
  2. restfule接口
    https://github.com/Netflix/eureka/wiki/Eureka-REST-operations 文檔中記錄了restful接口實例恩尾。不過實際代碼中使用的url與文檔不同,url中不包含v2挽懦,雖然不包括v2翰意,但默認(rèn)版本是v2,所以邏輯一致信柿。

2.1 eureka-core中的com.netflix.eureka.resources包內(nèi)的*Resource類中包含了實例間通信的restful接口冀偶,功能類似controller。
2.2 spring-cloud-netflix-eureka-server-3.0.0.jar 包中的org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration#jerseyApplication方法實例化了javax.ws.rs.core.Application這個Bean渔嚷。實例化的過程中會掃描2.1中提到的部分*Resource類进鸠。這些類會在后續(xù)的構(gòu)造出url及其對對應(yīng)的

2.3 jersey-server.jar中的com.sun.jersey.server.impl.application.WebApplicationImpl#_initiate方法末尾處

RulesMap<UriRule> rootRules = new RootResourceUriRules(this,
        resourceConfig, wadlFactory, injectableFactory).getRules();

這是根據(jù)2.2中掃描出的*Resource類構(gòu)造出RulesMap,是一種規(guī)則映射形病,可以將restful請求映射到對應(yīng)的處理method客年。

2.4 jersey-servlet.jar中的com.sun.jersey.spi.container.servlet.WebComponent#service方法中的語句_application.handleRequest(cRequest, w);,將http request與2.3中的ruleMap進行匹配窒朋,找到對應(yīng)的處理method搀罢。
jersey-server.jar中的com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider.ResponseOutInvoker#_dispatch方法中通過反射調(diào)用具體的處理method。

InstanceRegistry

org.springframework.cloud.netflix.eureka.server.InstanceRegistry是核心類侥猩。
該類有一個緩存屬性榔至,是繼承于父類的屬性
com.netflix.eureka.registry.AbstractInstanceRegistry#responseCache,它默認(rèn)使用的是com.netflix.eureka.registry.ResponseCacheImpl實現(xiàn)欺劳。
ResponseCacheImpl中有兩個屬性
ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>()LoadingCache<Key, Value> readWriteCacheMap唧取。其中readWriteCacheMap是一個基于com.google.common.cache.LoadingCache構(gòu)建的緩存铅鲤。

com.netflix.eureka.registry.ResponseCacheImpl#generatePayload方法用于生成readWriteCacheMap緩存的值。緩存key為com.netflix.eureka.registry.Key枫弟。generatePayload方法內(nèi)部根據(jù)不同的Key執(zhí)行不同的加載邏輯邢享。

Eureka-CacheFillTimer線程每隔30s執(zhí)行一次com.netflix.eureka.registry.ResponseCacheImpl#getCacheUpdateTask,將ResponseCacheImpl內(nèi)的readWriteCacheMap中的緩存信息更新至readOnlyCacheMap淡诗。

/eureka/apps/

拉取實例信息列表
http method: GET

  1. server 調(diào)用方
    調(diào)用鏈為依次為
getApplicationsInternal:189, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
getApplications:167, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
.....
getApplications:134, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
getAndStoreFullRegistry:1101, DiscoveryClient (com.netflix.discovery)
fetchRegistry:1014, DiscoveryClient (com.netflix.discovery)
<init>:441, DiscoveryClient (com.netflix.discovery)

server 調(diào)用方僅僅請求/eureka/apps/url骇塘,沒有添加任何參數(shù)。
服務(wù)端返回的response是一個com.netflix.discovery.shared.Applications韩容,然后將該對象處理一下之后款违,存儲在com.netflix.discovery.DiscoveryClient#localRegionApps中。

  1. server 接收方
    handle method: com.netflix.eureka.resources.ApplicationsResource#getContainers
    該方法使用(entityType:Application, entityName:ALL_APPS,等)構(gòu)造com.netflix.eureka.registry.Key群凶,通過這個key在responseCache中查找緩存值插爹。緩存的相關(guān)信息見上面的InstanceRegistry一節(jié)。
    如果沒有緩存请梢,使用com.netflix.eureka.registry.AbstractInstanceRegistry#getApplications生成返回值并緩存赠尾。此方法調(diào)用com.netflix.eureka.registry.AbstractInstanceRegistry#getApplicationsFromMultipleRegions,返回com.netflix.eureka.registry.AbstractInstanceRegistry#registry中的全部已注冊實例信息毅弧。
    Eureka Server啟動后發(fā)出的第一個請求就是這個請求气嫁,但是這時候收到的response是空列表,因為還沒有任何實例在server中注冊形真。

/eureka/apps/{appName}

向服務(wù)器注冊實例信息
http method: POST

  1. server 調(diào)用方
    調(diào)用鏈為依次為
register:48, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
......
register:56, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
register:876, DiscoveryClient (com.netflix.discovery)
run:121, InstanceInfoReplicator (com.netflix.discovery)
run:101, InstanceInfoReplicator$1 (com.netflix.discovery)

server 調(diào)用方使用com.netflix.appinfo.InstanceInfo#getAppName作為url參數(shù)中的{appName}杉编,默認(rèn)為UNKNOWN。POST body為InstanceInfo咆霜。

InstanceInfo實例化于org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.RefreshableEurekaClientConfiguration#eurekaApplicationInfoManager,字段信息來源于org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean配置類嘶朱。
而這個配置類又實例化于org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean蛾坯。
EurekaInstanceConfigBean的初始化過程會賦予一些一些關(guān)鍵字段默認(rèn)值。

  • appName 默認(rèn)為 UNKNOWN
  • hostInfo 默認(rèn)來源于org.springframework.cloud.commons.util.InetUtils#findFirstNonLoopbackHostInfo疏遏,其中使用java.net.NetworkInterface#getNetworkInterfaces方法獲取所有網(wǎng)卡信息脉课。
  • ipAddress 默認(rèn)為hostInfo.getIpAddress()
  • hostname 默認(rèn)為hostInfo.getHostname()
  • instanceId 默認(rèn)為{hostname}:{port}
    之后這些字段會被人工配置值覆蓋掉。
  1. server 接收方
    handle method
    首先根據(jù)路徑/eureka/apps/{appName}匹配至方法com.netflix.eureka.resources.ApplicationsResource#getApplicationResource财异。
    該方法實例化了一個com.netflix.eureka.resources.ApplicationResource對象并返回倘零,然后繼續(xù)匹配,匹配至該對象的方法com.netflix.eureka.resources.ApplicationResource#addInstance戳寸。
    注冊方法
    org.springframework.cloud.netflix.eureka.server.InstanceRegistry#register()
    注冊到 <appName, <instanceId, Lease<InstanceInfo>>> 嵌套的map中呈驶。
    注冊成功后responseCache.invalidate()重置緩存,此緩存曾在上面的 InstanceRegistry 一節(jié)中提到疫鹊。
    然后執(zhí)行replicateToPeers()袖瞻,將信息同步至集群司致,使用類似下面的請求來同步信息。
    http://server-peer0:8080/eureka/peerreplication/batch/

/eureka/apps/{appName}/{instanceId}?status={status}&lastDirtyTimestamp={lastDirtyTimestamp}

心跳信息
http method: PUT

  1. server 調(diào)用方
sendHeartBeat:103, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
.....
sendHeartBeat:89, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
renew:893, DiscoveryClient (com.netflix.discovery)
run:1457, DiscoveryClient$HeartbeatThread (com.netflix.discovery)

該請求沒有http body

  1. server 接收方
    依次匹配
    com.netflix.eureka.resources.ApplicationsResource#getApplicationResource
    com.netflix.eureka.resources.ApplicationResource#getInstanceInfo
    com.netflix.eureka.resources.InstanceResource#renewLease
    刷新存活時間聋迎,同步至集群

/eureka/apps/delta

增量更新
http method: GET

  1. server 調(diào)用方
getApplicationsInternal:189, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
getDelta:172, AbstractJerseyEurekaHttpClient (com.netflix.discovery.shared.transport.jersey)
......
getDelta:149, EurekaHttpClientDecorator (com.netflix.discovery.shared.transport.decorator)
getAndUpdateDelta:1135, DiscoveryClient (com.netflix.discovery)
fetchRegistry:1016, DiscoveryClient (com.netflix.discovery)
refreshRegistry:1531, DiscoveryClient (com.netflix.discovery)
run:1498, DiscoveryClient$CacheRefreshThread (com.netflix.discovery)

此請求的返回值和/eureka/apps/{appName}請求一樣脂矫,是一個com.netflix.discovery.shared.Applications。然后通過com.netflix.discovery.DiscoveryClient#updateDelta方法將增量的變化信息存儲至com.netflix.discovery.DiscoveryClient#localRegionApps中霉晕。

  1. server 接收方
    com.netflix.eureka.resources.ApplicationsResource#getContainerDifferential
    該方法使用(entityType:Application, entityName:ALL_APPS_DELTA,等)構(gòu)造com.netflix.eureka.registry.Key庭再,通過這個key在responseCache中查找緩存值。緩存的相關(guān)信息見上面的InstanceRegistry一節(jié)牺堰。
    如果沒有緩存拄轻,使用com.netflix.eureka.registry.AbstractInstanceRegistry#getApplicationDeltass生成返回值并緩存。

Client

通過上文對 Server 的流程可知萌焰,Eureka Client 節(jié)點的主要邏輯均在于com.netflix.discovery.DiscoveryClient類哺眯,集群內(nèi)的節(jié)點信息存儲于localRegionApps屬性中,使用各種get方法獲取扒俯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奶卓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子撼玄,更是在濱河造成了極大的恐慌夺姑,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掌猛,死亡現(xiàn)場離奇詭異盏浙,居然都是意外死亡,警方通過查閱死者的電腦和手機荔茬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門废膘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慕蔚,你說我怎么就攤上這事丐黄。” “怎么了孔飒?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵灌闺,是天一觀的道長。 經(jīng)常有香客問我坏瞄,道長桂对,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任鸠匀,我火速辦了婚禮蕉斜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己蛛勉,他們只是感情好鹿寻,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诽凌,像睡著了一般毡熏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侣诵,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天痢法,我揣著相機與錄音,去河邊找鬼杜顺。 笑死财搁,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的躬络。 我是一名探鬼主播尖奔,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼穷当!你這毒婦竟也來了提茁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤馁菜,失蹤者是張志新(化名)和其女友劉穎茴扁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汪疮,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡峭火,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了智嚷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卖丸。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盏道,靈堂內(nèi)的尸體忽然破棺而出坯苹,到底是詐尸還是另有隱情,我是刑警寧澤摇天,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站恐仑,受9級特大地震影響泉坐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜裳仆,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一腕让、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦纯丸、人聲如沸偏形。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俊扭。三九已至,卻和暖如春坠陈,著一層夾襖步出監(jiān)牢的瞬間萨惑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工仇矾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庸蔼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓贮匕,卻偏偏與公主長得像姐仅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刻盐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354