服务发现-EurekaServer的初始化和启动原理

刚学习 SpringCloud 的时候先要学习注册中心,也就是服务发现与治理。SpringCloudNetflix 的方案是使用 Eureka,咱也都很清楚了,下面咱先搭建一个只有 EurekaServer 的工程。

pom依赖只需要两个:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
复制代码

启动类上标注 @EnableEurekaServer

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

application.yml 中配置一些最基础的信息:

server:
  port: 9000

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: eureka-server
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:9000/eureka/
复制代码

之后运行主启动类,EurekaServer 便会运行在9000端口上。

如果不标注 @EnableEurekaServer 注解,即便导入依赖也不会启动 EurekaServer,说明真正打开 EurekaServer 的是 @EnableEurekaServer 注解。

1. @EnableEurekaServer

@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
复制代码

它的文档注释非常简单:

Annotation to activate Eureka Server related configuration.

用于激活 EurekaServer 相关配置的注解。

它被标注了一个 @Import 注解,导入的是一个 EurekaServerMarkerConfiguration 的配置类。

如果小伙伴对 @Import 注解还不是很了解,可以移步我的 《 SpringBoot源码解读与原理分析 》小册,先了解 SpringFramework 的基础。

2. EurekaServerMarkerConfiguration

@Configuration
public class EurekaServerMarkerConfiguration {

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

	class Marker {

	}
}
复制代码

这段源码看上去莫名其妙的,它是一个配置类,然后它定义了一个 Marker 的内部类,又注册了一个Bean,但这光秃秃的,也没点别的逻辑,它到底想干啥?果然还是得靠文档注释:

Responsible for adding in a marker bean to activate EurekaServerAutoConfiguration.

负责添加标记Bean来激活 EurekaServerAutoConfiguration

好吧,原来它的作用是 给IOC容器中添加一个标记,代表要启用 EurekaServerAutoConfiguration 的自动配置类

那咱就移步 EurekaServerAutoConfiguration 来看它的定义了。

3. EurekaServerAutoConfiguration

看到 AutoConfiguration 结尾的类,咱马上要想到:这个类肯定在 spring.factories 文件标注好了,不然没法生效。

果然,在 spring-cloud-netflix-eureka-server 的 jar 包中发现了一个 spring.factories 文件,而文件内部的声明就是如此的简单:

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
复制代码

注意看 @ConditionalOnBean 的条件:必须IOC容器中有一个 EurekaServerMarkerConfiguration.Marker 类型的 Bean,该配置类才会生效!(原来它是这样做自动配置开关的)

注意到它继承了 WebMvcConfigurerAdapter ,但全篇没有找到跟 WebMvcConfigurer 相关的部分,也没重写对应的方法。那它这是几个意思?这个时候咱要了解一个小背景:

在 SpringFramework5.0+ 后,因为接口可以直接声明 default 方法,所以 WebMvcConfigurerAdapter 被废弃(被标注 @Deprecated ),替代方案是直接实现 WebMvcConfigurer 接口。

那既然是这样, 它还继承着这个适配器类,那咱可以大概猜测:它应该是旧版本的遗留。

回到正题,咱看 EurekaServerAutoConfiguration 的类定义声明上还有什么值得注意的。除了上面说的,那就只剩下一个了:它导入了一个 EurekaServerInitializerConfiguration

4. EurekaServerInitializerConfiguration

@Configuration
public class EurekaServerInitializerConfiguration implements ServletContextAware, SmartLifecycle, Ordered
复制代码

注意它实现了 SmartLifecycle 接口,之前咱在《SpringBoot源码解读与原理分析》原理小册中提到过(第16篇 12.2.2章节),如果小伙伴们对这部分不了解,可以移步我的 《 SpringBoot源码解读与原理分析 》小册,这里咱直接说,它的核心方法是 start

public void start() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // TODO: is this class even needed now?
                // 初始化、启动 EurekaServer
                eurekaServerBootstrap.contextInitialized(
                        EurekaServerInitializerConfiguration.this.servletContext);
                log.info("Started Eureka Server");

                // 发布Eureka已注册的事件
                publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                // 修改 EurekaServer 的运行状态
                EurekaServerInitializerConfiguration.this.running = true;
                // 发布Eureka已启动的事件
                publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
            } // catch ......
        }
    }).start();
}

复制代码

