日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Eureka Client注册到Eureka Server的秘密

發(fā)布時(shí)間:2024/7/5 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Eureka Client注册到Eureka Server的秘密 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

我們知道Eureka分為兩部分,Eureka Server和Eureka Client。Eureka Server充當(dāng)注冊中心的角色,Eureka Client相對于Eureka Server來說是客戶端,需要將自身信息注冊到注冊中心。本文主要介紹的就是在Eureka Client注冊到Eureka Server時(shí)RetryableClientQuarantineRefreshPercentage參數(shù)的使用技巧。

Eureka Client注冊過程分析

Eureka Client注冊到Eureka Server時(shí),首先遇到第一個(gè)問題就是Eureka Client端要知道Server的地址,這個(gè)參數(shù)對應(yīng)的是eureka.client.service-url.defaultZone舉個(gè)例子,在Eureka Client的properties文件中配置如下:

eureka.client.service-url.defaultZone=
http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka,http://localhost:8764/eureka

如上圖所示,Eureka Client配置對應(yīng)的Eureka Server地址分別是8761、8762、8763、8764。這里存在兩個(gè)問題:

  • Eureka Client會(huì)將自身信息分別注冊到這四個(gè)地址嗎?
  • Eureka Clinent注冊機(jī)制是怎樣的?

源碼面前一目了然,帶著這兩個(gè)問題我們通過源碼來解答這兩個(gè)問題。Eureka Client在啟動(dòng)的時(shí)候注冊源碼如下:
RetryableEurekaHttpClient中的execut方法

@Override
protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
List<EurekaEndpoint> candidateHosts = null;
int endpointIdx = 0;
for (int retry = 0; retry < numberOfRetries; retry++) {
EurekaHttpClient currentHttpClient = delegate.get();
EurekaEndpoint currentEndpoint = null;
if (currentHttpClient == null) {
if (candidateHosts == null) {
candidateHosts = getHostCandidates();
if (candidateHosts.isEmpty()) {
throw new TransportException("There is no known eureka server; cluster server list is empty");
}
}
if (endpointIdx >= candidateHosts.size()) {
throw new TransportException("Cannot execute request on any known server");
}

currentEndpoint = candidateHosts.get(endpointIdx++);
currentHttpClient = clientFactory.newClient(currentEndpoint);
}

try {
EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient);
if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) {
delegate.set(currentHttpClient);
if (retry > 0) {
logger.info("Request execution succeeded on retry #{}", retry);
}
return response;
}
logger.warn("Request execution failure with status code {}; retrying on another server if available", response.getStatusCode());
} catch (Exception e) {
logger.warn("Request execution failed with message: {}", e.getMessage()); // just log message as the underlying client should log the stacktrace
}

// Connection error or 5xx from the server that must be retried on another server
delegate.compareAndSet(currentHttpClient, null);
if (currentEndpoint != null) {
quarantineSet.add(currentEndpoint);
}
}
throw new TransportException("Retry limit reached; giving up on completing the request");
}

按照我的理解,代碼精簡后內(nèi)容如下:

int endpointIdx = 0;
//用來保存所有Eureka Server信息(8761、8762、8763、8764)
List<EurekaEndpoint> candidateHosts = null;
//numberOfRetries的值代碼寫死默認(rèn)為3次
for (int retry = 0; retry < numberOfRetries; retry++) {
/**
*首次進(jìn)入循環(huán)時(shí),獲取全量的Eureka Server信息(8761、8762、8763、8764)
*/
if (candidateHosts == null) {
candidateHosts = getHostCandidates();
}
/**
*通過endpointIdx自增,依次獲取Eureka Server信息,然后發(fā)送
*注冊的Post請求.
*/
currentEndpoint = candidateHosts.get(endpointIdx++);
currentHttpClient = clientFactory.newClient(currentEndpoint);
try {
/**
*發(fā)送注冊的Post請求動(dòng)作,注意如果成功,則跳出循環(huán),如果失敗則
*根據(jù)endpointIdx依次獲取下一個(gè)Eureka Server.
*/
response = requestExecutor.execute(currentHttpClient);
return respones;
} catch (Exception e) {
//向注冊中心(Eureka Server)發(fā)起注冊的post出現(xiàn)異常時(shí),打印日志...
}
//如果此次注冊動(dòng)作失敗,將當(dāng)前的信息保存到quarantineSet中(一個(gè)Set集合)
if (currentEndpoint != null) {
quarantineSet.add(currentEndpoint);
}
}
//如果都失敗,則以異常形式拋出...
throw new TransportException("Retry limit reached; giving up on completing the request");

