刚学习 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 的配置模型,这个模型类里的配置咱上面也看到一些了,后面的部分咱还会接触它,先有一个印象即可。