簡介
lastDirtyTimestamp在Eureka中承載了比較重要的作用骗灶,在續(xù)約贱鄙,設(shè)置覆蓋狀態(tài)螃征,刪除覆蓋狀態(tài)的
時候都有用到蝇狼。
定義: 實例的最后修改時間
Eureka Client
EurekaClient在系統(tǒng)啟動的時候,會啟動一個定時任務(wù)抗果,每40秒執(zhí)行一次筋帖,該定時任務(wù)負(fù)責(zé)比對
客戶端的信息,如果發(fā)生改變則更新lastDirtyTimestamp的值冤馏,同時對Eureka Server 重新發(fā)起注冊日麸。
public void run() {
try {
// 該方法負(fù)責(zé)比對客戶端存在的instance信息和實際的信息,是否發(fā)生改變
// 比如: 客戶端的狀態(tài)逮光,IP代箭,配置信息發(fā)生改變
discoveryClient.refreshInstanceInfo();
// 判斷信息是否改變,是否需要重新注冊涕刚。通過isInstanceInfoDirty這個布爾值判斷
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
// 注冊
discoveryClient.register();
// 取消
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
步驟說明:
1.判斷客戶端的狀態(tài)梢卸,IP,配置信息是否發(fā)生改變副女,如果發(fā)生改變則會調(diào)用setIsDirty() 蛤高, 設(shè)置最后更新時間
lastDirtyTimestamp , 同時設(shè)置isInstanceInfoDirty = true
public synchronized void setIsDirty() {
isInstanceInfoDirty = true;
lastDirtyTimestamp = System.currentTimeMillis();
}
- 判斷isInstanceInfoDirty 是否為true碑幅, 是的話戴陡,則返回最后修改時間
public synchronized Long isDirtyWithTime() {
if (isInstanceInfoDirty) {
return lastDirtyTimestamp;
} else {
return null;
}
}
- 如果isDirtyWithTime()不為null, 則需要重新發(fā)起注冊
4.卸載isInstanceInfoDirty 的狀態(tài)沟涨,修改為false
接下來重點講一下refreshInstanceInfo方法恤批。
void refreshInstanceInfo() {
// 1. 判斷數(shù)據(jù)中心的數(shù)據(jù)是否發(fā)生改變,
applicationInfoManager.refreshDataCenterInfoIfRequired();
// 2.判斷配置是否發(fā)生改變
applicationInfoManager.refreshLeaseInfoIfRequired();
InstanceStatus status;
try {
// 通過健康檢查器裹赴,獲取應(yīng)用的最新狀態(tài)
status = getHealthCheckHandler().getStatus(instanceInfo.getStatus());
} catch (Exception e) {
logger.warn("Exception from healthcheckHandler.getStatus, setting status to DOWN", e);
status = InstanceStatus.DOWN;
}
if (null != status) {
// 設(shè)置狀態(tài)
applicationInfoManager.setInstanceStatus(status);
}
}
步驟說明:
1.refreshDataCenterInfoIfRequired()方法喜庞,里面主要是判斷應(yīng)用的IP地址是否發(fā)生改變诀浪。
public void refreshDataCenterInfoIfRequired() {
// 獲取當(dāng)前的地址
String existingAddress = instanceInfo.getHostName();
// 獲取當(dāng)前實際的地址
String newAddress;
if (config instanceof RefreshableInstanceConfig) {
newAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(true);
} else {
newAddress = config.getHostName(true);
}
String newIp = config.getIpAddress();
// 判斷新舊地址是否一致,如果不一致延都,則進(jìn)入if結(jié)構(gòu)
if (newAddress != null && !newAddress.equals(existingAddress)) {
logger.warn("The address changed from : {} => {}", existingAddress, newAddress);
InstanceInfo.Builder builder = new InstanceInfo.Builder(instanceInfo);
builder.setHostName(newAddress).setIPAddr(newIp).setDataCenterInfo(config.getDataCenterInfo());
// 該方法最為重要雷猪,表示信息已經(jīng)發(fā)生改變,
instanceInfo.setIsDirty();
}
}
// InstanceInfo.java
public synchronized void setIsDirty() {
isInstanceInfoDirty = true;
lastDirtyTimestamp = System.currentTimeMillis();
}
如上代碼所示晰房,如果hostName發(fā)生改變求摇,則會更新本地的Instance的信息,同時調(diào)用setIsDirty()方法殊者,表示信息已經(jīng)被改變与境。
更新isInstanceInfoDirty = true ,同時設(shè)置lastDirtyTimestamp 為系統(tǒng)當(dāng)前時間
2.refreshLeaseInfoIfRequired() 判斷配置中的猖吴,“租約過期時間” 摔刁, “續(xù)約時間” 是否發(fā)生改變,如果發(fā)生改變了海蔽,那么就需要
更新本地instance的信息共屈,同時調(diào)用setIsDirty()方法表示信息已經(jīng)被改變,需要重新注冊
public void refreshLeaseInfoIfRequired() {
LeaseInfo leaseInfo = instanceInfo.getLeaseInfo();
if (leaseInfo == null) {
return;
}
// 租約過期時間准潭,默認(rèn)90秒
int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds();
// 續(xù)約時間趁俊,默認(rèn)30秒
int currentLeaseRenewal = config.getLeaseRenewalIntervalInSeconds();
// 判斷時間是否一致
if (leaseInfo.getDurationInSecs() != currentLeaseDuration || leaseInfo.getRenewalIntervalInSecs() != currentLeaseRenewal) {
LeaseInfo newLeaseInfo = LeaseInfo.Builder.newBuilder()
.setRenewalIntervalInSecs(currentLeaseRenewal)
.setDurationInSecs(currentLeaseDuration)
.build();
instanceInfo.setLeaseInfo(newLeaseInfo);
// 該方法最為重要域仇,表示信息已經(jīng)發(fā)生改變刑然,
instanceInfo.setIsDirty();
}
}
- 調(diào)用健康檢查器,獲取當(dāng)前的instance的狀態(tài)com.netflix.appinfo.HealthCheckCallbackToHandlerBridge
public class HealthCheckCallbackToHandlerBridge implements HealthCheckHandler {
private final HealthCheckCallback callback;
public HealthCheckCallbackToHandlerBridge() {
callback = null;
}
public HealthCheckCallbackToHandlerBridge(HealthCheckCallback callback) {
this.callback = callback;
}
@Override
public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus) {
// 判斷instance的狀態(tài)暇务。
if (null == callback || InstanceInfo.InstanceStatus.STARTING == currentStatus
|| InstanceInfo.InstanceStatus.OUT_OF_SERVICE == currentStatus) { // Do not go to healthcheck handler if the status is starting or OOS.
return currentStatus;
}
return callback.isHealthy() ? InstanceInfo.InstanceStatus.UP : InstanceInfo.InstanceStatus.DOWN;
}
}
由上可知泼掠, 健康檢查器中的getStatus方法,判斷步驟
判斷callback是否為空 垦细, 如果為空择镇,則以當(dāng)前實例的狀態(tài)為準(zhǔn)(默認(rèn)為null , 如果我們想自己實現(xiàn)自定義的健康檢查括改,可以設(shè)置起來)
判斷傳入的當(dāng)前實例的狀態(tài)是否等于STARTING腻豌, OUT_OF_SERVICE 這兩個狀態(tài),如果等于嘱能,則以當(dāng)前實例的狀態(tài)為準(zhǔn)
使用callback.isHealty()判斷實例的健康狀態(tài)吝梅,然后返回UP或則DOWN
- 調(diào)用applicationInfoManager.setInstanceStatus(status); , 設(shè)置實例的狀態(tài)惹骂,如果傳入的狀態(tài)和實例的狀態(tài)一直苏携,則不會修改。
如果狀態(tài)不一致对粪,則會修改instance的狀態(tài)右冻,同時調(diào)用setIsDirty() 表示信息發(fā)生改變装蓬。
public synchronized InstanceStatus setStatus(InstanceStatus status) {
// 判斷狀態(tài)是否一致,一致則不更新
if (this.status != status) {
InstanceStatus prev = this.status;
this.status = status;
//調(diào)用該方法纱扭,表示信息發(fā)生修改
setIsDirty();
return prev;
}
return null;
}
綜合以上代碼分析牍帚,我們可以發(fā)現(xiàn),當(dāng)客戶端的信息發(fā)生任何改變的時候跪但,都會調(diào)用setIsDirty() , 更新isInstanceInfoDirty = true 履羞,
同時設(shè)置lastDirtyTimestamp 為系統(tǒng)當(dāng)前時間 , 由此可見屡久,lastDirtyTimestamp 的定義為“實例的最后修改時間”
Eureka Server 用途
服務(wù)端在接收renew 忆首, stateUpdate, deleteStatusUpdate 的時候,都會要求客戶端傳入lastDirtyTimestamp 這個參數(shù) 被环,注冊的時候也會對這個值做對比
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
// ......
return response;
}
@Path("status")
public Response statusUpdate(
@QueryParam("value") String newStatus,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
// ......
}
@DELETE
@Path("status")
public Response deleteStatusUpdate(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("value") String newStatusValue,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
// ......
}
以上三個方法中糙及,當(dāng)服務(wù)端檢測到本地的instanceStatue需要更新的時候,也會更新Eureka Server本地的lastDirtyTimestamp 筛欢,下面針對
續(xù)約和注冊講一下在其中的用法浸锨。
續(xù)約
renew續(xù)約完成之后,會判斷傳入的lastDirtyTimestamp 和客戶端本地的lastDirtyTimestamp 是否一致版姑,如果客戶端的值大柱搜,那么就會返回404錯誤,客戶端就需要重新注冊了剥险, 具體機制可以查看深入理解Eureka 心跳續(xù)約(三)中的Eureka Server接收心跳那一小結(jié)聪蘸。
注冊
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
// .... 省略代碼
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
//如果Eureka Server中該實例已經(jīng)存在
if (existingLease != null && (existingLease.getHolder() != null)) {
// 比較lastDirtyTimestamp , 以lastDirtyTimestamp大的為準(zhǔn)
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
registrant = existingLease.getHolder();
}
}
// .... 省略代碼
} finally {
read.unlock();
}
}