原文:https://blog.tookbra.com/2017/08/25/Spring-Cloud-Eureka-Client-Source/

源码目录结构

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── org
    │   │       └── springframework
    │   │           └── cloud
    │   │               └── netflix
    │   │                   ├── eureka
    │   │                   │   ├── CloudEurekaClient.java
    │   │                   │   ├── CloudEurekaInstanceConfig.java
    │   │                   │   ├── CloudEurekaTransportConfig.java
    │   │                   │   ├── EnableEurekaClient.java
    │   │                   │   ├── EurekaClientAutoConfiguration.java
    │   │                   │   ├── EurekaClientConfigBean.java
    │   │                   │   ├── EurekaConstants.java
    │   │                   │   ├── EurekaDiscoveryClient.java
    │   │                   │   ├── EurekaDiscoveryClientConfiguration.java
    │   │                   │   ├── EurekaHealthCheckHandler.java
    │   │                   │   ├── EurekaHealthIndicator.java
    │   │                   │   ├── EurekaInstanceConfigBean.java
    │   │                   │   ├── InstanceInfoFactory.java
    │   │                   │   ├── MutableDiscoveryClientOptionalArgs.java
    │   │                   │   ├── config
    │   │                   │   │   ├── DiscoveryClientOptionalArgsConfiguration.java
    │   │                   │   │   ├── EurekaClientConfigServerAutoConfiguration.java
    │   │                   │   │   ├── EurekaDiscoveryClientConfigServiceAutoConfiguration.java
    │   │                   │   │   └── EurekaDiscoveryClientConfigServiceBootstrapConfiguration.java
    │   │                   │   ├── http
    │   │                   │   │   ├── EurekaApplications.java
    │   │                   │   │   ├── RestTemplateDiscoveryClientOptionalArgs.java
    │   │                   │   │   ├── RestTemplateEurekaHttpClient.java
    │   │                   │   │   ├── RestTemplateTransportClientFactories.java
    │   │                   │   │   └── RestTemplateTransportClientFactory.java
    │   │                   │   └── serviceregistry
    │   │                   │       ├── EurekaAutoServiceRegistration.java
    │   │                   │       ├── EurekaRegistration.java
    │   │                   │       └── EurekaServiceRegistry.java
    │   │                   └── ribbon
    │   │                       └── eureka
    │   │                           ├── DomainExtractingServerList.java
    │   │                           ├── EurekaRibbonClientConfiguration.java
    │   │                           ├── EurekaServerIntrospector.java
    │   │                           ├── RibbonEurekaAutoConfiguration.java
    │   │                           └── ZoneUtils.java
    │   └── resources
    │       └── META-INF
    │           └── spring.factories
    └── test
        ├── java
        │   └── org
        │       └── springframework
        │           └── cloud
        │               └── netflix
        │                   ├── eureka
        │                   │   ├── ConditionalOnRefreshScopeTests.java
        │                   │   ├── EurekaClientAutoConfigurationTests.java
        │                   │   ├── EurekaClientConfigBeanTests.java
        │                   │   ├── EurekaHealthCheckHandlerTests.java
        │                   │   ├── EurekaInstanceConfigBeanTests.java
        │                   │   ├── InstanceInfoFactoryTests.java
        │                   │   ├── config
        │                   │   │   ├── ConfigRefreshTests.java
        │                   │   │   ├── DiscoveryClientConfigServiceAutoConfigurationTests.java
        │                   │   │   ├── EurekaClientConfigServerAutoConfigurationTests.java
        │                   │   │   ├── JerseyOptionalArgsConfigurationTest.java
        │                   │   │   └── RestTemplateOptionalArgsConfigurationTest.java
        │                   │   ├── healthcheck
        │                   │   │   └── EurekaHealthCheckTests.java
        │                   │   ├── http
        │                   │   │   ├── EurekaServerMockApplication.java
        │                   │   │   ├── RestTemplateEurekaHttpClientTest.java
        │                   │   │   ├── RestTemplateTransportClientFactoriesTest.java
        │                   │   │   └── RestTemplateTransportClientFactoryTest.java
        │                   │   ├── sample
        │                   │   │   ├── ApplicationTests.java
        │                   │   │   ├── EurekaSampleApplication.java
        │                   │   │   └── RefreshEurekaSampleApplication.java
        │                   │   └── serviceregistry
        │                   │       └── EurekaServiceRegistryTests.java
        │                   └── ribbon
        │                       └── eureka
        │                           ├── DomainExtractingServerListTests.java
        │                           ├── EurekaDisabledRibbonClientIntegrationTests.java
        │                           ├── EurekaRibbonClientConfigurationTests.java
        │                           ├── EurekaRibbonClientPreprocessorIntegrationTests.java
        │                           ├── EurekaRibbonClientPropertyOverrideIntegrationTests.java
        │                           ├── RibbonClientPreprocessorIntegrationTests.java
        │                           ├── RibbonEurekaAutoConfigurationTests.java
        │                           └── ZoneUtilsTests.java
        └── resources
            └── application.yml

