SpringCloud源码阅读1-EurekaServer源码的秘密(上)

简介: SpringCloud源码阅读1-EurekaServer源码的秘密(上)

题外话:


Spring Cloud Netflix 作为springcloud 我们常用的一个项目,其子项目Eureka,zuul,Rebbion是我熟悉的。但是Spring Cloud Netflix 被宣布进入了维护模式, 意思不再添加新特性了,这对于我们来说很不友好了。 大家纷纷寻找相应的替代工具。(具体可以网上搜索)

但这不影响我们学习一些组件的框架思想。我对注册发现,负载均衡这块比较感兴趣。所以在此记录下自己的阅读心得。

版本说明:Finchley.SR1


1.组件的配置:


1.1 启用Eureka注册中心

当我们在springboot的启动类上加上@EnableEurekaServer,一个基本的注册中心就可以生效了。

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
}
}

@EnableEurekaServer仅仅是引入EurekaServerMarkerConfiguration类。 Marker的英文意思是标记的意思,spring相关框架中有很多类似xxxMarkerxxx这样的注解.其实他们的意思就是一个开关。会在其他地方进行开关的判断,有对应xxxMarkerxxx类就表示打开,没有表示关闭。

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

EurekaServerMarkerConfiguration开关打开的是哪个类呢??

org.springframework.cloud.netflix.eureka.server项目spring.factories资源文件中自动注入类EurekaServerAutoConfiguration,此类在自动注入的过程中,会判断开关是否打开来决定是否自动注入相关类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
    InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
  .....
}

由此看出EurekaServerMarkerConfiguration开关打开的EurekaServerAutoConfiguration


1.2 组件的配置。

下面我们看看EurekaServerAutoConfiguration配置了什么东西。 (1.先看注解上相关配置

@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
    InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
  ...
}
  • 引入EurekaServerInitializerConfiguration类,此类继承了SmartLifecycle接口,所以会在spring启动完毕时回调此类的start()方法
  • EurekaDashboardProperties 表示Euerka面板相关配置属性。例如:是否打开面板;面板的访问路径
  • InstanceRegistryProperties 表示实例注册相关配置属性。例如:每分钟最大的续约数量,默认打开的通信数量 等
  • 加载/eureka/server.properties的配置属性。

