eureka instanceのlastDirtyTimestampについて

18051 ワード

シーケンス
本文は主にeureka instanceのlastDirtyTimestampを研究する
serverエンド
  • lastDirtyTimestamp last timestamp when this instance information was updated.

  • すなわち、クライアント側で最後に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
  • eureka clientのHeartbeatThread
  • について話します
  • springcloudのEurekaClientAutoConfiguration
  • について話します
  • EurekaHealthCheckHandler
  • について話します
  • eureka serverのinstance登録およびメタデータ変更インタフェース
  • について