簡介
按照原定的計划,我將分三個部分來分析 Eureka 的源碼:
- Eureka 的配置體系(已經寫完,見Eureka詳解系列(三)--探索Eureka強大的配置體系);
- Eureka Client 的交互行為;
- Eureka Server 的交互行為。
今天,我們來研究第二部分的源碼。
我的思路是這樣子的:先明確 Eureka Client 擁有哪些功能,然后從源碼角度分析如何實現,最后,我會補充 Eureka Client 的配置解讀。
Eureka Client的功能
首先來回顧下 Eureka 的整個交互過程。

從用戶的角度來講,Eureka Client 要能夠向 Eureka Server 注冊當前實例以及獲取注冊表。
至於其他的功能,我們需要再思考下。
當我們把當前實例注冊到了 Eureka Server 后,並非一勞永逸,如果當前實例故障了,Eureka Server 需要及時將它從注冊表中剔除,那么,Eureka Server 怎么知道哪些實例故障了呢?做法比較簡單,Application Service 需要定期向 Eureka Server 報告自己的健康狀態,如果一直不報告,就認為是故障了。
考慮到性能和可靠性,Application Client 本地會緩存一份服務注冊表,並不需要每次用到就從 Eureka Server 重新獲取。但是,Application Service “來來去去”,Eureka Server 的注冊表並非一成不變,所以,Application Client 還需要定期同步注冊表。
最后還有一點,我們注冊到 Eureka Server 的實例信息,除了實例 IP、端口、服務名等,還有實例 id、附帶的元數據等,這些是可更改的,Application Service 需要及時地將這些更改同步到 Eureka Server。
通過上面的分析,我們知道一個 Eureka Client 需要具備以下功能:
- 注冊當前實例到 Eureka Server;
- 獲取 Eureka Server 的服務注冊表;
- 定期向 Eureka Server 發送心跳;
- 定期向 Eureka Server 同步當前實例信息;
- 定期刷新本地服務注冊表
如何實現這些功能
知道了 Eureka Client 需要具備哪些功能,接下來我們就從源碼的角度來看看怎樣實現這些功能。
和之前一樣,我更多的會從設計的層面來分析,而不會順序地去看每個過程的代碼,即重設計、輕實現。如果對源碼細節有疑問的,可以交流學習下。
那么,還是從一個 UML 圖開始吧。有了它,相信大家看源碼時會更輕松一些。

通過這個圖,我們再來看 Eureka Client 的幾個功能:
- 注冊當前實例到 Eureka Server;--初始化
DiscoveryClient
時就會注冊上去。 - 獲取 Eureka Server 的服務注冊表;--通過
DiscoveryClient
獲取。 - 定期向 Eureka Server 發送心跳;--通過
HeartbeatThread
任務實現。 - 定期向 Eureka Server 同步當前實例信息;--通過
InstanceInfoReplicator
任務實現。 - 定期刷新本地服務注冊表;--通過
CacheRefreshThread
任務實現。
我們拿Eureka詳解系列(二)--如何使用Eureka(原生API,無Spring) 中的例子來分析下整個過程。
// 創建ApplicationInfoManager對象
ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(
new MyDataCenterInstanceConfig(), new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
// 創建EurekaClient對象,這個時候完成了幾件事:
// 1. 注冊當前實例到Eureka Server(實例的初始狀態一般是STARTING);
// 2. 開啟心跳、刷緩存、同步實例信息的定時任務;
// 3. 注冊狀態監聽器到ApplicationInfoManager(不然后面的setInstanceStatus不會生效的)
EurekaClient eurekaClient = new DiscoveryClient(applicationInfoManager, new DefaultEurekaClientConfig());
// 設置當前實例狀態為STARTING(原狀態也是STARTING,所以這一句沒什么用)
applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.STARTING);
// 設置當前實例狀態為UP觸發(監聽器觸發,執行InstanceInfoReplicator的任務)
applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.UP);
// 和application client交互
// ······
// 關閉客戶端,同時也會注銷當前實例
eurekaClient.shutdown();
我們會發現,DiscoveryClient
初始化化時做了非常多的事情,核心的源碼都在它的構造方法里,大家感興趣的可以自行閱讀。
這里提醒下,Eureka 的定時任務有點奇怪,它不是完全交給ScheduledExecutorService
來調度,舉個例子,ScheduledExecutorService
只會按設定的延遲執行一次心跳任務,然后就不執行了,之所以能夠實現定時調度,是因為心跳任務里又提交了一次任務,代碼如下:
public void run() {
try {
// ······
} finally {
// ······
if (!scheduler.isShutdown()) {
scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
}
}
}
Eureka Client的配置詳解
回顧下Eureka詳解系列(三)--探索Eureka強大的配置體系的內容,在 Eureka 里,配置分成了三種:
- EurekaInstanceConfig:當前實例身份的配置信息,即我是誰?
- EurekaServerConfig:一些影響當前Eureka Server和客戶端或對等節點交互行為的配置信息,即怎么交互?
- EurekaClientConfig:一些影響當前實例和Eureka Server交互行為的配置信息,即和誰交互?怎么交互?

