Eureka詳解系列(四)--Eureka Client部分的原始碼和配置

子月生發表於2021-02-06

簡介

按照原定的計劃,我將分三個部分來分析 Eureka 的原始碼:

  1. Eureka 的配置體系(已經寫完,見Eureka詳解系列(三)--探索Eureka強大的配置體系);
  2. Eureka Client 的互動行為;
  3. Eureka Server 的互動行為。

今天,我們來研究第二部分的原始碼。

我的思路是這樣子的:先明確 Eureka Client 擁有哪些功能,然後從原始碼角度分析如何實現,最後,我會補充 Eureka Client 的配置解讀。

Eureka Client的功能

首先來回顧下 Eureka 的整個互動過程。

zzs_eureka_19

從使用者的角度來講,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 需要具備以下功能

  1. 註冊當前例項到 Eureka Server
  2. 獲取 Eureka Server 的服務登錄檔
  3. 定期向 Eureka Server 傳送心跳
  4. 定期向 Eureka Server 同步當前例項資訊
  5. 定期重新整理本地服務登錄檔

如何實現這些功能

知道了 Eureka Client 需要具備哪些功能,接下來我們就從原始碼的角度來看看怎樣實現這些功能。

和之前一樣,我更多的會從設計的層面來分析,而不會順序地去看每個過程的程式碼,即重設計、輕實現。如果對原始碼細節有疑問的,可以交流學習下。

那麼,還是從一個 UML 圖開始吧。有了它,相信大家看原始碼時會更輕鬆一些。

zzs_eureka_09

通過這個圖,我們再來看 Eureka Client 的幾個功能:

  1. 註冊當前例項到 Eureka Server;--初始化DiscoveryClient時就會註冊上去。
  2. 獲取 Eureka Server 的服務登錄檔;--通過DiscoveryClient獲取。
  3. 定期向 Eureka Server 傳送心跳;--通過HeartbeatThread任務實現。
  4. 定期向 Eureka Server 同步當前例項資訊;--通過InstanceInfoReplicator任務實現。
  5. 定期重新整理本地服務登錄檔;--通過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 裡,配置分成了三種:

  1. EurekaInstanceConfig:當前例項身份的配置資訊,即我是誰?
  2. EurekaServerConfig:一些影響當前Eureka Server和客戶端或對等節點互動行為的配置資訊,即怎麼互動?
  3. EurekaClientConfig:一些影響當前例項和Eureka Server互動行為的配置資訊,即和誰互動?怎麼互動?
zzs_eureka_18

這裡我們來講講EurekaInstanceConfigEurekaClientConfig的配置引數。

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 叢集的配置,有三種方法:

  1. 在 serviceUrl 中寫死 Eureka Server 的 IP,缺點就是每次增加、刪除、更改機器都要更改配置;
  2. 在 serviceUrl 中配置 Eureka Server 對應的 EIP,更改機器時不需要更改,但是增加、刪除機器都要更改配置;
  3. 採用 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://github.com/ZhangZiSheng001/eureka-demo

本文為原創文章,轉載請附上原文出處連結:https://www.cnblogs.com/ZhangZiSheng001/p/14381169.html

相關文章