客户端启动分析

@SpringBootApplication
@EnableDiscoveryClient
public class ClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ClientApplication.class, args);
    }
}

EnableDiscoveryClient类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
    boolean autoRegister() default true;
}
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
        extends SpringFactoryImportSelector<EnableDiscoveryClient> {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        String[] imports = super.selectImports(metadata);

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));

        boolean autoRegister = attributes.getBoolean("autoRegister");

        if (autoRegister) {
            List<String> importsList = new ArrayList<>(Arrays.asList(imports));
            importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
            imports = importsList.toArray(new String[0]);
        }

        return imports;
    }

    ...

}

eureka客户端只要在启动类中加上@EnableDiscoveryClient注解,便表示这是一个eureka客户端
在EnableDiscoveryClient类中,可以看到引入了EnableDiscoveryClientImportSelector类
在super.selectImports方法中加载了META-INF目录下有spring.factories文件中的org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration

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.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration

org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
@Order(2147483547)
public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> {
    public EnableDiscoveryClientImportSelector() {
    }

    //返回需要加载的EurekaDiscoveryClientConfiguration bean
    public String[] selectImports(AnnotationMetadata metadata) {
        //org.springframework.cloud.client.discovery.EnableDiscoveryClient=\org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
        String[] imports = super.selectImports(metadata);
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(this.getAnnotationClass().getName(), true));
        boolean autoRegister = attributes.getBoolean("autoRegister");
        if(autoRegister) {
            List<String> importsList = new ArrayList(Arrays.asList(imports));
            importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
            imports = (String[])importsList.toArray(new String[0]);
        }

        return imports;
    }

    protected boolean isEnabled() {
        return ((Boolean)(new RelaxedPropertyResolver(this.getEnvironment())).getProperty("spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE)).booleanValue();
    }

    protected boolean hasDefaultFactory() {
        return true;
    }
}

spring-boot在启动过程中通过加载、解析spring.factories文件中的信息,对一些bean进行自动装配,在这里我们就不关注了spring-boot 自动配置
在EurekaServerAutoConfiguration类中

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
        CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration")
public class EurekaClientAutoConfiguration {
   ...

   @Bean
       public DiscoveryClient discoveryClient(EurekaInstanceConfig config,
               EurekaClient client) {
           return new EurekaDiscoveryClient(config, client);
       }

       @Configuration
    @ConditionalOnRefreshScope
    protected static class RefreshableEurekaClientConfiguration {

        @Autowired
        private ApplicationContext context;

        @Autowired(required = false)
        private DiscoveryClientOptionalArgs optionalArgs;

        @Bean(destroyMethod = "shutdown")
        @ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
        @org.springframework.cloud.context.config.annotation.RefreshScope
        @Lazy
        public EurekaClient eurekaClient(ApplicationInfoManager manager,
                EurekaClientConfig config, EurekaInstanceConfig instance) {
            manager.getInfo(); // force initialization
            return new CloudEurekaClient(manager, config, this.optionalArgs,
                    this.context);
        }

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

    }

    @Bean
    public DiscoveryClient discoveryClient(EurekaInstanceConfig config,
            EurekaClient client) {
        return new EurekaDiscoveryClient(config, client);
    }

   ...
}

CloudEurekaClient
spring cloud使用CloudEurekaClient对eureka的DiscoveryClient初始化

EurekaAutoServiceRegistration类实现了SmartLifecycle接口的start方法,会在所有spring bean都初始化完成后调用该方法

public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered {
    ...

    @Override
    public void start() {
        // only set the port if the nonSecurePort is 0 and this.port != 0
        if (this.port.get() != 0 && this.registration.getNonSecurePort() == 0) {
            this.registration.setNonSecurePort(this.port.get());
        }

        // only initialize if nonSecurePort is greater than 0 and it isn't already running
        // because of containerPortInitializer below
        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
            // 设置 eureka 初始化状态 eureka.instance.initial-status
            this.serviceRegistry.register(this.registration);
            // 传递eureka注册事件
            // DiscoveryClientHealthIndicator类监听
            this.context.publishEvent(
                    new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
            // 设置在spring容器关闭的时候执行stop方法
            this.running.set(true);
        }
    }

    ....
}
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
    public void register(EurekaRegistration reg) {
        maybeInitializeClient(reg);

        if (log.isInfoEnabled()) {
            log.info("Registering application " + reg.getInstanceConfig().getAppname()
                    + " with eureka with status "
                    + reg.getInstanceConfig().getInitialStatus());
        }

        reg.getApplicationInfoManager()
                .setInstanceStatus(reg.getInstanceConfig().getInitialStatus());

        if (reg.getHealthCheckHandler() != null) {
            reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler());
        }
    }
}
@Singleton
public class DiscoveryClient implements EurekaClient {
    @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {
        if (args != null) {
            this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
            this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
            this.eventListeners.addAll(args.getEventListeners());
        } else {
            this.healthCheckCallbackProvider = null;
            this.healthCheckHandlerProvider = null;
        }

        this.applicationInfoManager = applicationInfoManager;
        //获取 instance
        InstanceInfo myInfo = applicationInfoManager.getInfo();
        //客户端配置
        clientConfig = config;
        staticClientConfig = clientConfig;
        //传输配置,心跳时间等
        transportConfig = config.getTransportConfig();
        instanceInfo = myInfo;
        if (myInfo != null) {
            appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
        } else {
            logger.warn("Setting instanceInfo to a passed in null value");
        }

        this.backupRegistryProvider = backupRegistryProvider;

        this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
        localRegionApps.set(new Applications());

        fetchRegistryGeneration = new AtomicLong(0);

        remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions());
        remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));

        //是否需要信息检索 eureka.client.fetchRegistry=true
        //
        if (config.shouldFetchRegistry()) {
            this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        //是否注册到eureka eureka.client.registerWithEureka=true
        if (config.shouldRegisterWithEureka()) {
            this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        logger.info("Initializing Eureka in region {}", clientConfig.getRegion());

        //如果不开启注册到eureka和从eureka检索服务
        //把定时任务、心跳设置为null
        if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            logger.info("Client configured to neither register nor query for data.");
            scheduler = null;
            heartbeatExecutor = null;
            cacheRefreshExecutor = null;
            eurekaTransport = null;
            instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());

            // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
            // to work with DI'd DiscoveryClient
            DiscoveryManager.getInstance().setDiscoveryClient(this);
            DiscoveryManager.getInstance().setEurekaClientConfig(config);

            initTimestampMs = System.currentTimeMillis();

            logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                    initTimestampMs, this.getApplications().size());
            return;  // no need to setup up an network tasks and we are done
        }

        // 配置作业
        try {
            scheduler = Executors.newScheduledThreadPool(3,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            eurekaTransport = new EurekaTransport();
            scheduleServerEndpointTask(eurekaTransport, args);

            AzToRegionMapper azToRegionMapper;
            // eureka.client.use-dns-for-fetching-service-urls
            if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
                azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
            } else {
                azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
            }
            if (null != remoteRegionsToFetch.get()) {
                azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
            }
            instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
        } catch (Throwable e) {
            throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
        }
        //刷新服务注册地址
        if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
            fetchRegistryFromBackup();
        }

        //初始化作业,心跳,状态、缓存
        initScheduledTasks();
        try {
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register timers", e);
        }

        // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
        // to work with DI'd DiscoveryClient
        DiscoveryManager.getInstance().setDiscoveryClient(this);
        DiscoveryManager.getInstance().setEurekaClientConfig(config);

        initTimestampMs = System.currentTimeMillis();
        logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                initTimestampMs, this.getApplications().size());
    }

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