這里我們來講講EurekaInstanceConfig
和EurekaClientConfig
的配置參數。
EurekaInstanceConfig--我是誰?
這些參數大部分用來向 Eureka Server 表明當前實例的身份,但我們會發現,這里混進了兩個“異類”--lease.renewalInterval 和 lease.duration,這個不應該放在EurekaClientConfig
里嗎?
我一開始也不明白,后來發現很重要的一點,EurekaClientConfig
的參數只能影響當前實例,而不能影響 Eureka Server,它的信息不能向 Eureka Server 傳遞,而EurekaInstanceConfig
的就可以,所以,除了表明實例的身份,EurekaInstanceConfig
還有另外一個功能,就是向 Eureka Server 傳遞某些重要的交互參數。
# 同一個服務下存在多個實例,這個可以作為唯一標識區分它們。默認為當前實例的主機名
eureka.instanceId=zzs
# 服務名。默認unknown
eureka.name=SampleService
# 當前實例開放服務的端口,默認80
eureka.port=8001
# 當前實例多久向Eureka Server發送一次心跳,單位秒。默認30s
eureka.lease.renewalInterval=30
# 如果沒收到心跳,Eureka Server隔多久將當前實例剔除,單位秒。默認90s
eureka.lease.duration=90
# 當前實例的虛擬主機名,通過這個可以直接訪問到當前實例。默認:當前主機名+port
eureka.vipAddress=sampleservice.zzs.cn
# 綁定在當前實例的一些自定義信息,它們會被放在一個map里,其他Eureka Client可以拿來用。默認是一個空map
eureka.metadata.name=zzs
eureka.metadata.age=18
# 這幾個一般不用,我就不展開了
eureka.appGroup=unknown
#eureka.asgName=
eureka.traffic.enabled=false
eureka.port.enabled=true
eureka.securePort=443
eureka.securePort.enabled=false
eureka.secureVipAddress=zzs:443
eureka.statusPageUrlPath=/Status
eureka.statusPageUrl=http://zzs:8001/Status
eureka.homePageUrlPath=/
eureka.homePageUr=http://zzs:8001/
eureka.healthCheckUrlPath=/healthcheck
eureka.healthCheckUrl=http://zzs:8001/healthcheck
eureka.secureHealthCheckUrl=https://zzs:443/healthcheck
EurekaClientConfig--和誰交互?怎么交互?
關於 Eureka Server 集群的配置,有三種方法:
- 在 serviceUrl 中寫死 Eureka Server 的 IP,缺點就是每次增加、刪除、更改機器都要更改配置;
- 在 serviceUrl 中配置 Eureka Server 對應的 EIP,更改機器時不需要更改,但是增加、刪除機器都要更改配置;
- 采用 DNS 配置 Eureka Server 的 IP,增加、刪除、更改機器都不需要更改配置。
這里還涉及到 region、zone 的概念,可以理解為:region 表示機器部署在不同的城市,zone 表示機器部署在同一個城市的不同機房里。默認情況下,Eureka Client 會優先選擇自己所屬 region 的 Eureka Server 來訪問。
# 當前實例多久同步一次本地注冊表,單位秒。默認30s
eureka.client.refresh.interval=30
# 當前實例多久同步一次實例信息,單位秒。默認30s
eureka.appinfo.replicate.interval=30
# 當前實例是否注冊到Eureka Server。默認true
eureka.registration.enabled=true
# 當前實例是否需要從Eureka Server獲取服務注冊表
eureka.shouldFetchRegistry=true
# 當前實例可以和哪些region的Eureka Server交互
eureka.fetchRemoteRegionsRegistry=beijing,shanghai
# 當前實例所在的region
eureka.region=beijing
# region下有哪些zone
eureka.beijing.availabilityZones=zone-1,zone-2
eureka.shanghai.availabilityZones=zone-3
# zone下有哪些Eureka Server(這種配置可以通過EIP來避免寫死IP,但擴展時還是要改,推薦使用DNS的方式)
eureka.serviceUrl.zone-1=http://ec2-552-627-568-165.compute-1.amazonaws.com:7001/eureka/v2/,http://ec2-368-101-182-134.compute-1.amazonaws.com:7001/eureka/v2/
eureka.serviceUrl.zone-2=http://ec2-552-627-568-170.compute-1.amazonaws.com:7001/eureka/v2/
eureka.serviceUrl.zone-3=http://ec2-500-179-285-592.compute-1.amazonaws.com:7001/eureka/v2/
# 當我們使用DNS配置serviceUrl時需要用到的配置(非常推薦使用,可以避免寫死IP,且方便擴展)
eureka.shouldUseDns=true
eureka.eurekaServer.domainName=sampleservice.zzs.cn
eureka.eurekaServer.port=8001
eureka.eurekaServer.context=eureka/v2
# 這幾個一般不用,我就不展開了
eureka.preferSameZone=true
eureka.appinfo.initial.replicate.time=40
eureka.serviceUrlPollIntervalMs=300
eureka.client.heartbeat.threadPoolSize=5
eureka.client.heartbeat.exponentialBackOffBound=10
eureka.client.cacheRefresh.threadPoolSize=5
eureka.client.cacheRefresh.exponentialBackOffBound=10
#eureka.eurekaServer.proxyHost=
#eureka.eurekaServer.proxyPort=
#eureka.eurekaServer.proxyUserName=
#eureka.eurekaServer.proxyPassword=
eureka.eurekaServer.gzipContent=true
eureka.eurekaServer.readTimeout=8
eureka.eurekaServer.connectTimeout=5
eureka.eurekaServer.maxTotalConnections=200
eureka.eurekaServer.maxConnectionsPerHost=50
eureka.eurekaserver.connectionIdleTimeoutInSeconds=45
#eureka.backupregistry=
eureka.shouldEnforceRegistrationAtInit=false
eureka.shouldEnforceFetchRegistryAtInit=false
eureka.shouldUnregisterOnShutdown=true
eureka.shouldFilterOnlyUpInstances=true
eureka.shouldOnDemandUpdateStatusChange=true
eureka.allowRedirects=true
eureka.printDeltaFullDiff=true
eureka.disableDelta=false
eureka.registryRefreshSingleVipAddress=false
eureka.dollarReplacement=_-
eureka.escapeCharReplacement=__
#eureka.encoderName=
#eureka.decoderName=
eureka.clientDataAccept=full
eureka.experimental.clientTransportFailFastOnInit=true
以上比較宏觀地講完了 Eureka Client 的源碼和配置,感謝您的閱讀。
參考資料
https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance
本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/14381169.html