Spring Eureka 源码解析

本文将简要分析一下关于 Spring Eureka 相关的一些必要的源代码,对应的版本:Spring Cloud 2021.0.1

@EnableEurekaServer 注解

@EnableEurekaServer 注解标记当前的应用程序作为一个注册中心,查看 @EnableEurekaServer 的源代码,具体内容如下所示:

package org.springframework.cloud.netflix.eureka.server;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Import;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}

可以看到,该注解导入了 EurekaServerMarkerConfiguration 配置类,继续查看 EurekaServerMarkerConfiguration 对应的源代码,具体内容如下所示:

package org.springframework.cloud.netflix.eureka.server;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {

	@Bean
	public Marker eurekaServerMarkerBean() {
		return new Marker();
	}

	class Marker {

	}

}

可以看到,该配置类只是定义了一个 Mark 类型的 Bean。可以看到,Mark 就是在 EurekaServerMarkerConfiguration 中定义的一个类,该类并没有任何特殊的地方。

由于 Spring-Boot 的自动配置是通过加载 spring.factories 文件中的内容来完成自动配置的,因此,可以试着从当前 EurekaServerMarkerConfiguration 配置类所在的包的 spring.factories 文件入手来进一步的进行分析

查看 EurekaServerMarkerConfiguration 对应的包的 spring.factories 文件,具体内容如下所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

可以看到,在 spring.factories 文件中只有一个自动配置类 .EurekaServerAutoConfiguratio,查看该类对应的源代码,具体的内容如下所示:

package org.springframework.cloud.netflix.eureka.server;

// 省略一些其它的包的导入

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
/*
    可以看到,在这里定义了一个条件 Bean,只有当这个 Bean 出现在 
    Spring 上下文环境中时,才会将 EurekaServerAutoConfiguration Bean 
    加载到 Spring 应用的上下文中
*/
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
    // 省略具体的源代码
}

也就是说,只有 Spring 中存在 EurekaServerMarkerConfiguration.Mark 类型的 Bean 时,才会将 EurekaServerAutoConfiguration 自动配置类加载到 Spring 上下文中,从而实现 Eureka 注册中心的功能

DashBoard Path

我们知道,当成功启动一个 Spring Eureka 注册中心时,访问对应的 IP 和端口即可看到对应的控制台界面,这里的访问路径是通过 EurekaDashboardProperties 来进行配置的。

查看 EurekaDashboardProperties 对应的源代码,如下所示:

package org.springframework.cloud.netflix.eureka.server;

import org.springframework.boot.context.properties.ConfigurationProperties;

/*
    这里通过配置属性的方式来进行 dashboard 访问路径的配置    
*/
@ConfigurationProperties("eureka.dashboard")
public class EurekaDashboardProperties {
    // 默认的 dashboard 访问路径
	private String path = "/";
    
    // 省略一部分不是特别重要的代码
}

这就是为什么直接访问注册中心的 IP 加端口号就能访问到控制台的原因

EnableDiscoveryClient 注解

实际上,对于普通的应用服务来讲(为了和注册中心区分,将非注册中心的服务称为客户端),加上 @EnableDiscoveryClient 就能够实现服务的自动注册和发现,@EnableDiscoveryClient 注解对应的源代码如下所示:

package org.springframework.cloud.client.discovery;

// 省略一部分不太重要的代码

public @interface EnableDiscoveryClient {
    /*
        可以看到,这里的默认值为 true,因此默认情况下,
        会将当前的服务注册到注册中心
    */
	boolean autoRegister() default true;
}

这是加上 @EnableDiscoveryClient 注解后的默认属性,不是

即使在启动类上没有加上 @EnableDiscoveryClient 注解,该服务依旧能够被注册中心发现,这是由于在 Spring Eureka Client 的自动配置类中默认了这种行为。

查看 spring-cloud-starter-netflix-eureka-client 包中的 spring.factories 文件,具体的内容如下所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration

org.springframework.boot.BootstrapRegistryInitializer=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapper

其中,org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration 自动配置类定义了这一默认的行为,具体的源代码如下所示:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = { "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
		"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
        // 在这个自动配置类中定义了服务发现的默认行为
		"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
		"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
    // 省略一部分不太重要的代码
}

继续查看 EurekaDiscoveryClientConfiguration 配置类的源代码,相关的内容如下所示:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
// 重点在这里,可以看到,这里的默认值为 true,即默认允许被自动发现和注册
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
public class EurekaDiscoveryClientConfiguration {
    // 省略一部分不太重要的代码
}

EurekaServerAutoConfiguration 解析

前文提到,在 EurekaServerMarkerConfiguration.Marker 类型的 Bean 装载到 Spring 上下文环境中时,才会加载 EurekaServerAutoConfiguration,这个配置类定义了有关 Eureka 服务端的相关配置,在这里进行简要的解析