在DiscoveryClient构造函数中:

  • 创建了scheduler定时任务的线程池,heartbeatExecutor心跳检查线程池(服务续约),cacheRefreshExecutor(服务获取)
  • 然后initScheduledTasks()开启上面三个线程池,往上面3个线程池分别添加相应任务。然后创建了一个instanceInfoReplicator(Runnable任务),然后调用InstanceInfoReplicator.start方法,把这个任务放进上面scheduler定时任务线程池(服务注册并更新)。
public class ApplicationInfoManager {
    public synchronized void setInstanceStatus(InstanceStatus status) {
        InstanceStatus next = instanceStatusMapper.map(status);
        if (next == null) {
            return;
        }

        InstanceStatus prev = instanceInfo.setStatus(next);
        if (prev != null) {
            for (StatusChangeListener listener : listeners.values()) {
                try {
                    //状态改变
                    listener.notify(new StatusChangeEvent(prev, next));
                } catch (Exception e) {
                    logger.warn("failed to notify listener: {}", listener.getId(), e);
                }
            }
        }
    }
}
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();
            }
        };

        //eureka.client.on-demand-update-status-change
        //默认为true,一般不设置成false
        if (clientConfig.shouldOnDemandUpdateStatusChange()) {
            //状态改变监听
            applicationInfoManager.registerStatusChangeListener(statusChangeListener);
        }
        //开启
        //初始化实例信息到Eureka服务端的间隔时间 eureka.client.initialInstanceInfoReplicationIntervalSeconds 默认40s
        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
    } else {
        logger.info("Not registering with Eureka server per configuration");
    }
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