03.Spring Cloud 之 Eureka Server 源码解析

1. 环境搭建

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring,工程是 tutorial-spring-cloud/tutorial-spring-cloud-provider/tutorial-spring-cloud-provider-eureka-5002

2. 源码解析

详细的源码注释可参考 https://github.com/masteryourself/eureka

2.1 EurekaClientAutoConfiguration 装配流程

EurekaClientAutoConfiguration

spring-cloud-starter-netflix-eureka-client-xxx.jar -> spring-cloud-netflix-eureka-client-xxx.jar -> 定义 spring.factories 文件 -> 向容器中导入 EurekaClientAutoConfiguration 组件,其中配置文件内容如下:

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

EurekaClientAutoConfiguration -> EurekaClientConfigBean + DiscoveryClient(它是一个接口,实际上用的是 CloudEurekaClient 类型)

EurekaClientConfigBean 其实就是配置类,自动关联 eureka.client 前缀的配置文件

CloudEurekaClient 继承了 DiscoveryClient 类,注意 DiscoveryClient 类是 netflix 提供的,并不属于 spring cloud,在构造 CloudEurekaClient 类时,首先会调用父类的构造方法 super(applicationInfoManager, config, args),在 DiscoveryClient 的构造函数里会完成一系列初始化工作

2.2 服务注册

2.2.1 流程分析

服务启动

2.2.2 核心源码剖析
1. com.netflix.discovery.DiscoveryClient#init()
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
    
    ...

    // 获取注册信息
    // 在 Spring Cloud 中,由 fetch-registry 配置控制 shouldFetchRegistry 属性,此值默认为 true,客户端都为 true,服务端不需要
    if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
        // 获取失败,从本地备份恢复
        fetchRegistryFromBackup();
    }

    ...

    if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
        try {
            // 向注册中心注册服务
            if (!register() ) {
                throw new IllegalStateException("Registration error at startup. Invalid server response.");
            }
        } catch (Throwable th) {
            logger.error("Registration error at startup: {}", th.getMessage());
            throw new IllegalStateException(th);
        }
    }

    // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
    // 初始化定时任务,包含 3 个任务
    initScheduledTasks();

    ...
    
}
2. com.netflix.discovery.DiscoveryClient#fetchRegistry
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
    Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

    try {
        // If the delta is disabled or if it is the first time, get all
        // applications
        // 第一次的 Applications 是刚刚创建出来的,里面没有任何数据
        // 后面定时任务触发的就是增量更新数据
        Applications applications = getApplications();

        if (clientConfig.shouldDisableDelta()
                || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                || forceFullRegistryFetch
                || (applications == null)
                || (applications.getRegisteredApplications().size() == 0)
                || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
        {
            
            ...
            
            logger.info("Application version is -1: {}", (applications.getVersion() == -1));
            // 全量存储
            getAndStoreFullRegistry();
        } else {
            // 增量更新数据
            getAndUpdateDelta(applications);
        }
        applications.setAppsHashCode(applications.getReconcileHashCode());
        logTotalInstances();
    }
    
    ...
    
}
3. com.netflix.discovery.DiscoveryClient#register
boolean register() throws Throwable {
    logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
    EurekaHttpResponse<Void> httpResponse;
    try {
        // 向注册中心注册
        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    }
    
    ...
    
}
4. com.netflix.discovery.DiscoveryClient#initScheduledTasks
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
        // 创建实例信息复制器线程,定时更新 client 信息给服务端
        instanceInfoReplicator = new InstanceInfoReplicator(
                this,
                instanceInfo,
                clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                2); // burstSize

        ...
        
        // 启动实例信息复制器线程
        instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
    } else {
        logger.info("Not registering with Eureka server per configuration");
    }
}

2.3 服务下线

2.3.1 流程分析

服务关闭

2.3.2 核心源码剖析
1. com.netflix.discovery.DiscoveryClient#shutdown
public synchronized void shutdown() {
    if (isShutdown.compareAndSet(false, true)) {
        logger.info("Shutting down DiscoveryClient ...");

        if (statusChangeListener != null && applicationInfoManager != null) {
            applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
        }
        // 取消定时任务
        cancelScheduledTasks();

        // If APPINFO was registered
        if (applicationInfoManager != null
                && clientConfig.shouldRegisterWithEureka()
                && clientConfig.shouldUnregisterOnShutdown()) {
            // 设置状态为 down
            applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
            // 服务下线
            unregister();
        }

        if (eurekaTransport != null) {
            eurekaTransport.shutdown();
        }

        heartbeatStalenessMonitor.shutdown();
        registryStalenessMonitor.shutdown();

        logger.info("Completed shut down of DiscoveryClient");
    }
}
2. com.netflix.discovery.DiscoveryClient#cancelScheduledTasks
private void cancelScheduledTasks() {
    // 取消实例信息复制器
    if (instanceInfoReplicator != null) {
        instanceInfoReplicator.stop();
    }
    // 取消心跳定时任务
    if (heartbeatExecutor != null) {
        heartbeatExecutor.shutdownNow();
    }
    // 取消刷新定时任务
    if (cacheRefreshExecutor != null) {
        cacheRefreshExecutor.shutdownNow();
    }
    // 取消总的定时任务
    if (scheduler != null) {
        scheduler.shutdownNow();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章