(2.再看类内部相关配置(代码比较长,这里只讲内容,建议打开源码看)寻找类中的Bean

  • HasFeatures 注册HasFeatures表示Eureka特征,
  • EurekaServerConfigBean配置类,表示EurekaServer的配置信息。通过@ConfigurationProperties(“eureka.server”)映射我们的配置文件中的eureka.server.xxxx格式的配置信息(此类很重要啊,我们想修改EurekaServer的配置信息,可以配置eureka.server.xxxx覆盖此类中的默认配置)
  • EurekaController: 面板的访问配置默认是“/”
  • 注册编码器(ServerCodecs)CloudServerCodecs
  • PeerAwareInstanceRegistry:对等节点同步器。 多个节点下复制相关。 与注册中心高可用有关的组件。此处注册的是 InstanceRegistry(注意PeerAwareInstanceRegistry实现了AbstractInstanceRegistry,这里准确的说是 对等节点+当前节点同步器
  • PeerEurekaNodes: Eureka-Server 集群节点的集合。存储了集群下各个节点信息。也是与高可用有关。
  • EurekaServerContext : 上下文。默认注册的DefaultEurekaServerContext
  • EurekaServerBootstrap: EurekaServer启动器。EurekaServerBootstrap
  • FilterRegistrationBean: 注册 Jersey filter过滤器。这里有必要讲一下。Eureka也是servlet应用。不过他是通过Jersey 框架来提供接口的。Jersey 框架是一个类Springmvc的web框架。我们项目中大多都是使用springmvc来处理。所以注册 Jersey filter过滤器,把/eureka开头的请求都交给Jersey 框架去解析。容器是com.sun.jersey.spi.container.servlet.ServletContainer
  • ApplicationResource: 暴漏com.netflix.discovery","com.netflix.eureka"包路径下的接口。通常我们再springmvc中通过Controller概念来表示接口,Jersey框架下用ApplicationResource的概念来表示接口。暴露的接口其实就是eureka各个应用通信的接口。(下面再说这些接口)

EurekaServerAutoConfiguration基本上就做了这些工作。我们来归类总结下

针对当前Eureka实例的相关组件:

  • EurekaDashboardProperties:面板属性
  • EurekaController: 面板的访问的处理器。
  • InstanceRegistryProperties:实例注册相关属性
  • (EurekaServerConfig)EurekaServerConfigBean:当前ErekekaServer相关配置
  • EurekaServerContext : 当前Eureka 注册中心上下文
  • 请求相关组件:注册/eureka路径的相关接口,注册拦截/eureka的拦截器,注册com.sun.jersey.spi.container.servlet.ServletContainer容器来处理对应的请求

两个针对集群下相关组件:

  • PeerAwareInstanceRegistry:用于集群下的节点相关复制信息用
  • PeerEurekaNodes:集群下的所有节点信息

两个针对启动相关类:

  • EurekaServerInitializerConfiguration: 对接spring,再spring启动完成后,调用
  • EurekaServerBootstrap:启动器,用于启动当前Eureak实例的上下文

至此:我们也可以大致了解了一个EurekaServer大致长什么样子了。



2.EurekaServerContext初始化:


EurekaServerContext作为上下文,应该是核心所在。上文讲过注册DefaultEurekaServerContext。此类中有@Inject,@PostConstruct, @PreDestroy注解的方法,重点来看看。

@Inject
public DefaultEurekaServerContext(EurekaServerConfig serverConfig,
                               ServerCodecs serverCodecs,
                               PeerAwareInstanceRegistry registry,
                               PeerEurekaNodes peerEurekaNodes,
                               ApplicationInfoManager applicationInfoManager) {
        this.serverConfig = serverConfig;
        this.serverCodecs = serverCodecs;
        this.registry = registry;
        this.peerEurekaNodes = peerEurekaNodes;
        this.applicationInfoManager = applicationInfoManager;
}


2.1 @Inject注解的构造方法

@Inject注解的方法,参数由IOC容器注入。serverConfig ,serverCodecs ,registry ,peerEurekaNodes我们已经认识了。ApplicationInfoManager 是用来管理应用信息的,也就是实例注册信息,由ApplicationInfoManager统一管理。


2.2 @PostConstruct注解的initialize()方法

@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次,被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行.

@PostConstruct
@Override
public void initialize() {
        logger.info("Initializing ...");
        peerEurekaNodes.start();
        try {
            registry.init(peerEurekaNodes);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        logger.info("Initialized");
}

这个方法很简明,主要有两个重要的的点:

  • peerEurekaNodes.start();
  • registry.init(peerEurekaNodes);


2.2.1 peerEurekaNodes.start()

PeerEurekaNodes: 用于管理PeerEurekaNode节点集合。 peerEurekaNodes.start();

public void start() {
    //创建一个单线程定时任务线程池:线程的名称叫做Eureka-PeerNodesUpdater
        taskExecutor = Executors.newSingleThreadScheduledExecutor(
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                        thread.setDaemon(true);
                        return thread;
                    }
                }
        );
        try {
          // 解析Eureka Server URL,并更新PeerEurekaNodes列表
            updatePeerEurekaNodes(resolvePeerUrls());
            //创建任务
            //任务内容为:解析Eureka Server URL,并更新PeerEurekaNodes列表
            Runnable peersUpdateTask = new Runnable() {
                @Override
                public void run() {
                    try {
                        updatePeerEurekaNodes(resolvePeerUrls());
                    } catch (Throwable e) {
                        logger.error("Cannot update the replica Nodes", e);
                    }
                }
            };
            //交给线程池执行,执行间隔10min
            taskExecutor.scheduleWithFixedDelay(
                    peersUpdateTask,
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                    TimeUnit.MILLISECONDS
            );
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        for (PeerEurekaNode node : peerEurekaNodes) {
            logger.info("Replica node URL:  {}", node.getServiceUrl());
        }
}


resolvePeerUrls():

解析配置的对等体URL。就是在配置文件中配置的多个Eureka注册中心的URL.


updatePeerEurekaNodes:
protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
        //计算需要移除的url= 原来-新配置。
        Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
        toShutdown.removeAll(newPeerUrls);
        //计算需要增加的url= 新配置-原来的。
        Set<String> toAdd = new HashSet<>(newPeerUrls);
        toAdd.removeAll(peerEurekaNodeUrls);
        //没有变化就不更新
        if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
            return;
        }
        List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);
         // 删除需要移除url对应的节点。
        if (!toShutdown.isEmpty()) {
            int i = 0;
            while (i < newNodeList.size()) {
                PeerEurekaNode eurekaNode = newNodeList.get(i);
                if (toShutdown.contains(eurekaNode.getServiceUrl())) {
                    newNodeList.remove(i);
                    eurekaNode.shutDown();
                } else {
                    i++;
                }
            }
        }
        // 添加需要增加的url对应的节点
        if (!toAdd.isEmpty()) {
            logger.info("Adding new peer nodes {}", toAdd);
            for (String peerUrl : toAdd) {
                newNodeList.add(createPeerEurekaNode(peerUrl));
            }
        }
        //更新节点列表
        this.peerEurekaNodes = newNodeList;
        //更新节点url列表
        this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
    }

