Spring Cloud Eureka Client源代码学习笔记

将一个Spring Boot应用注册到Eureka Server或者是从Eureka Server获取服务列表时,主要做两件事: 应用启动类配置 @EnableDiscoveryClient 注解并且在applicaiton.properties 中用eureka.client.serviceUrl.defaultZone参数指定服务注册中心的位置。 本章主要从源代码的角度学学Spring Cloud Eureka 的实现
 

Eureka Client客户端

1.@EnableDiscoveryClient 注解

从源码中可以看到,@EnableDiscoveryClient注解是一个@Import(EnableDiscoveryClientImportSelector.class)表示引用类EnableDiscoveryClientImportSelector

/**
 * Annotation to enable a DiscoveryClient implementation.
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

	/**
	 * If true, the ServiceRegistry will automatically register the local server.
	 */
	boolean autoRegister() default true;
}

EnableDiscoveryClientImportSelector类继承Spring Cloud的类 SpringFactoryImportSelector,并复写了其ImportSelector#selectImports接口实现, ImportSelector接口是spring中导入外部配置的核心接口,可以通过指定的选择条件来决定哪些类被注册到Spring中。 EnableDiscoveryClientImportSelector重写接口实现,将AutoServiceRegistrationConfiguration 加入import数组列表中,如果这个类没初始化的话,服务注册上去了也不能提供服务的,因为默认启动状态是STARTING,而如果要进行服务的话,需要是UP ,AutoServiceRegistrationConfiguration就可以让他变成UP

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

2.EurekaAutoServiceRegistration

从源码中可以看到 EurekaAutoServiceRegistration 实现了SmartLifecycle接口start()方法。SmartLifecycle接口的作用为当Spring容器加载所有bean并完成初始化之后,会接着回调实现该接口的类中对应的方法(start()方法)。通过此方法实现应用启动注册服务

//实现接口SmartLifecycle
public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered {
    private static final Log log = LogFactory.getLog(EurekaAutoServiceRegistration.class);
    private AtomicBoolean running = new AtomicBoolean(false);
    private int order = 0;
    private AtomicInteger port = new AtomicInteger(0);
    private ApplicationContext context;
    private EurekaServiceRegistry serviceRegistry;
    private EurekaRegistration registration;

    public EurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry serviceRegistry, EurekaRegistration registration) {
        this.context = context;
        this.serviceRegistry = serviceRegistry;
        this.registration = registration;
    }

    public void start() {
        if (this.port.get() != 0 && this.registration.getNonSecurePort() == 0) {
            this.registration.setNonSecurePort(this.port.get());
        }

        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
            //1. 通过EurekaServiceRegistry 对象注册服务
            this.serviceRegistry.register(this.registration);
            //2. 发布通知事件
            this.context.publishEvent(new InstanceRegisteredEvent(this, this.registration.getInstanceConfig()));
            //3. 设置启动=true
            this.running.set(true);
        }

    }
    //....
}

3.EurekaServiceRegistry

实现注册EurekaRegistration 会有三个操作

  1. 注册EurekaClient,这里使用的对象是CloudEurekaClient ,此对象继承DiscoveryClient ,而DiscoveryClient对象实现接口EurekaClient
  2. 设置Eureka 实例的状态为UP ,UP状态表示准备接收流量
  3. 如果设置了HealthCheckHandler则将其设置到EurekaClient中, HealthCheckHandler 接口可以用于提供比现有的更精细的运行状况检查
public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration> {
    //...

    public void register(EurekaRegistration reg) {
        //1.初始化
        this.maybeInitializeClient(reg);

        if (log.isInfoEnabled()) {
            log.info("Registering application " + reg.getInstanceConfig().getAppname() + " with eureka with status " + reg.getInstanceConfig().getInitialStatus());
        }
        // setInstanceStatus设置状态为UP
                reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());    
        // 设置心跳Handler
        if (reg.getHealthCheckHandler() != null) {
            reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler());
        }

    }

    private void maybeInitializeClient(EurekaRegistration reg) {
         reg.getApplicationInfoManager().getInfo();
         //在getEurekaClient方法调用的时候创建DiscoveryClient,
         //CloudEurekaClient 集成了DiscoveryClient
         reg.getEurekaClient().getApplications();
    }
    
    //...
}

 reg#getEurekaClient 方法中会先判断如果还没有生成CloudEurekaClient ,则通过getTargetObject以代理的方式生成CloudEurekaClient 并完成其注册

public class EurekaRegistration implements Registration {