上面代碼中還有一個(gè)方法很重要就是List<EurekaEndpoint> candidateHosts = getHostCandidates();接下來看下getHostCandidates()方法源碼

private List<EurekaEndpoint> getHostCandidates() {
List<EurekaEndpoint> candidateHosts = clusterResolver.getClusterEndpoints();
quarantineSet.retainAll(candidateHosts);

// If enough hosts are bad, we have no choice but start over again
int threshold = (int) (candidateHosts.size() * transportConfig.getRetryableClientQuarantineRefreshPercentage());
if (quarantineSet.isEmpty()) {
// no-op
} else if (quarantineSet.size() >= threshold) {
logger.debug("Clearing quarantined list of size {}", quarantineSet.size());
quarantineSet.clear();
} else {
List<EurekaEndpoint> remainingHosts = new ArrayList<>(candidateHosts.size());
for (EurekaEndpoint endpoint : candidateHosts) {
if (!quarantineSet.contains(endpoint)) {
remainingHosts.add(endpoint);
}
}
candidateHosts = remainingHosts;
}
return candidateHosts;
}

按照我的理解,將代碼精簡下,只包括關(guān)鍵邏輯,內(nèi)容如下:

private List<EurekaEndpoint> getHostCandidates() {
/**
* 獲取所有defaultZone配置的注冊中心信息(Eureka Server),
* 在本文例子中代表4個(gè)(8761、8762、8763、8764)Eureka Server
*/
List candidateHosts = clusterResolver.getClusterEndpoints();
/**
* quarantineSet這個(gè)Set集合中保存的是不可用的Eureka Server
* 此處是拿不可用的Eureka Server與全量的Eureka Server取交集
*/
quarantineSet.retainAll(candidateHosts);
/**
* 根據(jù)RetryableClientQuarantineRefreshPercentage參數(shù)計(jì)算閾值
* 該閾值后續(xù)會(huì)和quarantineSet中保存的不可用的Eureka Server個(gè)數(shù)
* 作比較,從而判斷是否返回全量的Eureka Server還是過濾掉不可用的
* Eureka Server。
*/
int threshold =
(int) (
candidateHosts.size()
*
transportConfig.getRetryableClientQuarantineRefreshPercentage()
);
if (quarantineSet.isEmpty()) {
/**
* 首次進(jìn)入的時(shí)候,此時(shí)quarantineSet為空,直接返回全量的
* Eureka Server列表
*/
} else if (quarantineSet.size() >= threshold) {
/**
* 將不可用的Eureka Server與threshold值相比較,如果不可
* 用的Eureka Server個(gè)數(shù)大于閾值,則將之間保存的Eureka
* Server內(nèi)容直接清空,并返回全量的Eureka Server列表。
*/
quarantineSet.clear();
} else {
/**
* 通過quarantineSet集合保存不可用的Eureka Server來過濾
* 全量的EurekaServer,從而獲取此次Eureka Client要注冊要
* 注冊的Eureka Server實(shí)例地址。
*/
List<EurekaEndpoint> remainingHosts = new ArrayList<>(candidateHosts.size());
for (EurekaEndpoint endpoint : candidateHosts) {
if (!quarantineSet.contains(endpoint)) {
remainingHosts.add(endpoint);
}
}
candidateHosts = remainingHosts;
}
return candidateHosts;
}

通過源碼分析,我們現(xiàn)在初步知道,當(dāng)Eureka Client向Eureka Server發(fā)起注冊請求的時(shí)候(根據(jù)defaultZone尋找Eureka Server列表),如果有一次請求注冊成功,那么后續(xù)就不會(huì)在向其他Eureka Server發(fā)起注冊請求。以本文為例,注冊中心有四個(gè)(8761、8762、8763、8764)。如果8761對應(yīng)的Eureka Server服務(wù)的狀態(tài)是UP,那么Eureka Client向該注冊中心注冊成功后,不會(huì)再向(8762、8763、8764)對應(yīng)的Eureka Server發(fā)起注冊請求(對應(yīng)程序是在for循環(huán)中直接return respones)。

