本文将简要分析一下关于 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. 如何在没有第三方.NET库源码的情况,调试第三库代码?

    大家好,我是沙漠尽头的狼. 本方首发于Dotnet9,介绍使用dnSpy调试第三方.NET库源码,行文目录: 安装dnSpy 编写示例程序 调试示例程序 调试.NET库原生方法 总结 1. 安装dnS ...

  2. 一种对数据库友好的GUID的变种使用方法

    概述 .NET生成的GUID唯一性很好,用之方便,但是,缺少像雪花算法那样的有序性.虽然分布式系统中做不到绝对的有序,但是,相对的有序对于目前数据库而言,索引效率等方面的提升还是有明显效果的(当然,我 ...

  3. C/C++基础——引用与指针有什么区别?C++中输入输出加速

    文章目录 1 引用与指针有什么区别? 2 C++中输入输出加速 tie sync_with_stdio 应用 1 引用与指针有什么区别? 指针和引用都是地址的概念,指针指向一块内存,它的内容是所指内存 ...

  4. How to Install Bugzilla on Ubuntu 20.04

    In this blog post, we are going to explain in step-by-step detail on how to install Bugzilla on Ubun ...

  5. 触发器引起的ADG备库同步错误

    数据库alert日志报错ORA-16000,查看对应的trc文件,大致如下报错: *** 2020-10-27 14:09:03.340*** SESSION ID:(3340.75) 2020-10 ...

  6. 【翻译】listener.ora

    今天仔细过一遍oracle的监听配置文件描述. cat $ORACLE_HOME/network/admin/samples/listener.ora # copyright (c) 1997 by ...

  7. vue之留言板

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. VK Cup 2016 - Round 1 (CF639)

    A. Bear and Displayed Friends Div2 的题,不写. B. Bear and Forgotten Tree 3 这种东西怎么评蓝的? Description 给定 \(n ...

  9. Python 利用pymysql和openpyxl操作MySQL数据库并插入Excel数据

    1. 需求分析 本文将介绍如何使用Python连接MySQL数据库,并从Excel文件中读取数据,将其插入到MySQL数据库中. 2. 环境准备 在开始本文之前,请确保您已经安装好了以下环境: Pyt ...

  10. Python:发送邮件或发带附件的邮件

    在进行发邮件之前,首先了解两个模块:smtplib和email. smtplib模块主要是负责发送邮件的,是一个发邮件的动作,比如连接邮箱服务,登录邮箱,发送邮件等. email模块主要是负责构造邮件 ...