總覽
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.EurekaClientConfigBean
和org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
兩個配置實例叉跛。
分別對應(yīng)了eureka.client
和eureka.instance
的相關(guān)配置松忍。eureka.client
內(nèi)的配置是作為client的一些行為相關(guān)的配置,如server地址筷厘,是否向server注冊本client等鸣峭。eureka.instance
內(nèi)的配置是作為instance的一些相關(guān)配置,如本實例的instanceId酥艳,hostname等摊溶。
向其他server發(fā)送請求
- 程序啟動后颊埃,實例化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)
- heartbeat (默認(rèn)30秒后執(zhí)行一次惰爬,然后每隔30秒執(zhí)行一次)
// 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ù)的啟動不平滑
- 發(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
接口
接受請求
- 啟動后
EurekaController
負(fù)責(zé)接收Eureka網(wǎng)頁請求 - 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
- 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
中。
- 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
- 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}
之后這些字段會被人工配置值覆蓋掉。
- 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
- 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
- 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
- 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
中霉晕。
- 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方法獲取扒俯。