03.Spring Cloud 之 Eureka Client 源码解析

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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值