(至此应该进一步意识到为什么上面 EurekaServerAutoConfiguration 继承了一个过时的类, Runnable 都没换成 Lambda 表达式。。。当然也跟 Eureka 1.x 不继续更新有关吧)

这个 start 方法只干了一件事,起一个新的线程来启动 EurekaServer 。这里面核心的 run 方法执行了这么几件事,都已经标注在源码中了。

这里面最重要的步骤就是第一步: 初始化、启动 EurekaServer

在继续展开这部分源码之前,要带小伙伴了解一点前置知识。

EurekaServer 本身应该是一个完整的 Servlet 应用,在原生的 EurekaServer 中, EurekaServerBootstrap 这个类会实现 ServletContextListener 接口(Servlet3.0规范)来引导启动 EurekaServer 。SpringBoot 应用一般使用嵌入式 Web 容器,没有所谓 Servlet3.0 规范作用的机会了,所以需要另外的启动方式,于是 SpringCloud 在整合这部分时,借助了IOC容器中支持的 LifeCycle 机制,来以此触发 EurekaServer 的启动。

4.0 eurekaServerBootstrap.contextInitialized:初始化、启动EurekaServer

public void contextInitialized(ServletContext context) {
    try {
        initEurekaEnvironment();
        initEurekaServerContext();
        context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
    } // catch......
}

复制代码

这里面又分为两个部分,依此来看:

4.1 initEurekaEnvironment:初始化Eureka的运行环境

private static final String TEST = "test";
private static final String DEFAULT = "default";

protected void initEurekaEnvironment() throws Exception {
    log.info("Setting the eureka configuration..");

    // Eureka的数据中心
    String dataCenter = ConfigurationManager.getConfigInstance()
            .getString(EUREKA_DATACENTER);
    if (dataCenter == null) {
        log.info(
                "Eureka data center value eureka.datacenter is not set, defaulting to default");
        ConfigurationManager.getConfigInstance()
                .setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
    }
    else {
        ConfigurationManager.getConfigInstance()
                .setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
    }
    // Eureka运行环境
    String environment = ConfigurationManager.getConfigInstance()
            .getString(EUREKA_ENVIRONMENT);
    if (environment == null) {
        ConfigurationManager.getConfigInstance()
                .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
        log.info(
                "Eureka environment value eureka.environment is not set, defaulting to test");
    }
    else {
        ConfigurationManager.getConfigInstance()
                .setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment);
    }
}

复制代码

这里面的逻辑咱乍一看,貌似都长得差不多啊,都是 获取 → 判断 → 设置 ,而且它们都有对应的默认值(源码中已标注)。至于这部分是干嘛的呢,咱不得不关注一下 setProperty 方法中的两个常量:

private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";
private static final String ARCHAIUS_DEPLOYMENT_DATACENTER = "archaius.deployment.datacenter";

复制代码

配置项的前缀是 archaius ,它是 Netflix 旗下的一个配置管理组件(提到这里,是不是产生了一种感觉:它会不会跟 SpringCloudConfig 有关系?然而并不是,当引入 SpringCloudConfig 时,archaius 并不会带进来),这个组件可以实现更强大的动态配置,它的基底是 Apachecommons-configuration

对于这个组件,小册不展开研究了,小伙伴们只需要知道有这么回事就可以了,下面的才是重点。

4.2 initEurekaServerContext:初始化EurekaServer的运行上下文

protected void initEurekaServerContext() throws Exception {
    // For backward compatibility  兼容低版本Eureka
    JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
            XStream.PRIORITY_VERY_HIGH);
    XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
            XStream.PRIORITY_VERY_HIGH);

    if (isAws(this.applicationInfoManager.getInfo())) {
        this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
                this.eurekaClientConfig, this.registry, this.applicationInfoManager);
        this.awsBinder.start();
    }

    // 注册EurekaServerContextHolder,通过它可以很方便的获取EurekaServerContext
    EurekaServerContextHolder.initialize(this.serverContext);

    log.info("Initialized server context");

    // Copy registry from neighboring eureka node
    // Eureka复制集群节点注册表
    int registryCount = this.registry.syncUp();
    this.registry.openForTraffic(this.applicationInfoManager, registryCount);

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

复制代码

