我的博客他巨,原文出處http://www.deepinblog.com/eureka/175/
先直接給出配置讓你嘗鮮
EurekaServer端配置
eureka:
server:
#Eureka Server會(huì)定時(shí)(間隔值是eureka.server.eviction-interval-timer-in-ms蒂秘,默認(rèn)60s)進(jìn)行檢查,如果發(fā)現(xiàn)實(shí)例在在一定時(shí)間
#(此值由客戶端設(shè)置的eureka.instance.lease-expiration-duration-in-seconds定義,默認(rèn)值為90s)內(nèi)沒有收到心跳淘太,則會(huì)注銷此實(shí)例姻僧。
#我們這里配置每秒鐘去檢測(cè)一次规丽,驅(qū)除失效的實(shí)例
eviction-interval-timer-in-ms: 1000
#關(guān)閉一級(jí)緩存,讓客戶端直接從二級(jí)緩存去讀取撇贺,省去各緩存之間的同步的時(shí)間
use-read-only-response-cache: false
EurekaClient端(應(yīng)用端)配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# EurekaClient每隔多久從EurekaServer拉取一次服務(wù)列表赌莺,默認(rèn)30秒,這里修改為2秒鐘從注冊(cè)中心拉取一次
registry-fetch-interval-seconds: 2
#租約期限以及續(xù)約期限的配置
instance:
#租約到期松嘶,服務(wù)失效時(shí)間雄嚣,默認(rèn)值90秒,服務(wù)超過90秒沒有發(fā)生心跳喘蟆,EurekaServer會(huì)將服務(wù)從列表移除
#這里修改為6秒
lease-expiration-duration-in-seconds: 6
#租約續(xù)約間隔時(shí)間缓升,默認(rèn)30秒,這里修改為3秒鐘
lease-renewal-interval-in-seconds: 3
#這里是Ribbon緩存實(shí)例列表的刷新間隔蕴轨,默認(rèn)30秒鐘港谊,這里修改為每秒鐘刷新一次實(shí)例信息
ribbon:
ServerListRefreshInterval: 1000
下面給你剖析原理
Eureka服務(wù)端詳解
服務(wù)端緩存
如圖所示
服務(wù)注冊(cè)到注冊(cè)中心后,服務(wù)實(shí)例信息是存儲(chǔ)在注冊(cè)表中的橙弱,也就是內(nèi)存中歧寺。但Eureka為了提高響應(yīng)速度,在內(nèi)部做了優(yōu)化棘脐,加入了兩層的緩存結(jié)構(gòu)斜筐,將Client需要的實(shí)例信息,直接緩存起來蛀缝,獲取的時(shí)候 直接從緩存中拿數(shù)據(jù)然后響應(yīng)給 Client顷链。
第一層緩存是readOnlyCacheMap,readOnlyCacheMap是采用ConcurrentHashMap來存儲(chǔ)數(shù)據(jù)的屈梁,主要負(fù)責(zé)定時(shí)與readWriteCacheMap進(jìn)行數(shù)據(jù)同步嗤练,默認(rèn)同 步時(shí)間為 30 秒一次。
第二層緩存是readWriteCacheMap在讶,readWriteCacheMap采用Guava來實(shí)現(xiàn)緩存煞抬。緩存過期時(shí)間默認(rèn)為180秒,當(dāng)服務(wù)下線构哺、過期革答、注冊(cè)、狀態(tài)變更等操作都會(huì)清除此緩存中的數(shù)據(jù)曙强。
第三層是數(shù)據(jù)存儲(chǔ)層残拐。
Client獲取服務(wù)實(shí)例數(shù)據(jù)時(shí),會(huì)先從一級(jí)緩存中獲取旗扑,如果一級(jí)緩存中不存在蹦骑,再從二級(jí)緩存中獲取,如果二級(jí)緩存也不存在臀防,會(huì)觸發(fā)緩存的加載眠菇,從存儲(chǔ)層拉取數(shù)據(jù)到緩存中边败,然后再返回給 Client。
Eureka 之所以設(shè)計(jì)二級(jí)緩存機(jī)制捎废,也是為了提高 Eureka Server 的響應(yīng)速度笑窜,缺點(diǎn)是緩存會(huì)導(dǎo)致 Client 獲取不到最新的服務(wù)實(shí)例信息,然后導(dǎo)致無法快速發(fā)現(xiàn)新的服務(wù)和已下線的服務(wù)登疗。
了解了服務(wù)端的實(shí)現(xiàn)后排截,想要解決這個(gè)問題就變得很簡(jiǎn)單了,我們可以縮短只讀緩存的更新時(shí)間 (eureka.server.response-cache-update-interval-ms)讓服務(wù)發(fā)現(xiàn)變得更加及時(shí)辐益,或者直接將只讀緩 存關(guān)閉(eureka.server.use-read-only-response-cache=false)断傲,多級(jí)緩存也導(dǎo)致Client層面(數(shù)據(jù)一致性)很薄弱。
客戶端緩存
客戶端緩存主要分為兩塊內(nèi)容智政,一塊是 Eureka Client 緩存认罩,一塊是 Ribbon 緩存。
Eureka Client緩存 续捂,EurekaClient負(fù)責(zé)跟EurekaServer進(jìn)行交互垦垂,在EurekaClient中的 com.netflix.discovery.DiscoveryClient.initScheduledTasks() 方法中,初始化了一個(gè) CacheRefreshThread 定時(shí)任務(wù)專?用來拉取 Eureka Server 的實(shí)例信息到本地牙瓢。所以我們需要縮短這個(gè)定時(shí)拉取服務(wù)信息的時(shí)間間隔(此值在客戶端配置eureka.client.registryFetchIntervalSeconds) 來快速發(fā)現(xiàn)新的服務(wù)
Ribbon緩存劫拗,Ribbon會(huì)從EurekaClient中獲取服務(wù)信息,ServerListUpdater是Ribbon中負(fù)責(zé)服務(wù)實(shí)例 更新的組件矾克,默認(rèn)的實(shí)現(xiàn)是PollingServerListUpdater页慷,通過線程定時(shí)去更新實(shí)例信息。定時(shí)刷新的時(shí) 間間隔默認(rèn)是30秒聂渊,當(dāng)服務(wù)停止或者上線后差购,這邊最快也需要30秒才能將實(shí)例信息更新成最新的。我們可以將這個(gè)時(shí)間調(diào)短一點(diǎn)汉嗽,比如 3 秒。
刷新間隔的參數(shù)是通過 getRefreshIntervalMs 方法來獲取的找蜜,方法中的邏輯也是從 Ribbon的配置中進(jìn)行取值的饼暑。所以我們需要縮短這個(gè)更新間隔(此值在客戶端配置ribbon.ServerListRefreshInterval)來快速的更新Ribbon緩存實(shí)例列表
將這些服務(wù)端緩存和客戶端緩存的時(shí)間全部縮短后,跟默認(rèn)的配置時(shí)間相比洗做,快了很多弓叛。我們通過調(diào)整 參數(shù)的方式來盡量加快服務(wù)發(fā)現(xiàn)的速度,但是還是不能完全解決報(bào)錯(cuò)的問題诚纸,間隔時(shí)間設(shè)置為3秒撰筷,也還是會(huì)有間隔。所以我們一般都會(huì)開啟重試功能畦徘,當(dāng)路由的服務(wù)出現(xiàn)問題時(shí)毕籽,可以重試到另一個(gè)服務(wù)來 保證這次請(qǐng)求的成功抬闯。
服務(wù)端緩存部分源碼如下:
/**
*The class that is responsible for caching registry information that will be
*queried by the clients.
*/
public class ResponseCacheImpl implements ResponseCache {
//一級(jí)緩存
private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();
//二級(jí)緩存(Guava實(shí)現(xiàn))
private final LoadingCache<Key, Value> readWriteCacheMap;
//數(shù)據(jù)存儲(chǔ)層
private final AbstractInstanceRegistry registry;
}
客戶端緩存更新部分源碼如下:
Eureka Client緩存刷新部分源碼
//Eureka Client緩存刷新部分源碼
/**
* Initializes all scheduled tasks.
* 在實(shí)例化com.netflix.discovery.DiscoveryClient被調(diào)用
*/
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
//從哪個(gè)Eureka客戶端拉取實(shí)例列表的間隔時(shí)間
//通過eureka.client.registryFetchIntervalSeconds可配置
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
//執(zhí)行刷新的Runable定時(shí)任務(wù)
new CacheRefreshThread()
),
//從哪個(gè)Eureka客戶端拉取實(shí)例列表的間隔時(shí)間
//通過eureka.client.registryFetchIntervalSeconds可配置
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
Ribbon緩存刷新部分源碼
//PollingServerListUpdater,Ribbon緩存刷新部分源碼
@Override
public synchronized void start(final UpdateAction updateAction) {
if (isActive.compareAndSet(false, true)) {
final Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
if (!isActive.get()) {
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
return;
}
try {
updateAction.doUpdate();
lastUpdated = System.currentTimeMillis();
} catch (Exception e) {
logger.warn("Failed one update cycle", e);
}
}
};
scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
wrapperRunnable,
initialDelayMs,
refreshIntervalMs,//默認(rèn)30秒,通過ribbon.ServerListRefreshInterval來配置更小的值來快速更新Ribbon實(shí)例緩存
TimeUnit.MILLISECONDS
);
} else {
logger.info("Already active, no-op");
}
}
服務(wù)下線
- 當(dāng)服務(wù)正常關(guān)閉操作時(shí)关筒,會(huì)發(fā)送服務(wù)下線的REST請(qǐng)求給EurekaServer溶握。
- 服務(wù)中心接受到請(qǐng)求后,將該服務(wù)置為下線狀態(tài)
失效剔除
Eureka Server 中會(huì)有定時(shí)任務(wù)去檢測(cè)失效的服務(wù)蒸播,將服務(wù)實(shí)例信息從注冊(cè)表中移除睡榆,也可以將這個(gè)失效檢測(cè)的時(shí)間縮短,這樣服務(wù)下線后就能夠及時(shí)從注冊(cè)表中清除袍榆。
- Eureka Server會(huì)定時(shí)(間隔值是eureka.server.eviction-interval-timer-in-ms胀屿,默認(rèn)60s)進(jìn)行檢查,
如果發(fā)現(xiàn)實(shí)例在在一定時(shí)間
(此值由客戶端設(shè)置的eureka.instance.lease-expiration-duration-in-seconds定義包雀,默認(rèn)值為90s)內(nèi)沒有收到心跳碉纳,
則會(huì)注銷此實(shí)例。
Eureka客戶端
服務(wù)提供者(也是Eureka客戶端)
- 服務(wù)提供者(也是Eureka客戶端)要向EurekaServer注冊(cè)服務(wù)馏艾,并完成服務(wù)續(xù)約等工作
- 服務(wù)注冊(cè)詳解(服務(wù)提供者)
- 當(dāng)我們導(dǎo)入了eureka-client依賴坐標(biāo)劳曹,配置Eureka服務(wù)注冊(cè)中心地址
- 服務(wù)在啟動(dòng)時(shí)會(huì)向注冊(cè)中心發(fā)起注冊(cè)請(qǐng)求,攜帶服務(wù)元數(shù)據(jù)信息
- Eureka注冊(cè)中心會(huì)把服務(wù)的信息保存在Map中琅摩。
- 服務(wù)續(xù)約詳解(服務(wù)提供者)
- 服務(wù)每隔30秒會(huì)向注冊(cè)中心續(xù)約(心跳)一次(也稱為報(bào)活)铁孵,如果沒有續(xù)約,租約在90秒后到期房资,然后服務(wù)會(huì)被失效蜕劝。
每隔30秒的續(xù)約操作我們稱之為心跳檢測(cè)往往不需要我們調(diào)整這兩個(gè)配置
- 服務(wù)每隔30秒會(huì)向注冊(cè)中心續(xù)約(心跳)一次(也稱為報(bào)活)铁孵,如果沒有續(xù)約,租約在90秒后到期房资,然后服務(wù)會(huì)被失效蜕劝。
#向Eureka服務(wù)中心集群注冊(cè)服務(wù)
eureka:
#租約期限以及續(xù)約期限的配置
instance:
#租約到期,服務(wù)失效時(shí)間轰异,默認(rèn)值90秒岖沛,服務(wù)超過90秒沒有發(fā)生心跳,EurekaServer會(huì)將服務(wù)從列表移除
#這里修改為6秒
lease-expiration-duration-in-seconds: 6
#租約續(xù)約間隔時(shí)間搭独,默認(rèn)30秒婴削,這里修改為3秒鐘
lease-renewal-interval-in-seconds: 3
服務(wù)消費(fèi)者(也是Eureka客戶端)
- 服務(wù)消費(fèi)者每隔30秒服務(wù)會(huì)從注冊(cè)中心中拉取一份服務(wù)列表,這個(gè)時(shí)間可以通過配置修改牙肝。往往不需要我們調(diào)整
- 服務(wù)消費(fèi)者啟動(dòng)時(shí)唉俗,從 EurekaServer服務(wù)列表獲取只讀備份,緩存到本地;
- 默認(rèn)每隔30秒配椭,會(huì)重新獲取并更新數(shù)據(jù)虫溜;
- 每隔30秒的時(shí)間可以通過配置eureka.client.registry-fetch-interval-seconds修改,如下股缸。
#向Eureka服務(wù)中心集群注冊(cè)服務(wù)
eureka:
client:
# EurekaClient每隔多久從EurekaServer拉取一次服務(wù)列表衡楞,默認(rèn)30秒,這里修改為2秒鐘從注冊(cè)中心拉取一次
registry-fetch-interval-seconds: 2
客戶端緩存見Eureka服務(wù)端詳情章節(jié)
至此您應(yīng)該明白Eureka的服務(wù)發(fā)現(xiàn)機(jī)制了吧
最后再說說Eureka自我保護(hù)機(jī)制
服務(wù)提供者 —> 注冊(cè)中心
- 定期的續(xù)約(服務(wù)提供者和注冊(cè)中心通信)敦姻,假如服務(wù)提供者和注冊(cè)中心之間的網(wǎng)絡(luò)有點(diǎn)問題瘾境,
不代表 服務(wù)提供者不可用歧杏,不代表服務(wù)消費(fèi)者無法訪問服務(wù)提供者,所以有自我保護(hù)的機(jī)制 - 如果在15分鐘內(nèi)超過85%的客戶端節(jié)點(diǎn)都沒有正常的心跳寄雀,那么Eureka就認(rèn)為客戶端與注冊(cè)中心出現(xiàn)了網(wǎng)絡(luò)故障得滤,
Eureka Server自動(dòng)進(jìn)入自我保護(hù)機(jī)制。 - 為什么會(huì)有自我保護(hù)機(jī)制?
- 默認(rèn)情況下盒犹,如果Eureka Server在一定時(shí)間內(nèi)(默認(rèn)90秒)沒有接收到某個(gè)微服務(wù)實(shí)例的心跳懂更, Eureka Server將會(huì)移除該實(shí)例。
但是當(dāng)網(wǎng)絡(luò)分區(qū)故障發(fā)生時(shí)急膀,微服務(wù)與Eureka Server之間無法正常通信沮协,而微服務(wù)本身是正常運(yùn)行的,此時(shí)不應(yīng)該移除這個(gè)微服務(wù)卓嫂,
所以引入了自我保護(hù)機(jī)制慷暂。
- 默認(rèn)情況下盒犹,如果Eureka Server在一定時(shí)間內(nèi)(默認(rèn)90秒)沒有接收到某個(gè)微服務(wù)實(shí)例的心跳懂更, Eureka Server將會(huì)移除該實(shí)例。
服務(wù)中心?面會(huì)顯示如下提示信息
當(dāng)處于自我保護(hù)模式時(shí)
- 不會(huì)剔除任何服務(wù)實(shí)例(可能是服務(wù)提供者和EurekaServer之間網(wǎng)絡(luò)問題),保證了大多數(shù)服務(wù)依 然可用
- Eureka Server仍然能夠接受新服務(wù)的注冊(cè)和查詢請(qǐng)求晨雳,但是不會(huì)被同步到其它節(jié)點(diǎn)上行瑞,保證當(dāng)前節(jié)點(diǎn)依然可用,
當(dāng)網(wǎng)絡(luò)穩(wěn)定時(shí)餐禁,當(dāng)前Eureka Server新的注冊(cè)信息會(huì)被同步到其它節(jié)點(diǎn)中血久。 - 在Eureka Server工程中通過eureka.server.enable-self-preservation配置可用關(guān)停自我保護(hù),默認(rèn)值是打開
eureka:
server:
enable-self-preservation: false # 關(guān)閉自我保護(hù)模式(缺省為打開)