一、自动装配

  1、根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-server的spring.factories,查看spring.factories如下:

  2、进入EurekaServer的自动装配类EurekaServerAutoConfiguration:

  3、@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)也就是说当容器中有EurekaServerMarkerConfig uration.Marker.class时,该配置类才起作用。接下来看下启动类EurekaApplication,该启动类上不光有@SpringBootApplication自动装配,还有@EnableEurekaServer,开启EurekaServer。

二、EurekaServer

  1、@EnableEurekaServer注解开启EurekaServer

  1.1 点进去@EnableEurekaServer点进去,发现其@Import(EurekaServerMarkerConfiguration.class),导入了EurekaServer MarkerConfiguration配置类

  1.2 EurekaServerMarkerConfiguration配置类,该配置类导入了EurekaServerMarkerConfiguration.Marker.class。如下:

  2、EurekaServerAutoConfiguration配置类

  当容器中有EurekaServerMarkerConfiguration.Marker.class,就可以激活该配置类,接下来详细看下该配置类为我们做了什么。

  2.1 @Import(EurekaServerInitializerConfiguration.class)

  

  EurekaServerInitializerConfiguration配置类实现了SmartLifecycle,我们知道实现了SmartLifecycle接口的,会在Ioc容器中所有Bean初始化完成后,根据isAutoStartup()方法返回true来执行该配置类的start()

  ① 进入EurekaServerInitializerConfiguration.start()方法:

public void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//EurekaServerAutoConfiguration->@Bean EurekaServerBootstrap
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
//发布EurekaRegistryAvailableEvent事件
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
//设置运行状态为true
EurekaServerInitializerConfiguration.this.running = true;
//发布EurekaServerStartedEvent事件
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}
}).start();
}

  ② 进入EurekaServerBootstrap.contextInitialized(ServletContext context)方法:

public void contextInitialized(ServletContext context) {
try {
//初始化EurekaServer的运行环境
initEurekaEnvironment();
//初始化EurekaServer的上下文
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);
}
}

  ③ 进入EurekaServerBootstrap.initEurekaServerContext()方法:

protected void initEurekaServerContext() throws Exception {
//省略非核心代码
//从集群其他节点复制注册
int registryCount = this.registry.syncUp();
/**
* 1、修改状态为UP
* 2、调用父类的postInit 开启一个剔除定时任务,每隔60执行一次,从当前服务清单中把超时(默认90秒)没有续约剔
*/
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}

  ④ PeerAwareInstanceRegistryImpl.syncUp()方法:

  该方法中的eurekaClient.getApplications()是通过http调用,获取集群中的其他节点的所有服务实例。然后遍历获取到的apps,根据isRegisterable(instance)判断是否可注册,如果可以注册就调用register(instance, instance.getLeaseInfo().getDurationInSecs(), true)进行注册,注册实质就是往AbstractInstanceRegistry的属性private final ConcurrentHashMap<String, Map<String, Lease <InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();中加入服务实例信息。

  ⑤ PeerAwareInstanceRegistryImpl.openForTraffic()方法:

  该方法核心第一步:applicationInfoManager.setInstanceStatus(InstanceStatus.UP)修改状态为UP,第二步:调用super.postInit();开启一个剔除定时任务,每隔60执行一次,从当前服务清单中把超时(默认90秒)没有续约剔除。

  ⑥ 进入AbstractInstanceRegistry.postInit()方法:

protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
//设置剔除任务EvictionTask
evictionTaskRef.set(new AbstractInstanceRegistry.EvictionTask());
//每隔60s执行一次EvictionTask的run方法
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}

  ⑦ EvictionTask的run方法:

  执行AbstractInstanceRegistry.evict(),剔除逻辑:主要的功能是将注册表registry,其实就是一个ConcurrentHashMap的所有注册实例遍历下,看哪些是过期的,过期了就加入到expiredLeases中,然后遍历expiredLeases,执行internalCancel方法把实例状态修改成DELETED状态,这样客户端就拿不到。

  2.2 导入了一些Bean

  ① EurekaServerConfig:初始化eurekaServer配置;

  ② EurekaController:初始化dashboard的相关接口,用户获取eurekaServer的相关信息;

  ③ PeerAwareInstanceRegistry:初始化集群注册表;

  ④ PeerEurekaNodes:初始化集群节点;

  ⑤ EurekaServerContext:基于eurekaServer配置,注册表,集群节点,以及服务实例初始化eurekaServer上下文;

  ⑥ EurekaServerBootstrap:初始化eureka启动类;

  ⑦ FilterRegistrationBean:往Filter注册表里面注册一个Jsrsey过滤器;

  其中EurekaServerContext的默认实现DefaultEurekaServerContext在初始化的时候会调用initialize()方法,流程图如下:

  3、Eureka的Jersey服务

  3.1 服务注册接口

  ApplicationResource.addInstance()方法,核心逻辑就是调用PeerAwareInstanceRegistryImpl.register(final InstanceInfo info, final boolean isReplication)方法如下:

public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
//往注册表中注册实例信息,然后执行invalidateCache(),把读写缓存readWriteCacheMap失效掉
super.register(info, leaseDuration, isReplication);
//复制到集群中的其他节 发起http调用,调用集群中的其他节点的注册服务实例接口
replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

  3.2 获取全量实例信息接口

  ApplicationsResource.getContainers()方法如下:

public Response getContainers(@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo,
@Nullable @QueryParam("regions") String regionsStr) { //省略......
//1、构建缓存key
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
Response response;
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
//省略......
} else {
//2、根据缓存key从缓存中获取,首先从只读缓存中取为null,再去读写缓存中取,然后设置到只读缓存中
response = Response.ok(responseCache.get(cacheKey))
.build();
}
return response;
}

  3.3 获取增量实例信息接口

  ApplicationsResource.getContainerDifferential()方法,逻辑同获取全量实例信息接口一样,不同在于构建缓存key的时候传入的ALL_APPS_DELTA,而获取全量实例信息接口传入的是ALL_APPS。

  3.4 心跳接口

  InstanceResource.renewLease()方法,核心逻辑就是调用PeerAwareInstanceRegistryImpl.renew(final String appName, final String id, final boolean isReplication)方法进行续约,其实就是设置实例的lastUpdateTimestamp为当前时间+duration

public void renew() {
//设置lastUpdateTimestamp为当前时间+duration
lastUpdateTimestamp = System.currentTimeMillis() + duration;
}

  3.5 服务下线接口

  InstanceResource.cancelLease()方法,核心就是调用PeerAwareInstanceRegistryImpl.cancel(final String appName, final String id,final boolean isReplication)方法进行服务下线,其实就是把实例的状态设置成DELETE,然后执行invalidateCache(),把读写缓存readWriteCacheMap失效掉

三、Eureka服务端流程图

  自此Eureka服务端源码解析完成,接下来将对Eureka客户端源码进行解析。Eureka应用详见:Spring Cloud系列(二):Eureka应用详解

Spring Cloud系列(三):Eureka源码解析之服务端的更多相关文章

  1. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  2. ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析

    ThreadPoolExecutor 源码解析 本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.htm ...

  3. Spring Cloud系列(四):Eureka源码解析之客户端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...

  4. Eureka源码解析系列文章汇总

    先看一张图 0 这个图是Eureka官方提供的架构图,整张图基本上把整个Eureka的核心功能给列出来了,当你要阅读Eureka的源码时可以参考着这个图和下方这些文章 EurekaServer Eur ...

  5. Mybatis 系列6-结合源码解析节点配置:objectFactory、databaseIdProvider、plugins、mappers

    [Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...

  6. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  7. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  8. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  9. 死磕 java同步系列之ReentrantReadWriteLock源码解析

    问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...

随机推荐

  1. [PyTorch 学习笔记] 3.2 卷积层

    本章代码:https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson3/nn_layers_convolution.py 这篇文 ...

  2. 使用 C# 捕获进程输出

    使用 C# 捕获进程输出 Intro 很多时候我们可能会需要执行一段命令获取一个输出,遇到的比较典型的就是之前我们需要用 FFMpeg 实现视频的编码压缩水印等一系列操作,当时使用的是 FFMpegC ...

  3. 免费领CRMEB移动社交电商系统源码与授权

    移动电商风起云涌,直播带货重塑销售模式,传统商业更是举步维艰,各行各业转型移动电商迫在眉睫,拥有一款好的移动社群社交电商系统成为众多企业与商家的心病! 你曾是否被那些劣质的移动电商系统搞得心力憔悴? ...

  4. Android开发之下载服务器上的一张图片到本地java代码实现HttpURLConnection

    package com.david.HttpURLConnectionDemo; import java.io.FileOutputStream; import java.io.IOException ...

  5. Mac 的命令行配置字体颜色

    1.在mac命令行终端输入: ls -al  查看所有隐藏文件,找到.bash_profile vi .bash_profile  编辑文件,贴入以下内容并保存 source .bash_profil ...

  6. spring中bean初始化执行顺序

    常用的javabean的初始化方法为,构造方法,@PostConstruct,以及实现InitializingBean接口的afterPropertiesSet方法. note在构造方法执行时候,sp ...

  7. 08_线程间通信 ITC

    1.线程间的通信方式 1.共享全局变量 2.线程队列(queue) 3.信号量(Semaphore) 2.共享全局变量进行通信 from threading import Thread import ...

  8. JVM学习第三天(JVM的执行子系统)之类加载机制

    好几天没有学习了,前几天因为导出的事情,一直在忙,今天继续学习, 其实今天我也遇到了一个问题,如果有会的兄弟可以评论留给我谢谢; 问题:fastJSON中JSONObject.parseObject做 ...

  9. PHP的八个魔术常量

    1. 什么魔术常量 预定义常量:预定义常量就是PHP内置的常量,预先定义好的 PHP有很多预定义常量,比如:PHP_VERSION(版本号).PHP_OS(操作系统). 这些普通的预定义常量在程序中的 ...

  10. 设计模式也可以这么简单(7年开发老鸟PS注释总结)

    设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four (GoF) 的分类了,他们将设计模式分类为 23 种经典的模式,根据用途我们又可以分为三大类,分 ...