	public CloudEurekaClient getEurekaClient() {
		if (this.cloudEurekaClient.get() == null) {
			try {
				this.cloudEurekaClient.compareAndSet(null, getTargetObject(eurekaClient, CloudEurekaClient.class));
			} catch (Exception e) {
				log.error("error getting CloudEurekaClient", e);
			}
		}
		return this.cloudEurekaClient.get();
	}

	protected <T> T getTargetObject(Object proxy, Class<T> targetClass) throws Exception {
		if (AopUtils.isJdkDynamicProxy(proxy)) {
			return (T) ((Advised) proxy).getTargetSource().getTarget();
		} else {
			return (T) proxy; // expected to be cglib proxy then, which is simply a specialized class
		}
	}
}

4.DiscoveryClient

先看DiscoveryClient的注释,注释中大致意思是

这个类用于帮助与Eureka Server互相协作,主要负责内容为

  • 向Eureka Server注册服务实例
  • 向Eureka Server服务租约
  • 当服务关闭期间,向Eureka Server取消掉租约
  • 查询Eureka Server中的服务实例信息列表

DiscoveryClient 实现接口EurekaClient, 它的主要作用是用于与Eureka服务器进行交互的类,DiscoveryClient初始化的时候会实现两个调度 ,详情如下

@Singleton
public class DiscoveryClient implements EurekaClient {
    private static final Logger logger = LoggerFactory.getLogger(DiscoveryClient.class);
    private final ScheduledExecutorService scheduler;
    // additional executors for supervised subtasks
    private final ThreadPoolExecutor heartbeatExecutor; //心跳检测的线程池
    private final ThreadPoolExecutor cacheRefreshExecutor;//缓存服务的线程池
    private final EurekaTransport eurekaTransport;

     @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider) {

        //略。。
        try {
            scheduler = Executors.newScheduledThreadPool(3,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());
            
            //设置心跳线程池:通知Eureka Server 实现续约
            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

             //..
        } catch (Throwable e) {
            throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
        }

        //....
        
        //初始化调度任务
        //调度1: CacheRefreshThread线程实现定时执行,实现更新Eureka Server注册的服务
        //调度2:HeartbeatThread宣城定时执行,心跳通知Eureka Server实现服务续约
        initScheduledTasks();

    }


}

4.1服务注册&续约

上面提到的DiscoveryClient构造方法中initScheduledTasks中实现了服务注册,

当配置中配置为需要向Eureka注册服务时候

1. 调度中使用HeartbeatThread 实现定时向 Eureka Server 发送消息,告诉 Eureka Server当前服务还“活着”,防止Eureka Server的提出任务将本服务从服务列表中删除,也就是服务续约。 

参数eureka.client.less-renew-interval-in-seconds 配置客户端指定的续订间隔设置(秒),默认为30秒

2. InstanceInfoReplicator实现了Runnable接口, 其run()方法实现刷新DiscoverClinet配置信息,并且调用discoveryClient.register()方法实现向Eureka Server注册

参数eureka.client.instance.info.replication.interval.seconds配置更新受理信息到Eureka服务端的间隔时间(秒),默认为30秒

if (clientConfig.shouldRegisterWithEureka()) {

            // 通过HeartbeatThread()对象实现心跳
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            // InstanceInfo replicator
            // instanceInfoReplicator对象实现了Runnable
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

            

            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            //实现服务注册
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } 

4.2 服务获取

调度实现中, 使用CacheRefreshThread 实现定时从Eureka Server获取其上的注册服务列表,   

参数eureka.client.registry-fetch-interval-seconds 配置从中获取注册表信息的频率(秒) ,默认为30秒

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

整理时序图

Eureka配置总结

属性

属性作用说明

eureka.instance.hostname

Eureka Serve服务实例主机名,例如localhost
eureka.instance.lease-expiration-duration-in-seconds Eureka Server将超过配置时间没有续约的服务剔除,默认90秒

eureka.client.serviceUrl.defaultZon

指定服务注册中心的地址

eureka.client.register-with-eureka

是否注册中心注册,默认=TRUE

eureka.client.fetch-registry

是否需要检索服务,默认=TRUE
eureka.client.registry-fetch-interval-seconds Eureka Client配置从中获取注册表信息的频率(秒) ,默认为30秒
eureka.instance.less-renew-interval-in-seconds Eureka Client配置客户端指定的续订间隔设置(秒),默认为30秒
eureka.client.instance.info.replication.interval.seconds 配置更新受理信息到Eureka服务端的间隔时间(秒),默认为30秒

    

    

 上一篇:Spring Cloud Eureka 概念与示例

猜你喜欢

转载自blog.csdn.net/Beijing_L/article/details/120996857