【Spring Cloud】SpringCloud 如何集成 Eureka-Client

262 阅读3分钟

这是我参与更文挑战的第9天,活动详情查看:更文挑战

一、前言

spring-cloud-starter-eureka-server 项目,就是对 eureka-server 进行了封装的 spring boot 项目。对应的官网:github.com/spring-clou…


可以对照之前 eureka-clien 源码分析:链接


既然都学习了 eureka-client 了,为什么还要学习 spring-cloud-starter-eureka-client

  1. 学习 spring-cloud 如何封装 eureka-client
  2. 学习 spring-cloud 如何启动 eureka-client

spring boot 集成 eureka-server 只需要加个注解 @EnableEurekaClient,代码如下:

@SpringBootApplication
@@EnableEurekaClient
public class EurekaClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }
}

小结

问题:

  1. spring-cloud 如何封装 eureka-client
  2. spring-cloud 如何启动 eureka-client

1. spring-cloud 如何封装 eureka-client

启动主要分为两大步:

  1. spring boot 启动后,将内嵌 tomcat 容器启动,把自身作为一个 web 应用启动
  2. 跟着 @EnableEurekaClient 注解,触发 EurekaClientAutoConfiguration 执行:
    1. application.yml 读取配置
    2. 完成 DiscoveryClient 的初始化和启动
    3. 触发 register() 服务注册,向 eureka-server 注册

2. spring-cloud 如何启动 eureka-client

最容易想到的是,直接拷贝代码。

但拷贝代码,又要减少耦合,保证个模块独立性,那么就需要 spring boot 的特征自动装配。

步骤可分为:

  1. application.yml 读取配置
  2. 完成 DiscoveryClient 的初始化和启动



二、直接怼源码

着手点就是 @EnableEurekaClient

@EnableEurekaClient 注解:就是在 spring boot 将自身的 web 容器启动之后,再把 eureka-client 启动起来。

@EnableEurekaClient 入手,主要看三个类,也可叫为三个步骤:

  1. EurekaClientAutoConfiguration 初始化
  2. EurekaAutoServiceRegistration 初始化:对应原来的 InstanceInfoReplicator 组件里面的服务注册

IDEA 中按 CTRL + 点击 进入 @EnableEurekaServer,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableEurekaClient {
}

  1. EurekaClientAutoConfiguration 初始化

@EnableEurekaClient 会触发 EurekaClientAutoConfiguration 初始化,代码如下:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(...)
public class EurekaClientAutoConfiguration {

    // 初始化一大堆 eureka client 组件
    // 并将这些组件生成对应 bean,加载进 spring 容器中
    ... ...
}

完成对 DiscoveryClient 的构造和初始化,eureka client 初始化和启动的流程,全部在 DiscoveryClient 中的。

其中 EurekaDiscoveryClientspring cloudeureka 原生的 DiscoveryClient 进行了一层封装,同时实现了 eurekaDiscoveryClient 接口,依赖了一个原生的 EurekaClient


  1. EurekaAutoServiceRegistration 初始化

其源码如下:

public class EurekaAutoServiceRegistration 
    implements AutoServiceRegistration, SmartLifecycle, Ordered {
    
    // 值得说明的一点:SmartLifecycle ,在 bean 加载初始化完成之后,会调用其 start() 方法。
    
    // 其他参数
    ... ...
	private EurekaServiceRegistry serviceRegistry;
    
    // 通过实现 SmartLifecycle,在 spring 生命周期调用
	@Override
	public void start() {
        ... ...
        
        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {

            // 服务注册,但里面好像根本没有对应服务注册的逻辑
            // 可能因为 eureka-client 代码太烂了,spring-cloud 这帮人也没办法拯救
            this.serviceRegistry.register(this.registration);
            
            ... ...
        }
	}
}

// 那么进入 EurekaServiceRegistry.register() 看一眼:
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
    ... ...
    @Override
	public void register(EurekaRegistration reg) {
		... ...
        // ApplicationInfoManager.setInstanceStatus() 调用
        // 会通知自己所有的注册的监听器,状态发生改变
        // 这个监听器,是 DiscoveryClient.initScheduledTasks() 时候给创建的
		reg.getApplicationInfoManager()
				.setInstanceStatus(reg.getInstanceConfig().getInitialStatus());

		... ... 
	}
    ... ...
}

// 最后对照之前 eureka-client 分析,找到:
class InstanceInfoReplicator implements Runnable {
    public void start(int initialDelayMs) {
        if (this.started.compareAndSet(false, true)) {
            this.instanceInfo.setIsDirty();
            Future next = this.scheduler
                .schedule(this, (long)initialDelayMs, TimeUnit.SECONDS);
            this.scheduledPeriodicRef.set(next);
        }
    }
    
    public boolean onDemandUpdate() {
        if (this.rateLimiter.acquire(this.burstSize, (long)this.allowedRatePerMinute)) {
            if (!this.scheduler.isShutdown()) {
                this.scheduler.submit(new Runnable() {
                    public void run() {
                        ... ...
                        // 按理来说,启动线程,不用 run(), 而是 start()
                        // 这块代码还是比较乱的
                        InstanceInfoReplicator.this.run();
                    }
                });
                return true;
            } else {
                ... ...
            }
        } else {
            ... ...
        }
    }
    
    public void run() {
        // 一大堆代码
        ... ...
        this.discoveryClient.register();
        
        ... ...
    }
}

感觉这块写得还是挺烂的,项目结构不够清晰、调用逻辑混乱、线程调用乱用。