前面的一大段都是为了低版本兼容而做的一些额外工作,咱不关心这些。中间又是注册了一个 注册 EurekaServerContextHolder 的组件,通过它可以直接获取 EurekaServerContext (它的内部使用简单的单例实现,实现非常简单,小伙伴可自行查看)。

注意最后几行,倒数第二个单行注释的内容:

Copy registry from neighboring eureka node。

从相邻的eureka节点复制注册表。

节点复制注册表?这很明显是为了 Eureka 集群而设计的!由此可知 Eureka 集群能保证后起来的节点也不会出问题,是这里同步了注册表啊!这一步的操作非常复杂,咱后续另开一篇解释。

除了这部分之外, EurekaServerInitializerConfiguration 已经没有要配置的组件,回到 EurekaServerAutoConfiguration 中。

5. EurekaServerAutoConfiguration中配置的核心组件

5.1 EurekaController

@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
    return new EurekaController(this.applicationInfoManager);
}

复制代码

呦,一看这是个 Controller ,有木有立马想到自己写的那些 Controller ?赶紧点进去瞅一眼:

@Controller
@RequestMapping("${eureka.dashboard.path:/}")
public class EurekaController

复制代码

哇塞果然是我们熟悉的 SpringWebMvc 的内容!既然是一个 Controller ,那它肯定能给咱定义了一些处理方法,不然咱咋看到的 Eureka 控制台呢?翻看源码,它这里面定义了两个处理方法,分别是: status - 获取当前 EurekaServer 的状态(即控制台)、 lastn - 获取当前 EurekaServer 上服务注册动态历史记录。这部分咱不展开描述了,有兴趣的小伙伴们可以深入这个类来研究。

5.2 PeerAwareInstanceRegistry

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

复制代码

这个 PeerAwareInstanceRegistry 很重要,它是 EurekaServer 集群中节点之间同步微服务实例注册表的核心组件 (这里默认小伙伴已经对 EurekaServer 的集群配置及相关基础都了解了)。集群节点同步注册表的内容咱会另起一篇研究,这里咱只是看一下这个类的继承结构,方面后续看到时不至于不认识:

public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry
public abstract class AbstractInstanceRegistry implements InstanceRegistry

复制代码

这里面继承的两个类 PeerAwareInstanceRegistryImplAbstractInstanceRegistry ,它们将会在后续研究节点同步时有重要作用,包括里面涉及的功能会在后面的组件( EurekaServerContext 等)发挥功能时带着一起解释。

5.3 PeerEurekaNodes

@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs,
        ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
    return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
            this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
            replicationClientAdditionalFilters);
}

复制代码

这个 PeerEurekaNodes 可以理解成 微服务实例的节点集合 。换言之,一个 PeerEurekaNode 就是一个微服务节点实例的包装, PeerEurekaNodes 就是这组 PeerEurekaNode 的集合,这种节点是可以被 EurekaServer 集群中的各个注册中心节点共享的( PeerAwareInstanceRegistry )。翻开 PeerEurekaNodes 的结构,可以发现它的结构中有这么几样东西:

public class PeerEurekaNodes {

    protected final PeerAwareInstanceRegistry registry;
    // ......

    private volatile List<PeerEurekaNode> peerEurekaNodes = Collections.emptyList();
    private volatile Set<String> peerEurekaNodeUrls = Collections.emptySet();

    private ScheduledExecutorService taskExecutor;

复制代码
PeerAwareInstanceRegistry
List<PeerEurekaNode>
peerEurekaNodeUrls
ScheduledExecutorService

另外 PeerEurekaNodes 还提供了一个 startshutdown 方法:

5.3.1 start

public void start() {
    taskExecutor = Executors.newSingleThreadScheduledExecutor(
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
                    thread.setDaemon(true);
                    return thread;
                }
            }
    );
    try {
        updatePeerEurekaNodes(resolvePeerUrls());
        Runnable peersUpdateTask = new Runnable() {
            @Override
            public void run() {
                try {
                    updatePeerEurekaNodes(resolvePeerUrls());
                } // catch ......
            }
        };
        taskExecutor.scheduleWithFixedDelay(
                peersUpdateTask,
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
                TimeUnit.MILLISECONDS
        );
    } // catch ...... log ......
}

复制代码

