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();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章