zoukankan      html  css  js  c++  java
  • Eureka 续约 (心跳检测)

    Eureka 续约 (心跳检测)

    心跳,eureka client每隔一定的时间,会给eureka server发送心跳,保持心跳,让eureka server知道自己还活着,lease renewal,续约,心跳.

    Eureka-Client 向 Eureka-Server 发起注册应用实例成功后获得租约 ( Lease )。
    Eureka-Client 固定间隔向 Eureka-Server 发起续租( renew ),避免租约过期。

    默认情况下,租约有效期为 90 秒,续租频率为 30 秒。两者比例为 1 : 3 ,保证在网络异常等情况下,有三次重试的机会。

    (1)DiscoveryClient初始化的时候,会去调度一堆定时任务,其中有一个就是HeartbeatThread,心跳线程

    image-20211011172001382

    (2)在这里可以看到,默认是每隔30秒去发送一次心跳,每隔30秒执行一次HeartbeatTHread线程的逻辑,发送心跳

    (3)这边的话就是去发送这个心跳,走的是EurekaHttpClient的sendHeartbeat()方法,http://localhost:8080/v2/apps/ServiceA/i-000000-1,走的是put请求

        /**
         * The heartbeat task that renews the lease in the given intervals.
         */
        private class HeartbeatThread implements Runnable {
    
            public void run() {
                if (renew()) {
                    lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
                }
            }
        }
    
        /**
         * Renew with the eureka service by making the appropriate REST call
         */
        boolean renew() {
            EurekaHttpResponse<InstanceInfo> httpResponse;
            try {
                httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
                logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
                if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
                    REREGISTER_COUNTER.increment();
                    logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
                    long timestamp = instanceInfo.setIsDirtyWithTime();
                    boolean success = register();
                    if (success) {
                        instanceInfo.unsetIsDirty(timestamp);
                    }
                    return success;
                }
                return httpResponse.getStatusCode() == Status.OK.getStatusCode();
            } catch (Throwable e) {
                logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
                return false;
            }
        }
    

    (4)负责承接服务实例的心跳相关的这些操作的,是ApplicationsResource,服务相关的controller。找到ApplicationResource,再次找到InstanceResource,通过PUT请求,可以找到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;
            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;
        }
    

    (5)通过注册表的renew()方法,进去完成服务续约,实际进入AbstractInstanceRegistry的renew()方法

        /**
         * Marks the given instance of the given app name as renewed, and also marks whether it originated from
         * replication.
         *
         * @see com.netflix.eureka.lease.LeaseManager#renew(java.lang.String, java.lang.String, boolean)
         */
        public boolean renew(String appName, String id, boolean isReplication) {
            RENEW.increment(isReplication);
            Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
            Lease<InstanceInfo> leaseToRenew = null;
            if (gMap != null) {
                leaseToRenew = gMap.get(id);
            }
            if (leaseToRenew == null) {
                RENEW_NOT_FOUND.increment(isReplication);
                logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
                return false;
            } else {
                InstanceInfo instanceInfo = leaseToRenew.getHolder();
                if (instanceInfo != null) {
                    // touchASGCache(instanceInfo.getASGName());
                    InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
                            instanceInfo, leaseToRenew, isReplication);
                    if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
                        logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
                                + "; re-register required", instanceInfo.getId());
                        RENEW_NOT_FOUND.increment(isReplication);
                        return false;
                    }
                    if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
                        logger.info(
                                "The instance status {} is different from overridden instance status {} for instance {}. "
                                        + "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
                                        overriddenInstanceStatus.name(),
                                        instanceInfo.getId());
                        instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
    
                    }
                }
                // 新增 续租每分钟次数
                renewsLastMin.increment();
                // 设置 租约最后更新时间(续租)
                leaseToRenew.renew();
                return true;
            }
        }
    

    实际的服务续约的逻辑,其实就是在Lease对象中,更新一下lastUpdateTimestamp这个时间戳,每次续约,就更新一下,duration这里加了一遍,也是eureka的bug,但是官网并不做修改。

    public void renew() {
        lastUpdateTimestamp = System.currentTimeMillis() + duration;
    }
    
  • 相关阅读:
    索引失效原因总结
    Kafka、RabbitMQ、RocketMQ等消息中间件的介绍和对比
    一次JVM OOM问题的解决
    Java 如何有效地避免OOM:善于利用软引用和弱引用
    一个java内存泄漏的排查案例
    http的理解
    session和cookie区别
    数据库4种范式
    数据库的四种隔离级别
    C++malloc/free与new/delete的区别
  • 原文地址:https://www.cnblogs.com/dalianpai/p/15395758.html
Copyright © 2011-2022 走看看