可以发现 start 方法的核心是 借助线程池完成定时任务 。定时任务的内容是中间那一段实现了 Runnable 接口的匿名内部类,它会执行一个 updatePeerEurekaNodes 方法来更新集群节点。下面定时任务的执行时间,借助IDEA跳转到 EurekaServerConfigBean 中发现默认的配置是 10 分钟,即 每隔10分钟会同步一次集群节点 。至于 updatePeerEurekaNodes 的具体实现,咱同样放到后面跟节点同步放在一起来解析。

5.3.2 shutdown

public void shutdown() {
    taskExecutor.shutdown();
    List<PeerEurekaNode> toRemove = this.peerEurekaNodes;

    this.peerEurekaNodes = Collections.emptyList();
    this.peerEurekaNodeUrls = Collections.emptySet();

    for (PeerEurekaNode node : toRemove) {
        node.shutDown();
    }
}

复制代码

这个方法的内容比较简单,它会把线程池的定时任务停掉,并移除掉当前所有的服务节点信息。它被调用的时机是下面要解析的 EurekaServerContext

5.4 EurekaServerContext

@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
        PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
    return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
            registry, peerEurekaNodes, this.applicationInfoManager);
}

复制代码

它创建了一个 DefaultEurekaServerContext ,文档注释原文翻译:

Represent the local server context and exposes getters to components of the local server such as the registry.

表示本地服务器上下文,并将 getter 方法暴露给本地服务器的组件(例如注册表)。

可以大概的意识到,它确实跟 SpringFramework 的 ApplicationContext 差不太多哈,可以这么简单地理解吧,咱还是看看里面比较特殊的内容。

进入到 DefaultEurekaServerContext 中,果然发现了两个特殊的方法:

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

@PreDestroy
public void shutdown() {
    logger.info("Shutting down ...");
    registry.shutdown();
    peerEurekaNodes.shutdown();
    logger.info("Shut down");
}

复制代码

果然,是 EurekaServerContext 的初始化,带动 PeerEurekaNodes 的初始化, EurekaServerContext 的销毁带动 PeerEurekaNodes 的销毁。除了带动 PeerEurekaNodes 之前,还有一个 PeerAwareInstanceRegistry 也带动初始化了,看一眼它的 init 方法吧:

5.4.1 PeerAwareInstanceRegistry#init

关键部分注释已标注在源码:

public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
    // 5.4.1.1 启动续订租约的频率统计器
    this.numberOfReplicationsLastMin.start();
    this.peerEurekaNodes = peerEurekaNodes;
    initializedResponseCache();
    // 5.4.1.2 开启续订租约最低阈值检查的定时任务
    scheduleRenewalThresholdUpdateTask();
    // 5.4.1.3 初始化远程分区注册中心
    initRemoteRegionRegistry();

    try {
        Monitors.registerObject(this);
    } catch (Throwable e) {
        logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
    }
}

复制代码

源码标注了三个关键的环节,一一来看:

5.4.1.1 numberOfReplicationsLastMin.start():启动续订租约的频率统计器

private final AtomicLong lastBucket = new AtomicLong(0);
private final AtomicLong currentBucket = new AtomicLong(0);

private final long sampleInterval;

public synchronized void start() {
    if (!isActive) {
        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);
        isActive = true;
    }
}

复制代码

这个方法实现不难理解,它会隔一段时间重置 lastBucketcurrentBucket 的值为0,那时间间隔是多少呢?翻看整个类,发现只有构造方法可以设置时间间隔:

public MeasuredRate(long sampleInterval) {
    this.sampleInterval = sampleInterval;
    this.timer = new Timer("Eureka-MeasureRateTimer", true);
    this.isActive = false;
}

复制代码

借助IDEA,发现设置 sampleInterval 的值有两处,但值都是一样的: new MeasuredRate(1000 * 60 * 1); ,也就是 1分钟重置一次 。可关键的问题是,它这个操作是干嘛呢?为啥非得一分钟统计一次续约次数呢?实际上,这个计算次数会体现在 Eureka 的控制台,以及配合 Servo 完成 续约次数监控 (说白了,咱这看着没啥用,微服务监控和治理还是管用的,不然为什么 Eureka 被称为 服务发现与治理 的框架呢)。

5.4.1.2 scheduleRenewalThresholdUpdateTask:开启续订租约最低阈值检查的定时任务

private int renewalThresholdUpdateIntervalMs = 15 * MINUTES;

