eureka instanceのlastDirtyTimestampについて
本文は主にeureka instanceのlastDirtyTimestampを研究する
serverエンド
すなわち、クライアント側で最後にinstanceが変更されたタイムスタンプ
Instanceのインタフェースでは、metaの更新やcancelLease操作のほか、eurka-core-1.8.8-sourcesなどのlastDirtyTimestampを追加します.jar!/com/netflix/eureka/resources/InstanceResource.java
@PUT
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
//......
}
@PUT
@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) {
//......
}
renewLeaseを除いて、他の2つのlastDirtyTimestampは伝達用にしか使われていません.
renewLease
@PUT
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
boolean isFromReplicaNode = "true".equals(isReplication);
boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
// Not found in the registry, immediately ask for a register
if (!isSuccess) {
logger.warn("Not Found (Renew): {} - {}", app.getName(), id);
return Response.status(Status.NOT_FOUND).build();
}
// Check if we need to sync based on dirty time stamp, the client
// instance might have changed some value
Response response = null;
if (lastDirtyTimestamp != null && serverConfig.shouldSyncWhenTimestampDiffers()) {
response = this.validateDirtyTimestamp(Long.valueOf(lastDirtyTimestamp), isFromReplicaNode);
// Store the overridden status since the validation found out the node that replicates wins
if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()
&& (overriddenStatus != null)
&& !(InstanceStatus.UNKNOWN.name().equals(overriddenStatus))
&& isFromReplicaNode) {
registry.storeOverriddenStatusIfRequired(app.getAppName(), id, InstanceStatus.valueOf(overriddenStatus));
}
} else {
response = Response.ok().build();
}
logger.debug("Found (Renew): {} - {}; reply status={}", app.getName(), id, response.getStatus());
return response;
}
renewが成功しない場合は404を返し、クライアント側はregisterロジックを実行します.renewが成功し、lastDirtyTimestampがnullでない場合は、SyncWhenTimestampDiffersが必要かどうかを判断します.デフォルトtrue
validateDirtyTimestamp
private Response validateDirtyTimestamp(Long lastDirtyTimestamp,
boolean isReplication) {
InstanceInfo appInfo = registry.getInstanceByAppAndId(app.getName(), id, false);
if (appInfo != null) {
if ((lastDirtyTimestamp != null) && (!lastDirtyTimestamp.equals(appInfo.getLastDirtyTimestamp()))) {
Object[] args = {id, appInfo.getLastDirtyTimestamp(), lastDirtyTimestamp, isReplication};
if (lastDirtyTimestamp > appInfo.getLastDirtyTimestamp()) {
logger.debug(
"Time to sync, since the last dirty timestamp differs -"
+ " ReplicationInstance id : {},Registry : {} Incoming: {} Replication: {}",
args);
return Response.status(Status.NOT_FOUND).build();
} else if (appInfo.getLastDirtyTimestamp() > lastDirtyTimestamp) {
// In the case of replication, send the current instance info in the registry for the
// replicating node to sync itself with this one.
if (isReplication) {
logger.debug(
"Time to sync, since the last dirty timestamp differs -"
+ " ReplicationInstance id : {},Registry : {} Incoming: {} Replication: {}",
args);
return Response.status(Status.CONFLICT).entity(appInfo).build();
} else {
return Response.ok().build();
}
}
}
}
return Response.ok().build();
}
lastDirtyTimestampパラメータがserverローカルinstanceのlastDirtyTimestamp値より大きい場合、responseは404を返す.サーバがlastDirtyTimestampパラメータよりもローカルに大きい場合、replicationモードでない場合は200、replicationモードである場合は409 Conflictを返し、呼び出し元に自分のデータを同期させる.
PeerEurekaNode
eureka-core-1.8.8-sources.jar!/com/netflix/eureka/cluster/PeerEurekaNode.java
/**
* Send the heartbeat information of an instance to the node represented by
* this class. If the instance does not exist the node, the instance
* registration information is sent again to the peer node.
*
* @param appName
* the application name of the instance.
* @param id
* the unique identifier of the instance.
* @param info
* the instance info {@link InstanceInfo} of the instance.
* @param overriddenStatus
* the overridden status information if any of the instance.
* @throws Throwable
*/
public void heartbeat(final String appName, final String id,
final InstanceInfo info, final InstanceStatus overriddenStatus,
boolean primeConnection) throws Throwable {
if (primeConnection) {
// We do not care about the result for priming request.
replicationClient.sendHeartBeat(appName, id, info, overriddenStatus);
return;
}
ReplicationTask replicationTask = new InstanceReplicationTask(targetHost, Action.Heartbeat, info, overriddenStatus, false) {
@Override
public EurekaHttpResponse execute() throws Throwable {
return replicationClient.sendHeartBeat(appName, id, info, overriddenStatus);
}
@Override
public void handleFailure(int statusCode, Object responseEntity) throws Throwable {
super.handleFailure(statusCode, responseEntity);
if (statusCode == 404) {
logger.warn("{}: missing entry.", getTaskName());
if (info != null) {
logger.warn("{}: cannot find instance id {} and hence replicating the instance with status {}",
getTaskName(), info.getId(), info.getStatus());
register(info);
}
} else if (config.shouldSyncWhenTimestampDiffers()) {
InstanceInfo peerInstanceInfo = (InstanceInfo) responseEntity;
if (peerInstanceInfo != null) {
syncInstancesIfTimestampDiffers(appName, id, info, peerInstanceInfo);
}
}
}
};
long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
batchingDispatcher.process(taskId("heartbeat", info), replicationTask, expiryTime);
}
ここでのhandleFailureは、404に対する処理が再registerである.他にも409のような場合、shouldSyncWhenTimestampDiffers構成をオンにするかどうかを判断し、このデフォルトはtrueであり、peerが返した最新のinfo情報をローカルに上書きする
syncInstancesIfTimestampDiffers
/**
* Synchronize {@link InstanceInfo} information if the timestamp between
* this node and the peer eureka nodes vary.
*/
private void syncInstancesIfTimestampDiffers(String appName, String id, InstanceInfo info, InstanceInfo infoFromPeer) {
try {
if (infoFromPeer != null) {
logger.warn("Peer wants us to take the instance information from it, since the timestamp differs,"
+ "Id : {} My Timestamp : {}, Peer's timestamp: {}", id, info.getLastDirtyTimestamp(), infoFromPeer.getLastDirtyTimestamp());
if (infoFromPeer.getOverriddenStatus() != null && !InstanceStatus.UNKNOWN.equals(infoFromPeer.getOverriddenStatus())) {
logger.warn("Overridden Status info -id {}, mine {}, peer's {}", id, info.getOverriddenStatus(), infoFromPeer.getOverriddenStatus());
registry.storeOverriddenStatusIfRequired(appName, id, infoFromPeer.getOverriddenStatus());
}
registry.register(infoFromPeer, true);
}
} catch (Throwable e) {
logger.warn("Exception when trying to set information from peer :", e);
}
}
この段落はpeerNodeがreplicateで409に遭遇したとき、返されたInstanceInfoに基づいてローカルを上書きするプロセスです.
クライアント側
eureka-client-1.8.8-sources.jar!/com/netflix/appinfo/InstanceInfo.java
/**
* Sets the dirty flag so that the instance information can be carried to
* the discovery server on the next heartbeat.
*/
public synchronized void setIsDirty() {
isInstanceInfoDirty = true;
lastDirtyTimestamp = System.currentTimeMillis();
}
これはlastDirtyTimestampを変更する基本的な方法で、この方法を呼び出す他の方法は以下の通りです.
@Deprecated
public void setSID(String sid) {
this.sid = sid;
setIsDirty();
}
/**
* Set the status for this instance.
*
* @param status status for this instance.
* @return the prev status if a different status from the current was set, null otherwise
*/
public synchronized InstanceStatus setStatus(InstanceStatus status) {
if (this.status != status) {
InstanceStatus prev = this.status;
this.status = status;
setIsDirty();
return prev;
}
return null;
}
/**
* @param isDirty true if dirty, false otherwise.
* @deprecated use {@link #setIsDirty()} and {@link #unsetIsDirty(long)} to set and unset
*
* Sets the dirty flag so that the instance information can be carried to
* the discovery server on the next heartbeat.
*/
@Deprecated
public synchronized void setIsDirty(boolean isDirty) {
if (isDirty) {
setIsDirty();
} else {
isInstanceInfoDirty = false;
// else don't update lastDirtyTimestamp as we are setting isDirty to false
}
}
/**
* Set the dirty flag, and also return the timestamp of the isDirty event
*
* @return the timestamp when the isDirty flag is set
*/
public synchronized long setIsDirtyWithTime() {
setIsDirty();
return lastDirtyTimestamp;
}
/**
* Register application specific metadata to be sent to the discovery
* server.
*
* @param runtimeMetadata
* Map containing key/value pairs.
*/
synchronized void registerRuntimeMetadata(
Map runtimeMetadata) {
metadata.putAll(runtimeMetadata);
setIsDirty();
}
isInstanceInfoDirty
@JsonIgnore
public boolean isDirty() {
return isInstanceInfoDirty;
}
/**
* @return the lastDirtyTimestamp if is dirty, null otherwise.
*/
public synchronized Long isDirtyWithTime() {
if (isInstanceInfoDirty) {
return lastDirtyTimestamp;
} else {
return null;
}
}
/**
* Unset the dirty flag iff the unsetDirtyTimestamp matches the lastDirtyTimestamp. No-op if
* lastDirtyTimestamp > unsetDirtyTimestamp
*
* @param unsetDirtyTimestamp the expected lastDirtyTimestamp to unset.
*/
public synchronized void unsetIsDirty(long unsetDirtyTimestamp) {
if (lastDirtyTimestamp <= unsetDirtyTimestamp) {
isInstanceInfoDirty = false;
} else {
}
}
この方法は、lastDirtyTimestamp<=unsetDirtyTimestampの場合、isInstanceInfoDirtyをfalseと識別する
InstanceInfoReplicator
eureka-client-1.8.8-sources.jar!/com/netflix/discovery/InstanceInfoReplicator.java
public void run() {
try {
discoveryClient.refreshInstanceInfo();
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);
}
}
ここではまずrefreshInstanceInfoを、isInstanceInfoDirtyであればregister操作を実行すると判断します
refreshInstanceInfo
/**
* Refresh the current local instanceInfo. Note that after a valid refresh where changes are observed, the
* isDirty flag on the instanceInfo is set to true
*/
void refreshInstanceInfo() {
applicationInfoManager.refreshDataCenterInfoIfRequired();
applicationInfoManager.refreshLeaseInfoIfRequired();
InstanceStatus status;
try {
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) {
applicationInfoManager.setInstanceStatus(status);
}
}
refreshDataCenterInfoIfRequired
eureka-client-1.8.8-sources.jar!/com/netflix/appinfo/ApplicationInfoManager.java
/**
* Refetches the hostname to check if it has changed. If it has, the entire
* DataCenterInfo
is refetched and passed on to the eureka
* server on next heartbeat.
*
* see {@link InstanceInfo#getHostName()} for explanation on why the hostname is used as the default address
*/
public void refreshDataCenterInfoIfRequired() {
String existingAddress = instanceInfo.getHostName();
String newAddress;
if (config instanceof RefreshableInstanceConfig) {
// Refresh data center info, and return up to date address
newAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(true);
} else {
newAddress = config.getHostName(true);
}
String newIp = config.getIpAddress();
if (newAddress != null && !newAddress.equals(existingAddress)) {
logger.warn("The address changed from : {} => {}", existingAddress, newAddress);
// :( in the legacy code here the builder is acting as a mutator.
// This is hard to fix as this same instanceInfo instance is referenced elsewhere.
// We will most likely re-write the client at sometime so not fixing for now.
InstanceInfo.Builder builder = new InstanceInfo.Builder(instanceInfo);
builder.setHostName(newAddress).setIPAddr(newIp).setDataCenterInfo(config.getDataCenterInfo());
instanceInfo.setIsDirty();
}
}
public void refreshLeaseInfoIfRequired() {
LeaseInfo leaseInfo = instanceInfo.getLeaseInfo();
if (leaseInfo == null) {
return;
}
int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds();
int currentLeaseRenewal = config.getLeaseRenewalIntervalInSeconds();
if (leaseInfo.getDurationInSecs() != currentLeaseDuration || leaseInfo.getRenewalIntervalInSecs() != currentLeaseRenewal) {
LeaseInfo newLeaseInfo = LeaseInfo.Builder.newBuilder()
.setRenewalIntervalInSecs(currentLeaseRenewal)
.setDurationInSecs(currentLeaseDuration)
.build();
instanceInfo.setLeaseInfo(newLeaseInfo);
instanceInfo.setIsDirty();
}
}
新しい構成をリフレッシュし、変更があればinstanceInfo.setIsDirty()
小結
サーバ側でrenewLeaseを処理する場合、lastDirtyTimestampパラメータを判断し、serverローカルinstanceのlastDirtyTimestamp値より大きい場合、responseは404を返す.サーバがlastDirtyTimestampパラメータよりもローカルに大きい場合、replicationモードでない場合は200、replicationモードである場合は409 Conflictを返し、呼び出し元に自分のデータを同期させる.
クライアント側には、lastDirtyTimestampとisInstanceInfoDirtyの2つのプロパティがあります.Instanceのstatusを更新するとsetisDirtyが呼び出されます.すなわちlastDirtyTimestampを更新し、isInstanceInfoDirtyをtrueに設定します.その後、クライアント側にはInstanceInfoReplicatorのタイミングタスクがあり、定期的にプロファイルを読み込み、変更があればsetisDirtyを呼び出し、その後instanceInfoを呼び出す.isDirtyWithTime()は、dirtyの場合は再びregisterしisInstanceInfoDirtyをリセットし、lastDirtyTimestampを更新します.
doc