本文将简要分析一下关于 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

Spring Eureka 源码解析的更多相关文章

  1. Spring Cloud系列(四):Eureka源码解析之客户端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...

  2. Spring系列(三):Spring IoC源码解析

    一.Spring容器类继承图 二.容器前期准备 IoC源码解析入口: /** * @desc: ioc原理解析 启动 * @author: toby * @date: 2019/7/22 22:20 ...

  3. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  4. Spring IoC源码解析之invokeBeanFactoryPostProcessors

    一.Bean工厂的后置处理器 Bean工厂的后置处理器:BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)和BeanDefinitionRegistr ...

  5. Spring IoC源码解析之getBean

    一.实例化所有的非懒加载的单实例Bean 从org.springframework.context.support.AbstractApplicationContext#refresh方法开发,进入到 ...

  6. Spring系列(五):Spring AOP源码解析

    一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...

  7. Spring系列(六):Spring事务源码解析

    一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...

  8. Spring Boot系列(四):Spring Boot源码解析

    一.自动装配原理 之前博文已经讲过,@SpringBootApplication继承了@EnableAutoConfiguration,该注解导入了AutoConfigurationImport Se ...

  9. Spring Security源码解析一:UsernamePasswordAuthenticationFilter之登录流程

    一.前言 spring security安全框架作为spring系列组件中的一个,被广泛的运用在各项目中,那么spring security在程序中的工作流程是个什么样的呢,它是如何进行一系列的鉴权和 ...

  10. Spring Cloud系列(三):Eureka源码解析之服务端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-ser ...

随机推荐

  1. flask中cookies的使用

    flask中cookies的使用 在Flask中对cookie的处理 1. 设置cookie: 设置cookie,默认有效期是临时cookie,浏览器关闭就失效 可以通过 max_age 设置有效期, ...

  2. Jmeter连接数据库sql语句操作,查询后取值做变量

    第一步 :导入jar包 第二步 :创建JDBC Reques 第三步 :创建JDBC Connection Configuration  第四步:在request中输入数据进行操作 Query Typ ...

  3. 特殊符号传到后端发生变异 &amp; &quot;&lt;&gt;

    业务遇到bug,前端传回数据 & ,到后台接收到的数据就是 & 后台接收到的数据就携带了amp;的后缀 网上查找原因,大部分说法是前端传回的数据导致,但是实际并不是,这里是框架的正则过 ...

  4. JUC并发编程学习(五)集合类不安全

    集合类不安全 List不安全 单线程情况下集合类和很多其他的类都是安全的,因为同一时间只有一个线程在对他们进行修改,但是如果是多线程情况下,那么集合类就不一定是安全的,可能会出现一条线程正在修改的同时 ...

  5. Gitlab集成jenkins及docker自动化部署教程

    Gitlab集成jenkins及docker自动化部署教程 能实现提交代码到gitlab后,我们只需要合并代码到指定分支就可以上Jenkins自动拉取最新代码并重新构建部署 1.登录Jenkins点击 ...

  6. 记一次 .NET 某券商论坛系统 卡死分析

    一:背景 1. 讲故事 前几个月有位朋友找到我,说他们的的web程序没有响应了,而且监控发现线程数特别高,内存也特别大,让我帮忙看一下怎么回事,现在回过头来几经波折,回味价值太浓了. 二:程序到底经历 ...

  7. 支持向量机SVM:从数学原理到实际应用

    本篇文章全面深入地探讨了支持向量机(SVM)的各个方面,从基本概念.数学背景到Python和PyTorch的代码实现.文章还涵盖了SVM在文本分类.图像识别.生物信息学.金融预测等多个实际应用场景中的 ...

  8. go并发 - goroutine

    概述 Go并发模型独树一帜,简洁.高效.Go语言最小执行单位称为协程(goroutine),运行时可以创建成千万上个协程,这在Java.C等线程模型中是不可想象的,并发模型是Go的招牌能力之一.很多文 ...

  9. OpenAI宫斗,尘埃落定,微软成最大赢家

    周末被OpenAI董事会闹剧刷屏,ChatGPT之父Sam Altman前一天被踢出董事会,免职CEO,后一天重返OpenAI,目前结局未知. 很多同学想要围观,缺少背景知识,这里老章为大家简单介绍前 ...

  10. vivado仿真(无需testbench)

    vivado仿真(无testbench) 实现步骤 新建一个工程并添加自己编写的Verilog文件 添加后vivado会自动识别文件中的module 创建block design文件,添加模块 添加前 ...