說到這里又引出來另外一個(gè)問題,如果8761這個(gè)Eureka Server是down掉的呢?

根據(jù)源碼我們可知Eureka Client首次會(huì)向8761這個(gè)Server發(fā)起注冊請求,如果該Server的狀態(tài)是down,那么它會(huì)將該Server保存到quarantineSet這個(gè)Set集合中,然后再次訪問8762這個(gè)Eureka Server,如果8762這個(gè)Server的狀態(tài)依舊是down,它也會(huì)把這個(gè)Server保存到quarantineSet這個(gè)Set集合中,然后繼續(xù)訪問8763這個(gè)Server,如果8763這個(gè)Server的狀態(tài)依舊是down,此時(shí)除了會(huì)將其保存到quarantineSet這個(gè)Set集合中之外,還會(huì)跳出本次循環(huán)。從而結(jié)束此次注冊過程。

說道這里有人要問接下來會(huì)不會(huì)向8764這個(gè)Server發(fā)起注冊,答案是否定的,因?yàn)檠h(huán)的次數(shù)默認(rèn)是3次。所以即使8764這個(gè)Server的狀態(tài)是UP,它也不會(huì)接收到來自Eureka Client發(fā)起的注冊信息。

Eureka Client向Eureka Server發(fā)起注冊信息的過程除了在Eureka Client啟動(dòng)的時(shí)候觸發(fā),還有另外一種方式,就是后臺(tái)定時(shí)任務(wù)。
假設(shè)我們上面描述的場景是在Eureka Client啟動(dòng)的時(shí)候,因?yàn)樵趩?dòng)的時(shí)候注冊這個(gè)過程全部失敗了,當(dāng)后臺(tái)定時(shí)任務(wù)執(zhí)行時(shí),還會(huì)進(jìn)入該注冊流程。注意此時(shí)quarantineSet的值為3(8761、8762、8763之前注冊失敗的Eureka Server)。
所以當(dāng)程序再次進(jìn)入getHostCandidates()方法時(shí),if (quarantineSet.isEmpty())這個(gè)方法是不滿足的,接下來會(huì)走else if (quarantineSet.size() >= threshold)這個(gè)判斷,如果這個(gè)判斷成立,那么會(huì)將quarantineSet集合清空,同時(shí)返回全量的Eureka Server列表,如果這個(gè)判斷不成立,會(huì)拿quarantineSet集合中保存的內(nèi)容去過濾Eureka Server的全量列表。以本文為例:

  • quarantineSet中保存的是(8761、8762、8763)三個(gè)Eureka Server
  • Eureka Server全量列表的內(nèi)容是(8761、8762、8763、8764)四個(gè)Eureka Server,過濾后返回的結(jié)果為8764這個(gè)Eureka Server。

在本文的例子中8761、8762、8763這三個(gè)Eureka Server的狀態(tài)是down而8764這個(gè)Eureka Server的狀態(tài)是UP,我們其實(shí)是想走到最后的else分支,從而完成過濾操作,并最終得到8764這個(gè)Server,遺憾的是它并不會(huì)走到這個(gè)分支,而是被上面的else if (quarantineSet.size() >= threshold)這個(gè)分支所攔截,返回的依舊是全量的Eureka Server列表。這樣造成的后果就是Eureka Client依舊會(huì)依次向(8761、8762、8763)這三個(gè)down的Eureka Server發(fā)起注冊請求。
那么問題的關(guān)鍵在哪里呢?問題的關(guān)鍵就是threshold這個(gè)值的由來,因?yàn)榇藭r(shí)quarantineSet.size()的值為3,而3這個(gè)值大于threshold,從而導(dǎo)致,會(huì)將quarantineSet集合清空,返回全量的Server列表。
我們知道threshold這個(gè)值是根據(jù)全量的Eureka Server列表乘以一個(gè)可配置的參數(shù)計(jì)算出來的,在本文的例子當(dāng)中,我的properties文件中除了defaultZone之外并沒有配置這個(gè)參數(shù),那么也就是說這個(gè)參數(shù)是有默認(rèn)值的,通過源碼我們了解到,這個(gè)默認(rèn)值是0.66。具體源碼如下:

