刚学习 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
注解。
@Import(EurekaServerMarkerConfiguration.class) public @interface EnableEurekaServer { } 复制代码
它的文档注释非常简单:
Annotation to activate Eureka Server related configuration.
用于激活 EurekaServer 相关配置的注解。
它被标注了一个 @Import
注解,导入的是一个 EurekaServerMarkerConfiguration
的配置类。
如果小伙伴对 @Import 注解还不是很了解,可以移步我的 《 SpringBoot源码解读与原理分析 》小册,先了解 SpringFramework 的基础。
@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
来看它的定义了。
看到 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
。
@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 的启动。
public void contextInitialized(ServletContext context) { try { initEurekaEnvironment(); initEurekaServerContext(); context.setAttribute(EurekaServerContext.class.getName(), this.serverContext); } // catch...... } 复制代码
这里面又分为两个部分,依此来看:
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 并不会带进来),这个组件可以实现更强大的动态配置,它的基底是 Apache 的 commons-configuration
:
对于这个组件,小册不展开研究了,小伙伴们只需要知道有这么回事就可以了,下面的才是重点。
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
中。
@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 上服务注册动态历史记录。这部分咱不展开描述了,有兴趣的小伙伴们可以深入这个类来研究。
@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 复制代码
这里面继承的两个类 PeerAwareInstanceRegistryImpl
、 AbstractInstanceRegistry
,它们将会在后续研究节点同步时有重要作用,包括里面涉及的功能会在后面的组件( EurekaServerContext
等)发挥功能时带着一起解释。
@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
还提供了一个 start
和 shutdown
方法:
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
的具体实现,咱同样放到后面跟节点同步放在一起来解析。
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
。
@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
方法吧:
关键部分注释已标注在源码:
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); } } 复制代码
源码标注了三个关键的环节,一一来看:
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; } } 复制代码
这个方法实现不难理解,它会隔一段时间重置 lastBucket
和 currentBucket
的值为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 被称为 服务发现与治理 的框架呢)。
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 的自我保护机制 。
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 的高级特性时再聊。
当 EurekaServerContext
被销毁时,会回调 @PreDestory
标注的 shutdown
方法,而这个方法又调到 PeerAwareInstanceRegistry
的 shutdown
方法。
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(); } 复制代码
可以发现也是跟监控相关的组件停止,不再赘述。
@Bean public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, EurekaServerContext serverContext) { return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig, registry, serverContext); } 复制代码
这个咱上面已经提过了,有了 EurekaServerBootstrap
才能引导启动 EurekaServer
。
@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节),这里咱直接说核心的 Filter
是 ServletContainer
:
package com.sun.jersey.spi.container.servlet; public class ServletContainer extends HttpServlet implements Filter 复制代码
注意它所在的包,里面有一个很关键的词: jersey ,它是一个类似于 SpringWebMvc 的框架,由于 Eureka 本身也是一个 Servlet 应用,只是它使用的 Web 层框架不是 SpringWebMvc 而是 Jersey 而已,Jersey 在 Eureka 的远程请求、心跳包发送等环节起到至关重要的作用,后续咱会详细解释。
@Bean public javax.ws.rs.core.Application jerseyApplication(Environment environment, ResourceLoader resourceLoader) { // ...... } 复制代码
这个类的创建咱不是很关心,瞅一眼这个类的子类,发现全部都是来自 Jersey 的:
而且上面的 ServletContainer
中正好也用到了这个 Application
,那大概也明白它是配合上面的过滤器使用,后续咱会跟上面的 Jersey 一起解释。
@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过滤器。
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 的配置模型,这个模型类里的配置咱上面也看到一些了,后面的部分咱还会接触它,先有一个印象即可。