zoukankan      html  css  js  c++  java
  • EurekaClient 服务注册、发现、续约

      经过自己的测试发现客户端不加注解@EnableEurekaClient 也是可以的,这个注解在2.2.1版本中本身也没做任何的处理,如下:

    package org.springframework.cloud.netflix.eureka;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface EnableEurekaClient {
    
    }

      而在1.2.6版本中这个注解引入了@EnableDiscoveryClient, 查看如下:

    package org.springframework.cloud.netflix.eureka;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @EnableDiscoveryClient
    public @interface EnableEurekaClient {
    }

      @EnableDiscoveryClient 通过Import 引入了相关的配置类:

    package org.springframework.cloud.client.discovery;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import org.springframework.context.annotation.Import;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({EnableDiscoveryClientImportSelector.class})
    public @interface EnableDiscoveryClient {
    }

    所以在2.2.1 版本中,发生作用的不在上面的注解中,而在spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar包中META-INF目录spring.factories文件:

     内容如下:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,
    org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,
    org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,
    org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,
    org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,
    org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,
    org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration
    
    org.springframework.cloud.bootstrap.BootstrapConfiguration=
    org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration

      上面是自动注入了一堆的自动配置类。EurekaDiscoveryClientConfiguration 就是重点研究的对象。这个类注入了一个Bean

        @Bean
        @ConditionalOnMissingBean
        public EurekaDiscoveryClient discoveryClient(EurekaClient client,
                EurekaClientConfig clientConfig) {
            return new EurekaDiscoveryClient(client, clientConfig);
        }

    可以看出 EurekaClient  也是自动注入的一个bean,在org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration 类中

            @Bean(destroyMethod = "shutdown")
            @ConditionalOnMissingBean(value = EurekaClient.class,
                    search = SearchStrategy.CURRENT)
            public EurekaClient eurekaClient(ApplicationInfoManager manager,
                    EurekaClientConfig config) {
                return new CloudEurekaClient(manager, config, this.optionalArgs,
                        this.context);
            }

    接下来从类CloudEurekaClient 开始分析, 构造方法如下:

        public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
                EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args,
                ApplicationEventPublisher publisher) {
            super(applicationInfoManager, config, args);
            this.applicationInfoManager = applicationInfoManager;
            this.publisher = publisher;
            this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,
                    "eurekaTransport");
            ReflectionUtils.makeAccessible(this.eurekaTransportField);
        }

    父类构造如下:com.netflix.discovery.DiscoveryClient#DiscoveryClient(com.netflix.appinfo.ApplicationInfoManager, com.netflix.discovery.EurekaClientConfig, com.netflix.discovery.AbstractDiscoveryClientOptionalArgs)

        public DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) {
            this(applicationInfoManager, config, args, ResolverUtils::randomize);
        }
    
        public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) {
            this(applicationInfoManager, config, args, new Provider<BackupRegistry>() {
                private volatile BackupRegistry backupRegistryInstance;
    
                public synchronized BackupRegistry get() {
                    if (this.backupRegistryInstance == null) {
                        String backupRegistryClassName = config.getBackupRegistryImpl();
                        if (null != backupRegistryClassName) {
                            try {
                                this.backupRegistryInstance = (BackupRegistry)Class.forName(backupRegistryClassName).newInstance();
                                DiscoveryClient.logger.info("Enabled backup registry of type {}", this.backupRegistryInstance.getClass());
                            } catch (InstantiationException var3) {
                                DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var3);
                            } catch (IllegalAccessException var4) {
                                DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var4);
                            } catch (ClassNotFoundException var5) {
                                DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var5);
                            }
                        }
    
                        if (this.backupRegistryInstance == null) {
                            DiscoveryClient.logger.warn("Using default backup registry implementation which does not do anything.");
                            this.backupRegistryInstance = new NotImplementedRegistryImpl();
                        }
                    }
    
                    return this.backupRegistryInstance;
                }
            }, randomizer);
        }
    
        @Inject
        DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
            this.RECONCILE_HASH_CODES_MISMATCH = Monitors.newCounter("DiscoveryClient_ReconcileHashCodeMismatch");
            this.FETCH_REGISTRY_TIMER = Monitors.newTimer("DiscoveryClient_FetchRegistry");
            this.REREGISTER_COUNTER = Monitors.newCounter("DiscoveryClient_Reregister");
            this.localRegionApps = new AtomicReference();
            this.fetchRegistryUpdateLock = new ReentrantLock();
            this.healthCheckHandlerRef = new AtomicReference();
            this.remoteRegionVsApps = new ConcurrentHashMap();
            this.lastRemoteInstanceStatus = InstanceStatus.UNKNOWN;
            this.eventListeners = new CopyOnWriteArraySet();
            this.registrySize = 0;
            this.lastSuccessfulRegistryFetchTimestamp = -1L;
            this.lastSuccessfulHeartbeatTimestamp = -1L;
            this.isShutdown = new AtomicBoolean(false);
            if (args != null) {
                this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
                this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
                this.eventListeners.addAll(args.getEventListeners());
                this.preRegistrationHandler = args.preRegistrationHandler;
            } else {
                this.healthCheckCallbackProvider = null;
                this.healthCheckHandlerProvider = null;
                this.preRegistrationHandler = null;
            }
    
            this.applicationInfoManager = applicationInfoManager;
            InstanceInfo myInfo = applicationInfoManager.getInfo();
            this.clientConfig = config;
            staticClientConfig = this.clientConfig;
            this.transportConfig = config.getTransportConfig();
            this.instanceInfo = myInfo;
            if (myInfo != null) {
                this.appPathIdentifier = this.instanceInfo.getAppName() + "/" + this.instanceInfo.getId();
            } else {
                logger.warn("Setting instanceInfo to a passed in null value");
            }
    
            this.backupRegistryProvider = backupRegistryProvider;
            this.endpointRandomizer = endpointRandomizer;
            this.urlRandomizer = new InstanceInfoBasedUrlRandomizer(this.instanceInfo);
            this.localRegionApps.set(new Applications());
            this.fetchRegistryGeneration = new AtomicLong(0L);
            this.remoteRegionsToFetch = new AtomicReference(this.clientConfig.fetchRegistryForRemoteRegions());
            this.remoteRegionsRef = new AtomicReference(this.remoteRegionsToFetch.get() == null ? null : ((String)this.remoteRegionsToFetch.get()).split(","));
            if (config.shouldFetchRegistry()) {
                this.registryStalenessMonitor = new ThresholdLevelsMetric(this, "eurekaClient.registry.lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
            } else {
                this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
            }
    
            if (config.shouldRegisterWithEureka()) {
                this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, "eurekaClient.registration.lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
            } else {
                this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
            }
    
            logger.info("Initializing Eureka in region {}", this.clientConfig.getRegion());
            if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
                logger.info("Client configured to neither register nor query for data.");
                this.scheduler = null;
                this.heartbeatExecutor = null;
                this.cacheRefreshExecutor = null;
                this.eurekaTransport = null;
                this.instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), this.clientConfig.getRegion());
                DiscoveryManager.getInstance().setDiscoveryClient(this);
                DiscoveryManager.getInstance().setEurekaClientConfig(config);
                this.initTimestampMs = System.currentTimeMillis();
                logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", this.initTimestampMs, this.getApplications().size());
            } else {
                try {
                    this.scheduler = Executors.newScheduledThreadPool(2, (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-%d").setDaemon(true).build());
                    this.heartbeatExecutor = new ThreadPoolExecutor(1, this.clientConfig.getHeartbeatExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").setDaemon(true).build());
                    this.cacheRefreshExecutor = new ThreadPoolExecutor(1, this.clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").setDaemon(true).build());
                    this.eurekaTransport = new DiscoveryClient.EurekaTransport();
                    this.scheduleServerEndpointTask(this.eurekaTransport, args);
                    Object azToRegionMapper;
                    if (this.clientConfig.shouldUseDnsForFetchingServiceUrls()) {
                        azToRegionMapper = new DNSBasedAzToRegionMapper(this.clientConfig);
                    } else {
                        azToRegionMapper = new PropertyBasedAzToRegionMapper(this.clientConfig);
                    }
    
                    if (null != this.remoteRegionsToFetch.get()) {
                        ((AzToRegionMapper)azToRegionMapper).setRegionsToFetch(((String)this.remoteRegionsToFetch.get()).split(","));
                    }
    
                    this.instanceRegionChecker = new InstanceRegionChecker((AzToRegionMapper)azToRegionMapper, this.clientConfig.getRegion());
                } catch (Throwable var10) {
                    throw new RuntimeException("Failed to initialize DiscoveryClient!", var10);
                }
    
                if (this.clientConfig.shouldFetchRegistry() && !this.fetchRegistry(false)) {
                    this.fetchRegistryFromBackup();
                }
    
                if (this.preRegistrationHandler != null) {
                    this.preRegistrationHandler.beforeRegistration();
                }
    
                if (this.clientConfig.shouldRegisterWithEureka() && this.clientConfig.shouldEnforceRegistrationAtInit()) {
                    try {
                        if (!this.register()) {
                            throw new IllegalStateException("Registration error at startup. Invalid server response.");
                        }
                    } catch (Throwable var9) {
                        logger.error("Registration error at startup: {}", var9.getMessage());
                        throw new IllegalStateException(var9);
                    }
                }
    
                this.initScheduledTasks();
    
                try {
                    Monitors.registerObject(this);
                } catch (Throwable var8) {
                    logger.warn("Cannot register timers", var8);
                }
    
                DiscoveryManager.getInstance().setDiscoveryClient(this);
                DiscoveryManager.getInstance().setEurekaClientConfig(config);
                this.initTimestampMs = System.currentTimeMillis();
                logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", this.initTimestampMs, this.getApplications().size());
            }
        }

    所以重点在最后的构造方法上。接下来从这里开始研究。

    上面构造如果不取服务也不注册服务则不做操作,shouldFetchRegistry()、shouldRegisterWithEureka() 就是我们在yml配置的,如下:

    org.springframework.cloud.netflix.eureka.EurekaClientConfigBean的方法:

        public boolean shouldRegisterWithEureka() {
            return this.registerWithEureka;
        }
    
        public boolean shouldFetchRegistry() {
            return this.fetchRegistry;
        }

    try代码块创建了3个线程池:scheduler、 heartbeatExecutor、cacheRefreshExecutor,接下来的操作就是线程池进行的操作。

    1. 服务注册

    1. 上面方法创建完线程池调用  initScheduledTasks() 方法

    com.netflix.discovery.DiscoveryClient#initScheduledTasks

        /**
         * Initializes all scheduled tasks.
         */
        private void initScheduledTasks() {
            if (clientConfig.shouldFetchRegistry()) {
                // registry cache refresh timer
                int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
                int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
                scheduler.schedule(
                        new TimedSupervisorTask(
                                "cacheRefresh",
                                scheduler,
                                cacheRefreshExecutor,
                                registryFetchIntervalSeconds,
                                TimeUnit.SECONDS,
                                expBackOffBound,
                                new CacheRefreshThread()
                        ),
                        registryFetchIntervalSeconds, TimeUnit.SECONDS);
            }
    
            if (clientConfig.shouldRegisterWithEureka()) {
                int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
                int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
                logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
    
                // Heartbeat timer
                scheduler.schedule(
                        new TimedSupervisorTask(
                                "heartbeat",
                                scheduler,
                                heartbeatExecutor,
                                renewalIntervalInSecs,
                                TimeUnit.SECONDS,
                                expBackOffBound,
                                new HeartbeatThread()
                        ),
                        renewalIntervalInSecs, TimeUnit.SECONDS);
    
                // InstanceInfo replicator
                instanceInfoReplicator = new InstanceInfoReplicator(
                        this,
                        instanceInfo,
                        clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                        2); // burstSize
    
                statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                    @Override
                    public String getId() {
                        return "statusChangeListener";
                    }
    
                    @Override
                    public void notify(StatusChangeEvent statusChangeEvent) {
                        if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                                InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                            // log at warn level if DOWN was involved
                            logger.warn("Saw local status change event {}", statusChangeEvent);
                        } else {
                            logger.info("Saw local status change event {}", statusChangeEvent);
                        }
                        instanceInfoReplicator.onDemandUpdate();
                    }
                };
    
                if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                    applicationInfoManager.registerStatusChangeListener(statusChangeListener);
                }
    
                instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
            } else {
                logger.info("Not registering with Eureka server per configuration");
            }
        }

    2. clientConfig.shouldRegisterWithEureka() 代码块走注册逻辑===下面主要是维持心跳的逻辑

    (1) 拿到renewalIntervalInSecs 获取到续约的时间,默认是30s==这里可以看到是通过线程池的scheduler 定时任务

    (2) 如下代码块开启维持心跳的代码块:

                // Heartbeat timer
                scheduler.schedule(
                        new TimedSupervisorTask(
                                "heartbeat",
                                scheduler,
                                heartbeatExecutor,
                                renewalIntervalInSecs,
                                TimeUnit.SECONDS,
                                expBackOffBound,
                                new HeartbeatThread()
                        ),
                        renewalIntervalInSecs, TimeUnit.SECONDS);

    TimedSupervisorTask  构造如下:

        public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor, int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
            this.scheduler = scheduler;
            this.executor = executor;
            this.timeoutMillis = timeUnit.toMillis((long)timeout);
            this.task = task;
            this.delay = new AtomicLong(this.timeoutMillis);
            this.maxDelay = this.timeoutMillis * (long)expBackOffBound;
            this.successCounter = Monitors.newCounter("success");
            this.timeoutCounter = Monitors.newCounter("timeouts");
            this.rejectedCounter = Monitors.newCounter("rejectedExecutions");
            this.throwableCounter = Monitors.newCounter("throwables");
            this.threadPoolLevelGauge = new LongGauge(MonitorConfig.builder("threadPoolUsed").build());
            Monitors.registerObject(name, this);
        }

    接下来调用 TimedSupervisorTask的run方法: (也就是用线程池跑任务,这里是scheduler里面跑了个ThreadPoolExecutor 线程池异步任务)

        public void run() {
            Future future = null;
    
            try {
                future = this.executor.submit(this.task);
                this.threadPoolLevelGauge.set((long)this.executor.getActiveCount());
                future.get(this.timeoutMillis, TimeUnit.MILLISECONDS);
                this.delay.set(this.timeoutMillis);
                this.threadPoolLevelGauge.set((long)this.executor.getActiveCount());
                this.successCounter.increment();
            } catch (TimeoutException var12) {
                logger.warn("task supervisor timed out", var12);
                this.timeoutCounter.increment();
                long currentDelay = this.delay.get();
                long newDelay = Math.min(this.maxDelay, currentDelay * 2L);
                this.delay.compareAndSet(currentDelay, newDelay);
            } catch (RejectedExecutionException var13) {
                if (!this.executor.isShutdown() && !this.scheduler.isShutdown()) {
                    logger.warn("task supervisor rejected the task", var13);
                } else {
                    logger.warn("task supervisor shutting down, reject the task", var13);
                }
    
                this.rejectedCounter.increment();
            } catch (Throwable var14) {
                if (!this.executor.isShutdown() && !this.scheduler.isShutdown()) {
                    logger.warn("task supervisor threw an exception", var14);
                } else {
                    logger.warn("task supervisor shutting down, can't accept the task");
                }
    
                this.throwableCounter.increment();
            } finally {
                if (future != null) {
                    future.cancel(true);
                }
    
                if (!this.scheduler.isShutdown()) {
                    this.scheduler.schedule(this, this.delay.get(), TimeUnit.MILLISECONDS);
                }
    
            }
    
        }

    run方法,会跑构造传进来的心跳线程心跳线程如下:com.netflix.discovery.DiscoveryClient.HeartbeatThread

        /**
         * The heartbeat task that renews the lease in the given intervals.
         */
        private class HeartbeatThread implements Runnable {
    
            public void run() {
                if (renew()) {
                    lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
                }
            }
        }

    (3) 接下来调用com.netflix.discovery.DiscoveryClient#renew

        /**
         * 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;
            }
        }

    这里也就是维持心跳的核心逻辑,通过HttpClient访问心跳接口。

    (4) com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#sendHeartBeat 如下:

        public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
            String urlPath = "apps/" + appName + '/' + id;
            ClientResponse response = null;
            try {
                WebResource webResource = jerseyClient.resource(serviceUrl)
                        .path(urlPath)
                        .queryParam("status", info.getStatus().toString())
                        .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
                if (overriddenStatus != null) {
                    webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
                }
                Builder requestBuilder = webResource.getRequestBuilder();
                addExtraHeaders(requestBuilder);
                response = requestBuilder.put(ClientResponse.class);
                EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
                if (response.hasEntity()) {
                    eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
                }
                return eurekaResponseBuilder.build();
            } finally {
                if (logger.isDebugEnabled()) {
                    logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
                }
                if (response != null) {
                    response.close();
                }
            }
        }

     其生成的url如下:

     requestBuilder如下:

     接下来调用requestBuilder.put(ClientResponse.class);发送心跳, 也就是通过Put方法发送心跳请求。(发送的信息包括自己的状态一个时间参数)

    如果返回的结果是404(Status.NOT_FOUND),就是说不存在,从来没有注册过,那么重新走注册流程。会进行下面一系列操作:

    1》修改时间参数

    com.netflix.discovery.DiscoveryClient#renew代码中instanceInfo.setIsDirtyWithTime()

    com.netflix.appinfo.InstanceInfo#setIsDirtyWithTime

        public synchronized long setIsDirtyWithTime() {
            setIsDirty();
            return lastDirtyTimestamp;
        }

    2》 重新注册服务

    com.netflix.discovery.DiscoveryClient#register

        /**
         * Register with the eureka service by making the appropriate REST call.
         */
        boolean register() throws Throwable {
            logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
            EurekaHttpResponse<Void> httpResponse;
            try {
                httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
            } catch (Exception e) {
                logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
                throw e;
            }
            if (logger.isInfoEnabled()) {
                logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
            }
            return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
        }

    最终调用到方法:com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register

        @Override
        public EurekaHttpResponse<Void> register(InstanceInfo info) {
            String urlPath = "apps/" + info.getAppName();
            ClientResponse response = null;
            try {
                Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
                addExtraHeaders(resourceBuilder);
                response = resourceBuilder
                        .header("Accept-Encoding", "gzip")
                        .type(MediaType.APPLICATION_JSON_TYPE)
                        .accept(MediaType.APPLICATION_JSON)
                        .post(ClientResponse.class, info);
                return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
            } finally {
                if (logger.isDebugEnabled()) {
                    logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                            response == null ? "N/A" : response.getStatus());
                }
                if (response != null) {
                    response.close();
                }
            }
        }

    3. com.netflix.discovery.DiscoveryClient#initScheduledTasks代码块中clientConfig.shouldRegisterWithEureka() 代码块走注册逻辑===下面主要是注册逻辑

    (1) 创建com.netflix.discovery.InstanceInfoReplicator对象:

                // InstanceInfo replicator
                instanceInfoReplicator = new InstanceInfoReplicator(
                        this,
                        instanceInfo,
                        clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                        2); // burstSize

    构造如下:(创建一个scheduler 定时线程池)

        InstanceInfoReplicator(DiscoveryClient discoveryClient, InstanceInfo instanceInfo, int replicationIntervalSeconds, int burstSize) {
            this.discoveryClient = discoveryClient;
            this.instanceInfo = instanceInfo;
            this.scheduler = Executors.newScheduledThreadPool(1,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-InstanceInfoReplicator-%d")
                            .setDaemon(true)
                            .build());
    
            this.scheduledPeriodicRef = new AtomicReference<Future>();
    
            this.started = new AtomicBoolean(false);
            this.rateLimiter = new RateLimiter(TimeUnit.MINUTES);
            this.replicationIntervalSeconds = replicationIntervalSeconds;
            this.burstSize = burstSize;
    
            this.allowedRatePerMinute = 60 * this.burstSize / this.replicationIntervalSeconds;
            logger.info("InstanceInfoReplicator onDemand update allowed rate per min is {}", allowedRatePerMinute);
        }

    最后一行代码如下:

    instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds())

    clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()返回的是:(默认40s向eureka更新实例信息)

        /**
         * Indicates how long initially (in seconds) to replicate instance info to the eureka
         * server.
         */
        private int initialInstanceInfoReplicationIntervalSeconds = 40;

    com.netflix.discovery.InstanceInfoReplicator#start:(start方法里面开始注册==这里的start不是线程的启动方法,是一个同步方法)

        public void start(int initialDelayMs) {
            if (started.compareAndSet(false, true)) {
                instanceInfo.setIsDirty();  // for initial register
                Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
                scheduledPeriodicRef.set(next);
            }
        }

    scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS); 开始进行线程的启动执行:(线程启动调run方法)

        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);
            }
        }

    run逻辑解释:

    1》 discoveryClient.refreshInstanceInfo(); 方法更新实例信息

        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);
            }
        }

    (2) discoveryClient.register(); 方法进行注册

    com.netflix.discovery.DiscoveryClient#register

        boolean register() throws Throwable {
            logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
            EurekaHttpResponse<Void> httpResponse;
            try {
                httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
            } catch (Exception e) {
                logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
                throw e;
            }
            if (logger.isInfoEnabled()) {
                logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
            }
            return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
        }

     最终调到com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register

        public EurekaHttpResponse<Void> register(InstanceInfo info) {
            String urlPath = "apps/" + info.getAppName();
            ClientResponse response = null;
    
            EurekaHttpResponse var5;
            try {
                Builder resourceBuilder = this.jerseyClient.resource(this.serviceUrl).path(urlPath).getRequestBuilder();
                this.addExtraHeaders(resourceBuilder);
                response = (ClientResponse)((Builder)((Builder)((Builder)resourceBuilder.header("Accept-Encoding", "gzip")).type(MediaType.APPLICATION_JSON_TYPE)).accept(new String[]{"application/json"})).post(ClientResponse.class, info);
                var5 = EurekaHttpResponse.anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
            } finally {
                if (logger.isDebugEnabled()) {
                    logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", new Object[]{this.serviceUrl, urlPath, info.getId(), response == null ? "N/A" : response.getStatus()});
                }
    
                if (response != null) {
                    response.close();
                }
    
            }
    
            return var5;
        }

    resourceBuilder如下:

     发送的bean信息如下:

     最终生成的requestImpl如下:

    2. 服务获取

     com.netflix.discovery.DiscoveryClient#initScheduledTasks 代码块中this.clientConfig.shouldFetchRegistry() 是获取服务的配置:

            if (this.clientConfig.shouldFetchRegistry()) {
                renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
                expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
                this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
            }

    1. 拿到默认的时间:(30s)

    private int registryFetchIntervalSeconds = 30;

    2. 然后开启定时线程池,以30s为周期执行的线程任务如下:

    new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread())

    构造如下:

        public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor, int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
            this.scheduler = scheduler;
            this.executor = executor;
            this.timeoutMillis = timeUnit.toMillis((long)timeout);
            this.task = task;
            this.delay = new AtomicLong(this.timeoutMillis);
            this.maxDelay = this.timeoutMillis * (long)expBackOffBound;
            this.successCounter = Monitors.newCounter("success");
            this.timeoutCounter = Monitors.newCounter("timeouts");
            this.rejectedCounter = Monitors.newCounter("rejectedExecutions");
            this.throwableCounter = Monitors.newCounter("throwables");
            this.threadPoolLevelGauge = new LongGauge(MonitorConfig.builder("threadPoolUsed").build());
            Monitors.registerObject(name, this);
        }

    3. 然后会调用TimedSupervisorTask的run方法:

        public void run() {
            Future future = null;
    
            try {
                future = this.executor.submit(this.task);
                this.threadPoolLevelGauge.set((long)this.executor.getActiveCount());
                future.get(this.timeoutMillis, TimeUnit.MILLISECONDS);
                this.delay.set(this.timeoutMillis);
                this.threadPoolLevelGauge.set((long)this.executor.getActiveCount());
                this.successCounter.increment();
            } catch (TimeoutException var12) {
                logger.warn("task supervisor timed out", var12);
                this.timeoutCounter.increment();
                long currentDelay = this.delay.get();
                long newDelay = Math.min(this.maxDelay, currentDelay * 2L);
                this.delay.compareAndSet(currentDelay, newDelay);
            } catch (RejectedExecutionException var13) {
                if (!this.executor.isShutdown() && !this.scheduler.isShutdown()) {
                    logger.warn("task supervisor rejected the task", var13);
                } else {
                    logger.warn("task supervisor shutting down, reject the task", var13);
                }
    
                this.rejectedCounter.increment();
            } catch (Throwable var14) {
                if (!this.executor.isShutdown() && !this.scheduler.isShutdown()) {
                    logger.warn("task supervisor threw an exception", var14);
                } else {
                    logger.warn("task supervisor shutting down, can't accept the task");
                }
    
                this.throwableCounter.increment();
            } finally {
                if (future != null) {
                    future.cancel(true);
                }
    
                if (!this.scheduler.isShutdown()) {
                    this.scheduler.schedule(this, this.delay.get(), TimeUnit.MILLISECONDS);
                }
    
            }
    
        }

       里面又用一个线程池提交task任务,task任务是 构造方法传递的com.netflix.discovery.DiscoveryClient.CacheRefreshThread

        class CacheRefreshThread implements Runnable {
            CacheRefreshThread() {
            }
    
            public void run() {
                DiscoveryClient.this.refreshRegistry();
            }
        }

    4. 最终调到:com.netflix.discovery.DiscoveryClient#refreshRegistry

        void refreshRegistry() {
            try {
                boolean isFetchingRemoteRegionRegistries = this.isFetchingRemoteRegionRegistries();
                boolean remoteRegionsModified = false;
                String latestRemoteRegions = this.clientConfig.fetchRegistryForRemoteRegions();
                if (null != latestRemoteRegions) {
                    String currentRemoteRegions = (String)this.remoteRegionsToFetch.get();
                    if (!latestRemoteRegions.equals(currentRemoteRegions)) {
                        synchronized(this.instanceRegionChecker.getAzToRegionMapper()) {
                            if (this.remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) {
                                String[] remoteRegions = latestRemoteRegions.split(",");
                                this.remoteRegionsRef.set(remoteRegions);
                                this.instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions);
                                remoteRegionsModified = true;
                            } else {
                                logger.info("Remote regions to fetch modified concurrently, ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions);
                            }
                        }
                    } else {
                        this.instanceRegionChecker.getAzToRegionMapper().refreshMapping();
                    }
                }
    
                boolean success = this.fetchRegistry(remoteRegionsModified);
                if (success) {
                    this.registrySize = ((Applications)this.localRegionApps.get()).size();
                    this.lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
                }
    
                if (logger.isDebugEnabled()) {
                    StringBuilder allAppsHashCodes = new StringBuilder();
                    allAppsHashCodes.append("Local region apps hashcode: ");
                    allAppsHashCodes.append(((Applications)this.localRegionApps.get()).getAppsHashCode());
                    allAppsHashCodes.append(", is fetching remote regions? ");
                    allAppsHashCodes.append(isFetchingRemoteRegionRegistries);
                    Iterator var11 = this.remoteRegionVsApps.entrySet().iterator();
    
                    while(var11.hasNext()) {
                        Entry<String, Applications> entry = (Entry)var11.next();
                        allAppsHashCodes.append(", Remote region: ");
                        allAppsHashCodes.append((String)entry.getKey());
                        allAppsHashCodes.append(" , apps hashcode: ");
                        allAppsHashCodes.append(((Applications)entry.getValue()).getAppsHashCode());
                    }
    
                    logger.debug("Completed cache refresh task for discovery. All Apps hash code is {} ", allAppsHashCodes);
                }
            } catch (Throwable var9) {
                logger.error("Cannot fetch registry from server", var9);
            }
    
        }

     this.fetchRegistry(remoteRegionsModified); 如下:

        private boolean fetchRegistry(boolean forceFullRegistryFetch) {
            Stopwatch tracer = this.FETCH_REGISTRY_TIMER.start();
    
            label122: {
                boolean var4;
                try {
                    Applications applications = this.getApplications();
                    if (!this.clientConfig.shouldDisableDelta() && Strings.isNullOrEmpty(this.clientConfig.getRegistryRefreshSingleVipAddress()) && !forceFullRegistryFetch && applications != null && applications.getRegisteredApplications().size() != 0 && applications.getVersion().longValue() != -1L) {
                        this.getAndUpdateDelta(applications);
                    } else {
                        logger.info("Disable delta property : {}", this.clientConfig.shouldDisableDelta());
                        logger.info("Single vip registry refresh property : {}", this.clientConfig.getRegistryRefreshSingleVipAddress());
                        logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
                        logger.info("Application is null : {}", applications == null);
                        logger.info("Registered Applications size is zero : {}", applications.getRegisteredApplications().size() == 0);
                        logger.info("Application version is -1: {}", applications.getVersion().longValue() == -1L);
                        this.getAndStoreFullRegistry();
                    }
    
                    applications.setAppsHashCode(applications.getReconcileHashCode());
                    this.logTotalInstances();
                    break label122;
                } catch (Throwable var8) {
                    logger.error("DiscoveryClient_{} - was unable to refresh its cache! status = {}", new Object[]{this.appPathIdentifier, var8.getMessage(), var8});
                    var4 = false;
                } finally {
                    if (tracer != null) {
                        tracer.stop();
                    }
    
                }
    
                return var4;
            }
    
            this.onCacheRefreshed();
            this.updateInstanceRemoteStatus();
            return true;
        }

    最后会走 com.netflix.discovery.DiscoveryClient#getAndUpdateDelta

        private void getAndUpdateDelta(Applications applications) throws Throwable {
            long currentUpdateGeneration = this.fetchRegistryGeneration.get();
            Applications delta = null;
            EurekaHttpResponse<Applications> httpResponse = this.eurekaTransport.queryClient.getDelta((String[])this.remoteRegionsRef.get());
            if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
                delta = (Applications)httpResponse.getEntity();
            }
    
            if (delta == null) {
                logger.warn("The server does not allow the delta revision to be applied because it is not safe. Hence got the full registry.");
                this.getAndStoreFullRegistry();
            } else if (this.fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1L)) {
                logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
                String reconcileHashCode = "";
                if (this.fetchRegistryUpdateLock.tryLock()) {
                    try {
                        this.updateDelta(delta);
                        reconcileHashCode = this.getReconcileHashCode(applications);
                    } finally {
                        this.fetchRegistryUpdateLock.unlock();
                    }
                } else {
                    logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
                }
    
                if (!reconcileHashCode.equals(delta.getAppsHashCode()) || this.clientConfig.shouldLogDeltaDiff()) {
                    this.reconcileAndLogDifference(delta, reconcileHashCode);
                }
            } else {
                logger.warn("Not updating application delta as another thread is updating it already");
                logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
            }
    
        }

     最后调到方法:com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#getDelta

        public EurekaHttpResponse<Applications> getDelta(String... regions) {
            return this.getApplicationsInternal("apps/delta", regions);
        }
    
        private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
            ClientResponse response = null;
            String regionsParamValue = null;
    
            EurekaHttpResponse var8;
            try {
                WebResource webResource = this.jerseyClient.resource(this.serviceUrl).path(urlPath);
                if (regions != null && regions.length > 0) {
                    regionsParamValue = StringUtil.join(regions);
                    webResource = webResource.queryParam("regions", regionsParamValue);
                }
    
                Builder requestBuilder = webResource.getRequestBuilder();
                this.addExtraHeaders(requestBuilder);
                response = (ClientResponse)((Builder)requestBuilder.accept(new MediaType[]{MediaType.APPLICATION_JSON_TYPE})).get(ClientResponse.class);
                Applications applications = null;
                if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
                    applications = (Applications)response.getEntity(Applications.class);
                }
    
                var8 = EurekaHttpResponse.anEurekaHttpResponse(response.getStatus(), Applications.class).headers(headersOf(response)).entity(applications).build();
            } finally {
                if (logger.isDebugEnabled()) {
                    logger.debug("Jersey HTTP GET {}/{}?{}; statusCode={}", new Object[]{this.serviceUrl, urlPath, regionsParamValue == null ? "" : "regions=" + regionsParamValue, response == null ? "N/A" : response.getStatus()});
                }
    
                if (response != null) {
                    response.close();
                }
    
            }
    
            return var8;
        }

      生成的requestBuilder如下:

     最后调用返回的response信息如下:

       拿到应用信息之后返回比较之后进行更新。

       也就是说服务获取时候是scheduler里面又用一个ThreadPoolExecutor 线程池进行服务获取。这里的任务调度线程scheduler和上面心跳是同一个scheduler。

    3. 服务注销

       服务注销需要追溯到服务销毁时候的 shutdown 方法,com.netflix.discovery.DiscoveryClient#shutdown

        @PreDestroy
        public synchronized void shutdown() {
            if (this.isShutdown.compareAndSet(false, true)) {
                logger.info("Shutting down DiscoveryClient ...");
                if (this.statusChangeListener != null && this.applicationInfoManager != null) {
                    this.applicationInfoManager.unregisterStatusChangeListener(this.statusChangeListener.getId());
                }
    
                this.cancelScheduledTasks();
                if (this.applicationInfoManager != null && this.clientConfig.shouldRegisterWithEureka() && this.clientConfig.shouldUnregisterOnShutdown()) {
                    this.applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                    this.unregister();
                }
    
                if (this.eurekaTransport != null) {
                    this.eurekaTransport.shutdown();
                }
    
                this.heartbeatStalenessMonitor.shutdown();
                this.registryStalenessMonitor.shutdown();
                logger.info("Completed shut down of DiscoveryClient");
            }
    
        }

      可以看到代码取消了定时任务,然后调用unregister() 方法。com.netflix.discovery.DiscoveryClient#unregister:

        void unregister() {
            if (this.eurekaTransport != null && this.eurekaTransport.registrationClient != null) {
                try {
                    logger.info("Unregistering ...");
                    EurekaHttpResponse<Void> httpResponse = this.eurekaTransport.registrationClient.cancel(this.instanceInfo.getAppName(), this.instanceInfo.getId());
                    logger.info("DiscoveryClient_{} - deregister  status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
                } catch (Exception var2) {
                    logger.error("DiscoveryClient_{} - de-registration failed{}", new Object[]{this.appPathIdentifier, var2.getMessage(), var2});
                }
            }
    
        }

    最终调到com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#cancel:

        public EurekaHttpResponse<Void> cancel(String appName, String id) {
            String urlPath = "apps/" + appName + '/' + id;
            ClientResponse response = null;
    
            EurekaHttpResponse var6;
            try {
                Builder resourceBuilder = this.jerseyClient.resource(this.serviceUrl).path(urlPath).getRequestBuilder();
                this.addExtraHeaders(resourceBuilder);
                response = (ClientResponse)resourceBuilder.delete(ClientResponse.class);
                var6 = EurekaHttpResponse.anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
            } finally {
                if (logger.isDebugEnabled()) {
                    logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", new Object[]{this.serviceUrl, urlPath, response == null ? "N/A" : response.getStatus()});
                }
    
                if (response != null) {
                    response.close();
                }
    
            }
    
            return var6;
        }

    可以看出来是通过delete方法调用接口: http://localhost:7001/eureka/apps/ + appName + '/' + instanceId    删除服务

    总结:

      其实Client端核心的入口就是如下构造方法:

    com.netflix.discovery.DiscoveryClient#DiscoveryClient(com.netflix.appinfo.ApplicationInfoManager, com.netflix.discovery.EurekaClientConfig, com.netflix.discovery.AbstractDiscoveryClientOptionalArgs, javax.inject.Provider<com.netflix.discovery.BackupRegistry>, com.netflix.discovery.shared.resolver.EndpointRandomizer)

      构造方法创建了scheduler任务调度器,线程池大小为2;分别跑心跳任务heartbeat和获取服务的cacheRefresh 任务。需要注意调度器中的任务又是在线程池中跑的,分别对应heartbeatExecutor和cacheRefreshExecutor。异步任务执行完会再次开启scheduler任务调度,造成一个周期性任务调度的效果。renewalIntervalInSecs  心跳周期默认30s;registryFetchIntervalSeconds  服务获取周期默认是30 s。

      com.netflix.discovery.DiscoveryClient#initScheduledTasks方法中创建了一个instanceInfoReplicator,instanceInfoReplicator 构造中创建了一个线程池大小为1 的scheduler,用于跑注册服务(更新服务信息)的任务。 这个任务调度器跑的任务就是直接是InstanceInfoReplicator 自身,也就是跑其run方法,run方法结束继续任务调度。 也就是服务注册是scheduler 直接调度任务,没有再用线程池。initialInstanceInfoReplicationIntervalSeconds  更新服务信息周期 默认40s;

    补充:Eureka客户端请求地址,可以通过自己curl进行调用

    1. 维持心跳

    http://localhost:7001/eureka/apps/appName/instanceId?status=UP&lastDirtyTimestamp=1615368528026

    例如:

    http://localhost:7001/eureka/apps/CLOUD-PROVIDER-HYSTRIX-PAYMENT/QLQ-T460:cloud-provider-hystrix-payment:0?status=UP&lastDirtyTimestamp=1615368528026

    补充:应用名称(服务名)变为大写

    1. 如下方法: org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration.RefreshableEurekaClientConfiguration#eurekaApplicationInfoManager

            @Bean
            @ConditionalOnMissingBean(value = ApplicationInfoManager.class,
                    search = SearchStrategy.CURRENT)
            @org.springframework.cloud.context.config.annotation.RefreshScope
            @Lazy
            public ApplicationInfoManager eurekaApplicationInfoManager(
                    EurekaInstanceConfig config) {
                InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
                return new ApplicationInfoManager(config, instanceInfo);
            }

    2. 调用对象工厂使用建造者模式创建对象org.springframework.cloud.netflix.eureka.InstanceInfoFactory#create

        public InstanceInfo create(EurekaInstanceConfig config) {
            LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
                    .setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
                    .setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
    
            // Builder the instance information to be registered with eureka
            // server
            InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
    
            String namespace = config.getNamespace();
            if (!namespace.endsWith(".")) {
                namespace = namespace + ".";
            }
            builder.setNamespace(namespace).setAppName(config.getAppname())
                    .setInstanceId(config.getInstanceId())
                    .setAppGroupName(config.getAppGroupName())
                    .setDataCenterInfo(config.getDataCenterInfo())
                    .setIPAddr(config.getIpAddress()).setHostName(config.getHostName(false))
                    .setPort(config.getNonSecurePort())
                    .enablePort(InstanceInfo.PortType.UNSECURE,
                            config.isNonSecurePortEnabled())
                    .setSecurePort(config.getSecurePort())
                    .enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled())
                    .setVIPAddress(config.getVirtualHostName())
                    .setSecureVIPAddress(config.getSecureVirtualHostName())
                    .setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
                    .setStatusPageUrl(config.getStatusPageUrlPath(),
                            config.getStatusPageUrl())
                    .setHealthCheckUrls(config.getHealthCheckUrlPath(),
                            config.getHealthCheckUrl(), config.getSecureHealthCheckUrl())
                    .setASGName(config.getASGName());
    
            // Start off with the STARTING state to avoid traffic
            if (!config.isInstanceEnabledOnit()) {
                InstanceInfo.InstanceStatus initialStatus = InstanceInfo.InstanceStatus.STARTING;
                if (log.isInfoEnabled()) {
                    log.info("Setting initial instance status as: " + initialStatus);
                }
                builder.setStatus(initialStatus);
            }
            else {
                if (log.isInfoEnabled()) {
                    log.info("Setting initial instance status as: "
                            + InstanceInfo.InstanceStatus.UP
                            + ". This may be too early for the instance to advertise itself as available. "
                            + "You would instead want to control this via a healthcheck handler.");
                }
            }
    
            // Add any user-specific metadata information
            for (Map.Entry<String, String> mapEntry : config.getMetadataMap().entrySet()) {
                String key = mapEntry.getKey();
                String value = mapEntry.getValue();
                // only add the metadata if the value is present
                if (value != null && !value.isEmpty()) {
                    builder.add(key, value);
                }
            }
    
            InstanceInfo instanceInfo = builder.build();
            instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
            return instanceInfo;
        }

    3.  建造者的setAppnName方法com.netflix.appinfo.InstanceInfo.Builder#setAppName

            public Builder setAppName(String appName) {
                result.appName = intern.apply(appName.toUpperCase(Locale.ROOT));
                return this;
            }

     补充:Scheuler 任务调度器调度多个周期任务的写法

    package test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    @Slf4j
    public class PlainTest {
    
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    log.error("5s" + Thread.currentThread().getName());
    
                    // 再次任务调度
                    scheduledExecutorService.schedule(this, 5, TimeUnit.SECONDS);
                }
            }, 5, TimeUnit.SECONDS);
    
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    log.error("10s" + Thread.currentThread().getName());
    
                    scheduledExecutorService.schedule(this, 10, TimeUnit.SECONDS);
                }
            }, 10, TimeUnit.SECONDS);
        }
    }

    补充:集群模式下Client 发送请求的规则 

      其实Eureka集群是没有主从节点之后,都是平等节点。EurekaServer 收到客户端请求的操作是: 如果正常操作完成,会通知兄弟姐妹节点做同等的操作。

      EurekaClient 的操作是,从配置的集群节点默认选择最后一个为发送的节点;如果当前节点不可用则重试前面的节点。

    1. 集群模式客户端的配置

    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka

    2. 以注册为入口研究:

    (1) com.netflix.discovery.DiscoveryClient#register 方法为入口:

    (2) 方法调用到 com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator#register

        public EurekaHttpResponse<Void> register(final InstanceInfo info) {
            return execute(new RequestExecutor<Void>() {
                @Override
                public EurekaHttpResponse<Void> execute(EurekaHttpClient delegate) {
                    return delegate.register(info);
                }
    
                @Override
                public RequestType getRequestType() {
                    return RequestType.Register;
                }
            });
        }

    (3) 接下来调用com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient#execute

    (4) 接下来调用到com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient#execute

        protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
            List<EurekaEndpoint> candidateHosts = null;
            int endpointIdx = 0;
            for (int retry = 0; retry < numberOfRetries; retry++) {
                EurekaHttpClient currentHttpClient = delegate.get();
                EurekaEndpoint currentEndpoint = null;
                if (currentHttpClient == null) {
                    if (candidateHosts == null) {
                        candidateHosts = getHostCandidates();
                        if (candidateHosts.isEmpty()) {
                            throw new TransportException("There is no known eureka server; cluster server list is empty");
                        }
                    }
                    if (endpointIdx >= candidateHosts.size()) {
                        throw new TransportException("Cannot execute request on any known server");
                    }
    
                    currentEndpoint = candidateHosts.get(endpointIdx++);
                    currentHttpClient = clientFactory.newClient(currentEndpoint);
                }
    
                try {
                    EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient);
                    if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) {
                        delegate.set(currentHttpClient);
                        if (retry > 0) {
                            logger.info("Request execution succeeded on retry #{}", retry);
                        }
                        return response;
                    }
                    logger.warn("Request execution failure with status code {}; retrying on another server if available", response.getStatusCode());
                } catch (Exception e) {
                    logger.warn("Request execution failed with message: {}", e.getMessage());  // just log message as the underlying client should log the stacktrace
                }
    
                // Connection error or 5xx from the server that must be retried on another server
                delegate.compareAndSet(currentHttpClient, null);
                if (currentEndpoint != null) {
                    quarantineSet.add(currentEndpoint);
                }
            }
            throw new TransportException("Retry limit reached; giving up on completing the request");
        }

    方法内部第一次进来currentHttpClient 为空,然后创建currentHttpClient 对象

    获取到的List<EurekaEndpoint> 备用端点如下:

     然后以第一个的地址创建EurekaHttpClient 之后存入delegate。创建的对象如下:

     (5) 假设我们的7002端口服务断掉: 则不会走try代码块的return 语句。 打印日志之后走最后的清除currentHttpClient 代码。然后retry++进行下一次尝试,打印的日志如下:

    2021-03-16 20:24:42.632 ERROR 33480 --- [           main] c.n.d.s.t.d.RedirectingEurekaHttpClient  : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:7002/eureka/}
    
    com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
        at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
        at com.sun.jersey.api.client.filter.GZIPContentEncodingFilter.handle(GZIPContentEncodingFilter.java:123) ~[jersey-client-1.19.1.jar:1.19.1]
        at com.netflix.discovery.EurekaIdentityHeaderFilter.handle(EurekaIdentityHeaderFilter.java:27) ~[eureka-client-1.9.13.jar:1.9.13]
        at com.sun.jersey.api.client.Client.handle(Client.java:652) ~[jersey-client-1.19.1.jar:1.19.1]
        at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682) ~[jersey-client-1.19.1.jar:1.19.1]
        at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74) ~[jersey-client-1.19.1.jar:1.19.1]
        at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:509) ~[jersey-client-1.19.1.jar:1.19.1]
        at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.getApplicationsInternal(AbstractJerseyEurekaHttpClient.java:194) ~[eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.getApplications(AbstractJerseyEurekaHttpClient.java:165) ~[eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient.execute(MetricsCollectingEurekaHttpClient.java:73) ~[eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.executeOnNewServer(RedirectingEurekaHttpClient.java:118) ~[eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.execute(RedirectingEurekaHttpClient.java:79) ~[eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:120) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.DiscoveryClient.getAndStoreFullRegistry(DiscoveryClient.java:1069) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.DiscoveryClient.fetchRegistry(DiscoveryClient.java:983) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:430) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:276) [eureka-client-1.9.13.jar:1.9.13]
        at com.netflix.discovery.DiscoveryClient.<init>(DiscoveryClient.java:272) [eureka-client-1.9.13.jar:1.9.13]
        at org.springframework.cloud.netflix.eureka.CloudEurekaClient.<init>(CloudEurekaClient.java:67) [spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration.eurekaClient(EurekaClientAutoConfiguration.java:324) [spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_171]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_171]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_171]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_171]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:636) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$1(AbstractBeanFactory.java:359) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.cloud.context.scope.GenericScope$BeanLifecycleWrapper.getBean(GenericScope.java:389) ~[spring-cloud-context-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.cloud.context.scope.GenericScope.get(GenericScope.java:186) ~[spring-cloud-context-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:356) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) [spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration.getTargetObject(EurekaRegistration.java:129) ~[spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration.getEurekaClient(EurekaRegistration.java:117) ~[spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_171]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_171]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_171]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_171]
        at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:499) ~[spring-cloud-context-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration$$EnhancerBySpringCGLIB$$cfe6ed5b.getEurekaClient(<generated>) ~[spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry.maybeInitializeClient(EurekaServiceRegistry.java:57) ~[spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry.register(EurekaServiceRegistry.java:38) ~[spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration.start(EurekaAutoServiceRegistration.java:83) ~[spring-cloud-netflix-eureka-client-2.2.1.RELEASE.jar:2.2.1.RELEASE]
        at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:182) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:53) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:360) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:158) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:122) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:894) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:162) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:553) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
        at cn.qz.cloud.PaymentHystrixMain8081.main(PaymentHystrixMain8081.java:32) ~[classes/:na]
    Caused by: java.net.ConnectException: Connection refused: connect
        at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) ~[na:1.8.0_171]
        at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85) ~[na:1.8.0_171]
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_171]
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_171]
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_171]
        at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.8.0_171]
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_171]
        at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_171]
        at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:144) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:134) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:605) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:440) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:118) ~[httpclient-4.5.10.jar:4.5.10]
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) ~[httpclient-4.5.10.jar:4.5.10]
        at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:173) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
        ... 75 common frames omitted
    
    2021-03-16 20:24:48.653  WARN 33480 --- [           main] c.n.d.s.t.d.RetryableEurekaHttpClient    : Request execution failed with message: java.net.ConnectException: Connection refused: connect
    2021-03-16 20:25:38.674  INFO 33480 --- [           main] c.n.d.s.t.d.RetryableEurekaHttpClient    : Request execution succeeded on retry #1

    (6) 如果EurekaServer 下次上线又可以作为备用服务节点进行选择, 因为每次请求进来都是for循环开始。 如果这时候7001 也下线,则两个节点都下线,进来转一圈后报错。 如果有7001或者7002上线,在下次请求进来时会选择节点进行尝试

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    python基础--选择排序
    python基础--冒泡排序
    python基础----以面向对象的思想编写游戏技能系统
    python基础知识整理
    输入一个整数n,输出该整数中重复的数字,如果没有重复出现的数字则输出no repeat number!
    输入今天以前一个日期,算离今天的天数
    有1020个西瓜,第一天卖一半多两个,以后每天卖剩下的一半多两个,问几天以后能卖完?
    筛选法求素数
    冒泡、选择、插入、二分插入、希尔排序、快排、二分查找、去掉重复值
    n进制转m进制
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/14513239.html
Copyright © 2011-2022 走看看