private void scheduleRenewalThresholdUpdateTask() {
    timer.schedule(new TimerTask() {
                       @Override
                       public void run() {
                           updateRenewalThreshold();
                       }
                   }, serverConfig.getRenewalThresholdUpdateIntervalMs(),
            serverConfig.getRenewalThresholdUpdateIntervalMs());
}

复制代码

又是一个定时任务,配置项中的默认时间间隔可以发现是15分钟。那定时任务中执行的核心方法是 updateRenewalThreshold 方法,跳转过去:

private void updateRenewalThreshold() {
    try {
        Applications apps = eurekaClient.getApplications();
        int count = 0;
        for (Application app : apps.getRegisteredApplications()) {
            for (InstanceInfo instance : app.getInstances()) {
                if (this.isRegisterable(instance)) {
                    ++count;
                }
            }
        }
        synchronized (lock) {
            // Update threshold only if the threshold is greater than the
            // current expected threshold or if self preservation is disabled.
            if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
                    || (!this.isSelfPreservationModeEnabled())) {
                this.expectedNumberOfClientsSendingRenews = count;
                updateRenewsPerMinThreshold();
            }
        }
        logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
    } // catch ......
}

复制代码

上面的 for 循环很明显是检查当前已经注册到本地的服务实例是否还保持连接,由于该方法一定会返回 true (可翻看该部分实现,全部都是 return true ),故上面统计的 count 就是所有的微服务实例数量。

下面的同步代码块中,它会检查统计好的数量是否比预期的多,如果统计好的服务实例数比预期的数量多,证明出现了 新的服务注册 ,要替换下一次统计的期望数量值,以及重新计算接下来心跳的数量统计。心跳的数量统计方法 updateRenewsPerMinThreshold()

private int expectedClientRenewalIntervalSeconds = 30;
private double renewalPercentThreshold = 0.85;

protected void updateRenewsPerMinThreshold() {
    this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
            * (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
            * serverConfig.getRenewalPercentThreshold());
}

复制代码

可以看出来它的计算数是: 每隔30秒发一次心跳 (一分钟心跳两次),而且必须所有的服务实例的心跳总数要达到前面计算数量的85%才算整体微服务正常,其实这也就是 EurekaServer 的自我保护机制

5.4.1.3 initRemoteRegionRegistry:初始化远程分区注册中心

protected void initRemoteRegionRegistry() throws MalformedURLException {
    Map<String, String> remoteRegionUrlsWithName = serverConfig.getRemoteRegionUrlsWithName();
    if (!remoteRegionUrlsWithName.isEmpty()) {
        allKnownRemoteRegions = new String[remoteRegionUrlsWithName.size()];
        int remoteRegionArrayIndex = 0;
        for (Map.Entry<String, String> remoteRegionUrlWithName : remoteRegionUrlsWithName.entrySet()) {
            RemoteRegionRegistry remoteRegionRegistry = new RemoteRegionRegistry(
                    serverConfig,
                    clientConfig,
                    serverCodecs,
                    remoteRegionUrlWithName.getKey(),
                    new URL(remoteRegionUrlWithName.getValue()));
            regionNameVSRemoteRegistry.put(remoteRegionUrlWithName.getKey(), remoteRegionRegistry);
            allKnownRemoteRegions[remoteRegionArrayIndex++] = remoteRegionUrlWithName.getKey();
        }
    }
    logger.info("Finished initializing remote region registries. All known remote regions: {}",
            (Object) allKnownRemoteRegions);
}

复制代码

这里面提到了一个概念: RemoteRegionRegistry ,它的文档注释原文翻译:

Handles all registry operations that needs to be done on a eureka service running in an other region. The primary operations include fetching registry information from remote region and fetching delta information on a periodic basis.

处理在其他区域中运行的eureka服务上需要完成的所有注册表操作。主要操作包括从远程区域中获取注册表信息以及定期获取增量信息。

文档注释的解释看着似懂非懂,它没有把这个类的作用完全解释清楚。实际上这里涉及到 Eureka 的服务分区,这个咱留到后面解释 Eureka 的高级特性时再聊。

5.4.2 PeerAwareInstanceRegistry#shutdown

EurekaServerContext 被销毁时,会回调 @PreDestory 标注的 shutdown 方法,而这个方法又调到 PeerAwareInstanceRegistryshutdown 方法。