总结:start()方法,其实就是完成新配置的eureka集群信息的初始化更新工作。


2.2.2 registry.init(peerEurekaNodes)

对等节点同步器的初始化。

public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
        //统计最近X秒内的来自对等节点复制的续约数量(默认1秒)
        this.numberOfReplicationsLastMin.start();
        this.peerEurekaNodes = peerEurekaNodes;
        //初始化返回结果缓存
        initializedResponseCache();
        //更新续约阀值
        scheduleRenewalThresholdUpdateTask();
        //初始化远程区域注册 相关信息
        initRemoteRegionRegistry();
        ...
}


numberOfReplicationsLastMin.start():

启动一个定时任务,任务名称为Eureka-MeasureRateTimer,每1秒统计从对等节点复制的续约数,将当前的桶的统计数据放到lastBucket,当前桶置为0

this.numberOfReplicationsLastMin = new MeasuredRate(1000 * 60 * 1);
--
this.timer = new Timer("Eureka-MeasureRateTimer", true);
---
timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    try {
                        // Zero out the current bucket.
                        lastBucket.set(currentBucket.getAndSet(0));
                    } catch (Throwable e) {
                        logger.error("Cannot reset the Measured Rate", e);
                    }
                }
}, sampleInterval, sampleInterval);

注意:此统计器用于节点之间复制的统计。


相关文章
|
20天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
31 0
|
23天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
1天前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
13 6
|
2天前
|
Java 应用服务中间件 测试技术
深入探索Spring Boot Web应用源码及实战应用
【5月更文挑战第11天】本文将详细解析Spring Boot Web应用的源码架构,并通过一个实际案例,展示如何构建一个基于Spring Boot的Web应用。本文旨在帮助读者更好地理解Spring Boot的内部工作机制,以及如何利用这些机制优化自己的Web应用开发。
14 3
|
5天前
|
存储 前端开发 Java
Spring Boot自动装配的源码学习
【4月更文挑战第8天】Spring Boot自动装配是其核心机制之一,其设计目标是在应用程序启动时,自动配置所需的各种组件,使得应用程序的开发和部署变得更加简单和高效。下面是关于Spring Boot自动装配的源码学习知识点及实战。
13 1
|
6天前
|
传感器 人工智能 前端开发
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
智慧校园电子班牌,坐落于班级的门口,适合于各类型学校的场景应用,班级学校日常内容更新可由班级自行管理,也可由学校统一管理。让我们一起看看,电子班牌有哪些功能呢?
47 4
JAVA语言VUE2+Spring boot+MySQL开发的智慧校园系统源码(电子班牌可人脸识别)Saas 模式
|
14天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
18天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
19天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
24天前
|
Java Maven Nacos
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
31 0