final class PropertyBasedTransportConfigConstants {
/**
*省略部分源碼
*/
static class Values {
static final int SESSION_RECONNECT_INTERVAL = 20*60;
//默認(rèn)值為0.66
static final double QUARANTINE_REFRESH_PERCENTAGE = 0.66;
static final int DATA_STALENESS_TRHESHOLD = 5*60;
static final int ASYNC_RESOLVER_REFRESH_INTERVAL = 5*60*1000;
static final int ASYNC_RESOLVER_WARMUP_TIMEOUT = 5000;
static final int ASYNC_EXECUTOR_THREADPOOL_SIZE = 5;
}
}
/**
*@return the percentage of the full endpoints set above which the
*quarantine set is cleared in the range [0, 1.0]
*/
double getRetryableClientQuarantineRefreshPercentage();

看到這里就不難理解了,因?yàn)檫@個(gè)值是0.66而此時(shí)全量的Eureka Server值為4。計(jì)算之后的值為2,而由于注冊的for循環(huán)為3次,所以當(dāng)?shù)诙伟l(fā)起注冊流程的時(shí)候quarantineSet的值始終大于threshold。這樣就會(huì)導(dǎo)致一個(gè)問題,就是如果8761、8762、8763一直是down即使8764一直是好的,那么Eureka Client也不會(huì)注冊成功。而且這個(gè)參數(shù)值的區(qū)間為0到1.

既然通過源碼分析我們找到了問題根源,其實(shí)對應(yīng)的我們也找到了解決這個(gè)問題的辦法,就是對應(yīng)把這個(gè)參數(shù)值調(diào)大些。
這個(gè)值在properties中對應(yīng)的寫法如下:

eureka.client.transport.retryableClientQuarantineRefreshPercentage = xxx

接下來我們修改下properties文件,修改后的內(nèi)容如下:

eureka.client.service-url.defaultZone=
http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka,http://localhost:8764/eureka
eureka.client.transport.retryableClientQuarantineRefreshPercentage=1

接下來按照這個(gè)配置再次回顧下上面的流程:

  • Eureka Client啟動(dòng)時(shí)進(jìn)行注冊(8761、8762、8763的狀態(tài)是down),所以此時(shí)quarantineSet的值為3.
  • 接下來在定時(shí)任務(wù)中又觸發(fā)注冊事件,此時(shí)因?yàn)閰?shù)的值從0.66調(diào)整為1。所以計(jì)算出的threshold的值為4。而此時(shí)quarantineSet的值為3。所以不會(huì)進(jìn)入到else if (quarantineSet.size() >= threshold)分支,而是會(huì)進(jìn)入最后的esle分支。
  • 在else分支中會(huì)完成過濾功能,最終返回的list中的結(jié)果只有一個(gè)就是8764這個(gè)Eureka Server。
  • Eureka Client向8764這個(gè)Eureka Server發(fā)起注冊請求,得到成功相應(yīng),并返回。

遺留問題

說道這里我們感覺好像是解決了這個(gè)問題,那么問一個(gè)問題,這個(gè)參數(shù)值可以設(shè)置的無限大嗎?

