Eureka詳解系列(四)–Eureka Client部分的源碼和配置

簡介

按照原定的計劃,我將分三個部分來分析 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=//zzs:8001/Status
eureka.homePageUrlPath=/
eureka.homePageUr=//zzs:8001/
eureka.healthCheckUrlPath=/healthcheck
eureka.healthCheckUrl=//zzs:8001/healthcheck
eureka.secureHealthCheckUrl=//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=//ec2-552-627-568-165.compute-1.amazonaws.com:7001/eureka/v2/,//ec2-368-101-182-134.compute-1.amazonaws.com:7001/eureka/v2/
eureka.serviceUrl.zone-2=//ec2-552-627-568-170.compute-1.amazonaws.com:7001/eureka/v2/
eureka.serviceUrl.zone-3=//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 的源碼和配置,感謝您的閱讀。

參考資料

//github.com/Netflix/eureka/wiki/Eureka-at-a-glance

相關源碼請移步://github.com/ZhangZiSheng001/eureka-demo

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