相关属性介绍

EurekaServerAutoConfiguration 定义的相关属性如下:

// 省略一部分包的导入以及注解的声明

public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
    @Autowired
	private ApplicationInfoManager applicationInfoManager;

    /*
        Eureka 服务端相关的配置
    */
    @Autowired
	private EurekaServerConfig eurekaServerConfig;

    /*
        Eureka 客户端的相关配置
    */
    @Autowired
	private EurekaClientConfig eurekaClientConfig;        
    
    @Autowired
	private EurekaClient eurekaClient;

    @Autowired
	private InstanceRegistryProperties instanceRegistryProperties;
}  

服务启动

有关集群和应用服务程序的注册对应的源代码:

// 省略一部分包的导入以及注解的声明

public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
    // 省略部分源代码

    /*
        初始化集群注册表
    */
    @Bean
	public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
		this.eurekaClient.getApplications(); // force initialization
		return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient,
				this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
				this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
	}

    /*
        初始化集群节点集合
    */
	@Bean
	@ConditionalOnMissingBean
	public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs,
			ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
		return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs,
				this.applicationInfoManager, replicationClientAdditionalFilters);
	}

    /*
        Eureka 服务端上下文
    */
	@Bean
	@ConditionalOnMissingBean
	public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry,
			PeerEurekaNodes peerEurekaNodes) {
		return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes,
				this.applicationInfoManager);
	}

    /*
        Eureka 服务端启动类
    */
	@Bean
	public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
			EurekaServerContext serverContext) {
		return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig,
				registry, serverContext);
	}
}

具体的初始化类,可以看一下 EurekaServerAutoConfiguration 引入的注解:

@Configuration(proxyBeanMethods = false)
/*
    可以看到,这里引入了 EurekaServerInitializerConfiguration 类型的 Bean,
    该 Bean 的作用是用于初始化 Eureka Server
*/
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
    // 省略一部分具体的代码
}

继续查看 EurekaServerInitializerConfiguration 对应的源代码,如下所示:

// 省略一部分包的导入

@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration 
    /*
        注意这里的 SmartLifecycle 接口,这个接口继承了 Lifecycle,
        用于管理 Spring Context 的生命周期
        
        具体到当前分析的环境,由于是初始化 Eureka Server,因此需要调用
        start() 方法
    */
    implements ServletContextAware, SmartLifecycle, Ordered {

	private static final Log log = LogFactory.getLog(EurekaServerInitializerConfiguration.class);

	@Autowired
	private EurekaServerConfig eurekaServerConfig;

	private ServletContext servletContext;

	@Autowired
	private ApplicationContext applicationContext;

	@Autowired
	private EurekaServerBootstrap eurekaServerBootstrap;

	private boolean running;

	private int order = 1;

	@Override
	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

	@Override
	public void start() {
		new Thread(() -> {
			try {
				// TODO: is this class even needed now?
                /*
                    Eureka Server 启动之前的初始化
                */
				eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
				log.info("Started Eureka Server");

                /*
                    由于 Eureka Server 已经启动了,因此发布 Eureka Server 注册中心的启动事件
                */
				publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                
                // 设置 Eureka 的运行状态
				EurekaServerInitializerConfiguration.this.running = true;
                
                // 现在 Eureka Server 已经完全启动了,此时再发布一个 Eureka Server 启动事件
				publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
			} catch (Exception ex) {
				// Help!
				log.error("Could not initialize Eureka servlet context", ex);
			}
		}).start();
    }
    
    // 省略一部分代码
}

由于 EurekaServerInitializerConfiguration 实现了 SmartLifecycle,因此会被 Spring 容器进行相对应的处理,这里不做过多的介绍,有关 Spring IOC 部分的源码解析,可以查看 Spring IOC 容器源码分析_Javadoop,这是一篇关于 Spring IOC 源码分析比较好的文章。

在这里只需要知道由于 EurekaServerInitializerConfiguration 实现了 SmartLifecycle 接口,因此在 Spring 应用上下文的初始化过程中会调用 start() 方法即可

接下来继续分析 contextInitialized(ServletContext) 方法,该方法用于初始化启动 Eureka Server 之前的必要准备。对应的源代码如下所示:

public void contextInitialized(ServletContext context) {
	try {
        /*
            这里的具体实现只是单纯地打印一行日志
        */
		initEurekaEnvironment();
        
        /*
            这里初始化 Eureka Server 的上下文
        */
		initEurekaServerContext();

		context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
	}
	catch (Throwable e) {
		log.error("Cannot bootstrap eureka server :", e);
		throw new RuntimeException("Cannot bootstrap eureka server :", e);
	}
}