比如我將這個(gè)參數(shù)值設(shè)置為10,雖然javaDoc中說明這個(gè)參數(shù)值的范圍在0-1之間,但是并沒有說明如果將這個(gè)參數(shù)調(diào)整大于1會(huì)出現(xiàn)什么情況。接下來按照上面的流程我們分析下:
之前我們分析的流程中的前提是8761、8762、8763這三臺(tái)Server的狀態(tài)是down而8764這個(gè)server的狀態(tài)是up,現(xiàn)在我們修改下這個(gè)前提。
假設(shè)一開始8761、8762、8763、8764這四臺(tái)Eureka Server的狀態(tài)都是down。

  • Eureka Client啟動(dòng)時(shí)進(jìn)行注冊(8761、8762、8763的狀態(tài)是down),所以此時(shí)quarantineSet的值為3.
  • 接下來在定時(shí)任務(wù)中又觸發(fā)注冊事件,此時(shí)因?yàn)閰?shù)的值從0.66調(diào)整為10。所以計(jì)算出的threshold的值為40。而此時(shí)quarantineSet的值為3。所以不會(huì)進(jìn)入到else if (quarantineSet.size() >= threshold)分支,而是會(huì)進(jìn)入最后的esle分支。
  • 在else分支中會(huì)完成過濾功能,最終返回的list中的結(jié)果只有一個(gè)就是8764這個(gè)Eureka Server。
  • Eureka Client向8764這個(gè)Eureka Server發(fā)起注冊請求,因?yàn)榇藭r(shí)8764的狀態(tài)也是down導(dǎo)致注冊失敗,此時(shí)quarantineSet中的內(nèi)容是(8761、8762、8763、8764)
  • 當(dāng)定時(shí)任務(wù)再次觸發(fā)時(shí)if (quarantineSet.isEmpty())這個(gè)分支不會(huì)進(jìn)入,因?yàn)榇藭r(shí)quarantineSet的值為4
  • else if (quarantineSet.size() >= threshold)這分支也不會(huì)進(jìn)入因?yàn)閠hreshold的值為40
  • 最終會(huì)進(jìn)入else分支,這個(gè)分支原本的含義是想通過quarantineSet來充當(dāng)過濾器,從全量的Eureka Server中過濾掉之前狀態(tài)為down的Eureka Server,但是由于quarantineSet的值現(xiàn)在已經(jīng)是全量,導(dǎo)致過濾后的結(jié)果返回的是一個(gè)空的list。即使此時(shí)Eureka Server列表(8761、8762、8763、8764)任何一個(gè)Server的狀態(tài)變?yōu)閁P,該Eureka Client也不可能完成注冊事件。

解決辦法

上面出現(xiàn)的那個(gè)問題,根本原因個(gè)人認(rèn)為是由于eureka.client.transport.retryableClientQuarantineRefreshPercentage參數(shù)過大而源碼中沒有校驗(yàn),從而導(dǎo)致沒有進(jìn)入else if (quarantineSet.size() >= threshold)的邏輯分支,因?yàn)榇藭r(shí)如果quarantineSet中的值已經(jīng)達(dá)到了所有Eureka Server列表,那么此時(shí)我們希望的是將這個(gè)Set集合清空,從而再次返回全量的Eureka Server列表,也就是說再重新來一次注冊流程。
所以基于上面的分析,個(gè)人認(rèn)為在源碼的getHostCandidates增加下校驗(yàn),具體代碼如下:

private List<EurekaEndpoint> getHostCandidates() {
List<EurekaEndpoint> candidateHosts = clusterResolver.getClusterEndpoints();
quarantineSet.retainAll(candidateHosts);

// If enough hosts are bad, we have no choice but start over again
int threshold = (int) (candidateHosts.size() * transportConfig.getRetryableClientQuarantineRefreshPercentage());

/**
* 增加判斷如果threshold的值過大,即超過Eureka Server
* 列表的數(shù)量,那么將其再次賦值,賦值的內(nèi)容為Eureka Server
* 列表的數(shù)量。
*/
if (threshold > candidateHosts.size()) {
threshold = candidateHosts.size();
}

if (quarantineSet.isEmpty()) {
// no-op
} else if (quarantineSet.size() >= threshold) {
logger.debug("Clearing quarantined list of size {}", quarantineSet.size());
quarantineSet.clear();
} else {
List<EurekaEndpoint> remainingHosts = new ArrayList<>(candidateHosts.size());
for (EurekaEndpoint endpoint : candidateHosts) {
if (!quarantineSet.contains(endpoint)) {
remainingHosts.add(endpoint);
}
}
candidateHosts = remainingHosts;
}

return candidateHosts;
}

以上內(nèi)容就是個(gè)人對eureka.client.transport.retryableClientQuarantineRefreshPercentage的理解,由于本人知識(shí)水平有限,對此問題也可能理解不正確,還請大家多多留言討論。
這個(gè)問題本人也在Eureka官方gitHub提交iussue,具體內(nèi)容如下:https://github.com/Netflix/eureka/issues/1012

最后感謝spring4all社區(qū)提供這個(gè)平臺(tái),能讓大家交流學(xué)習(xí)Spring相關(guān)知識(shí)。


總結(jié)

以上是生活随笔為你收集整理的Eureka Client注册到Eureka Server的秘密的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。