本文主要研究一下eureka instance的lastDirtyTimestampjava
即該instance在client端最後被修改的時間戳node
instance的接口,除了更新meta以及cancelLease操做外,其餘修改的操做都要帶上lastDirtyTimestamp,好比 eureka-core-1.8.8-sources.jar!/com/netflix/eureka/resources/InstanceResource.javaspring
@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外,其餘兩個的lastDirtyTimestamp僅僅是用來作傳遞用。segmentfault
@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,client端則會執行register邏輯 若是renew成功,且lastDirtyTimestamp不爲null,則判斷是否須要SyncWhenTimestampDiffers,默認trueapp
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; 若是是server本地大於lastDirtyTimestamp參數,不是replication模式則返回200,replication模式,則返回409 Conflict,讓調用方同步本身的數據。ide
eureka-core-1.8.8-sources.jar!/com/netflix/eureka/cluster/PeerEurekaNode.javafetch
/** * 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<InstanceInfo> 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信息覆蓋到本地ui
/** * 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覆蓋本地的過程。this
eureka-client-1.8.8-sources.jar!/com/netflix/appinfo/InstanceInfo.javaspa
/** * 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 * <p> * 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<String, String> runtimeMetadata) { metadata.putAll(runtimeMetadata); setIsDirty(); }
@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
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操做
/** * 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); } }
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 * <code>DataCenterInfo</code> 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()
在server端,處理renewLease的時候,對lastDirtyTimestamp參數進行判斷,若是大於server本地instance的lastDirtyTimestamp值,則response返回404;若是是server本地大於lastDirtyTimestamp參數,不是replication模式則返回200,replication模式,則返回409 Conflict,讓調用方同步本身的數據。
在client端,有兩個屬性,一個是lastDirtyTimestamp,一個是isInstanceInfoDirty。在更新instance的status的時候,會調用setIsDirty,即更新lastDirtyTimestamp以及設置isInstanceInfoDirty爲true;而後client端還有個InstanceInfoReplicator定時任務,會定時讀取配置文件,若是有變動則調用setIsDirty,以後是調用instanceInfo.isDirtyWithTime(),若是是dirty則會從新register並重置isInstanceInfoDirty,更新lastDirtyTimestamp。