比较关系的是 Eureka Server 上下文的设置,继续进入该方法,对应的源代码如下所示:

protected void initEurekaServerContext() throws Exception {
	// For backward compatibility
	JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
	XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);

    /*
        针对 AWS 的特殊处理,一般情况下可能用不到,略过
    */
	if (isAws(this.applicationInfoManager.getInfo())) {
	    this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry,this.applicationInfoManager);
		this.awsBinder.start();
	}

	EurekaServerContextHolder.initialize(this.serverContext);

	log.info("Initialized server context");

	// Copy registry from neighboring eureka node
    /*
        邻接点的数据同步
    */
	int registryCount = this.registry.syncUp();
    
    /*
        服务剔除
    */
	this.registry.openForTraffic(this.applicationInfoManager, registryCount);

	// Register all monitoring statistics.
	EurekaMonitors.registerAllStats();
}

邻节点的数据同步

邻接点的同步 syncUp(),对应的源代码如下所示:

// 省略部分包的导入以及注解的声明

public class PeerAwareInstanceRegistryImpl 
    extends AbstractInstanceRegistry 
    implements PeerAwareInstanceRegistry {
    // 省略部分其它代码    

    @Override
    public int syncUp() {
        // Copy entire entry from neighboring DS node
        int count = 0;

        for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
            if (i > 0) {
                try {
                    Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
                } catch (InterruptedException e) {
                    logger.warn("Interrupted during registry transfer..");
                    break;
                }
            }
            /*
                apps 表示获取到的所有的应用信息
            */
            Applications apps = eurekaClient.getApplications();
            /*
                双层 Map 结构,第一层 Map 存储应用程序
            */
            for (Application app : apps.getRegisteredApplications()) {
                /*
                    第二层存储应用程序对应的实例信息
                */
                for (InstanceInfo instance : app.getInstances()) {
                    try {
                        /*
                            服务的注册实际执行的地方
                        */
                        if (isRegisterable(instance)) {
                            register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
                            count++;
                        }
                    } catch (Throwable t) {
                        logger.error("During DS init copy", t);
                    }
                }
            }
        }
        return count;
    }
}

服务剔除

服务剔除对应 openForTraffic(ApplicationInfoManager, int) 方法,实际对应的对象的源代码如下:

// 省略部分包导入代码

public class PeerAwareInstanceRegistryImpl 
    extends AbstractInstanceRegistry 
    implements PeerAwareInstanceRegistry {
    // 省略部分关系不大的代码
    
    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        // Renewals happen every 30 seconds and for a minute it should be a factor of 2.
        this.expectedNumberOfClientsSendingRenews = count;
        updateRenewsPerMinThreshold();
        logger.info("Got {} instances from neighboring DS node", count);
        logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        
        /*
            这里也是针对 AWS 的处理,一般用不到,略过
        */
        boolean isAws = Name.Amazon == selfName;
        if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            primeAwsReplicas(applicationInfoManager);
        }
        // 针对 AWS 的处理结束

        logger.info("Changing status to UP");
        applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
        super.postInit();
    }   
}

继续查看父类的 postInit() 方法,对应的源代码如下所示:

public abstract class AbstractInstanceRegistry 
    implements InstanceRegistry {
    // 省略部分不太重要的代码

    protected void postInit() {
        renewsLastMin.start();
        if (evictionTaskRef.get() != null) {
            evictionTaskRef.get().cancel();
        }
        // 这里添加一个剔除任务
        evictionTaskRef.set(new EvictionTask());
        
        // 设置这个剔除任务的执行周期
        evictionTimer.schedule(evictionTaskRef.get(),
                serverConfig.getEvictionIntervalTimerInMs(),
                serverConfig.getEvictionIntervalTimerInMs());
    }
}

这个剔除任务的定义对应的源代码如下所示:

class EvictionTask extends TimerTask {
    @Override
    public void run() {
        try {
            long compensationTimeMs = getCompensationTimeMs();
            logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
            evict(compensationTimeMs);
        } catch (Throwable e) {
            logger.error("Could not run the evict task", e);
        }
    }
}

evict(int) 对应的源代码比较长,这里就不再贴出。剔除工作交给 internalCancel(String, String, boolean) 方法来完成。

更加具体一点,internalCancel(String, String, boolean) 是按照实例 id 来删除双层 Map 中对应的元素来实现的。


参考:

[1]

https://mp.weixin.qq.com/s/Xfq5YCaSSc7WgOH6Yqz4zQhttps://mp.weixin.qq.com/s/Xfq5YCaSSc7WgOH6Yqz4zQ

posted @ 2022-02-25 16:47  FatalFlower  阅读(103)  评论(0编辑  收藏  举报