public void shutdown() {
    try {
        DefaultMonitorRegistry.getInstance().unregister(Monitors.newObjectMonitor(this));
    } // catch .......
    try {
        peerEurekaNodes.shutdown();
    } // catch .......
    numberOfReplicationsLastMin.stop();
    super.shutdown();
}

复制代码

这里它干的事情不算麻烦,它首先利用 DefaultMonitorRegistry 做了一个注销操作, DefaultMonitorRegistry 这个组件本身来源于 servo 包,它是做监控使用,那自然能猜出来这部分是 关闭监控 。接下来它会把那些微服务节点实例全部注销,停止计数器监控,最后回调父类的 shutdown 方法:

public void shutdown() {
    deltaRetentionTimer.cancel();
    evictionTimer.cancel();
    renewsLastMin.stop();
}

复制代码

可以发现也是跟监控相关的组件停止,不再赘述。

5.5 EurekaServerBootstrap

@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
        EurekaServerContext serverContext) {
    return new EurekaServerBootstrap(this.applicationInfoManager,
            this.eurekaClientConfig, this.eurekaServerConfig, registry,
            serverContext);
}

复制代码

这个咱上面已经提过了,有了 EurekaServerBootstrap 才能引导启动 EurekaServer

5.6 ServletContainer

@Bean
public FilterRegistrationBean jerseyFilterRegistration(javax.ws.rs.core.Application eurekaJerseyApp) {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new ServletContainer(eurekaJerseyApp));
    bean.setOrder(Ordered.LOWEST_PRECEDENCE);
    bean.setUrlPatterns(Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
    return bean;
}

复制代码

它注册的 FilterRegistrationBean 我在之前的《 SpringBoot源码解读与原理分析 》中有提过(第6章4.1.2节),这里咱直接说核心的 FilterServletContainer

package com.sun.jersey.spi.container.servlet;

public class ServletContainer extends HttpServlet implements Filter

复制代码

注意它所在的包,里面有一个很关键的词: jersey ,它是一个类似于 SpringWebMvc 的框架,由于 Eureka 本身也是一个 Servlet 应用,只是它使用的 Web 层框架不是 SpringWebMvc 而是 Jersey 而已,Jersey 在 Eureka 的远程请求、心跳包发送等环节起到至关重要的作用,后续咱会详细解释。

5.7 Application

@Bean
public javax.ws.rs.core.Application jerseyApplication(Environment environment,
        ResourceLoader resourceLoader) {
    // ......
}

复制代码

这个类的创建咱不是很关心,瞅一眼这个类的子类,发现全部都是来自 Jersey 的:

而且上面的 ServletContainer 中正好也用到了这个 Application ,那大概也明白它是配合上面的过滤器使用,后续咱会跟上面的 Jersey 一起解释。

5.8 HttpTraceFilter

@Bean
public FilterRegistrationBean traceFilterRegistration(@Qualifier("httpTraceFilter") Filter filter) {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(filter);
    bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
    return bean;
}

复制代码

它注册了一个名为 httpTraceFilter 的过滤器,借助IDEA发现这个过滤器来自 HttpTraceAutoConfiguration 的内部类 ServletTraceFilterConfiguration

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
static class ServletTraceFilterConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public HttpTraceFilter httpTraceFilter(HttpTraceRepository repository, HttpExchangeTracer tracer) {
        return new HttpTraceFilter(repository, tracer);
    }
}

复制代码

这个过滤器的作用也很容易猜想, trace 的概念咱从日志系统里也接触过,它打印的内容非常非常多,且涵盖了上面的几乎所有级别。这个类的文档注释也恰好印证了我们的猜想:

Servlet Filter that logs all requests to an HttpTraceRepository.

记录所有请求日志的Servlet过滤器。

6. EurekaServerConfigBeanConfiguration

EurekaServerAutoConfiguration 还有一个内部的配置类: EurekaServerConfigBeanConfiguration

@Configuration
protected static class EurekaServerConfigBeanConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
        EurekaServerConfigBean server = new EurekaServerConfigBean();
        if (clientConfig.shouldRegisterWithEureka()) {
            // Set a sensible default if we are supposed to replicate
            server.setRegistrySyncRetries(5);
        }
        return server;
    }
}

复制代码

它就是注册了默认的 EurekaServer 的配置模型,这个模型类里的配置咱上面也看到一些了,后面的部分咱还会接触它,先有一个印象即可。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章