Spring Cloud系列(三):Eureka源码解析之服务端
一、自动装配
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源码解析之服务端的更多相关文章
- Netty 4源码解析:服务端启动
Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...
- ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析
ThreadPoolExecutor 源码解析 本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.htm ...
- Spring Cloud系列(四):Eureka源码解析之客户端
一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...
- Eureka源码解析系列文章汇总
先看一张图 0 这个图是Eureka官方提供的架构图,整张图基本上把整个Eureka的核心功能给列出来了,当你要阅读Eureka的源码时可以参考着这个图和下方这些文章 EurekaServer Eur ...
- Mybatis 系列6-结合源码解析节点配置:objectFactory、databaseIdProvider、plugins、mappers
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
- 死磕 java同步系列之Phaser源码解析
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
- 死磕 java同步系列之StampedLock源码解析
问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...
- 死磕 java同步系列之ReentrantReadWriteLock源码解析
问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...
随机推荐
- 01.图文理解RDB和AOF两种持久化机制
一.RDB和AOF两种持久化机制的介绍 RDB:对redis中的数据执行周期性的持久化,每隔一个时刻生成一个RDB文件,这个RDB文件包含这个时刻所有的数据. AOF:记录每条写入命令,以append ...
- C++标准模板库(STL)常用介绍
1. 输入输出 C++既可以用C的scanf和printf,也可以用新增的的cin与cout,后者速度慢 1.1 C程序中输入输出 int a; scanf("%d",&a ...
- Istio安全-授权(实操三)
Istio安全-授权 目录 Istio安全-授权 授权HTTP流量 为使用HTTP流量的负载配置访问控制 卸载 授权TCP流量 部署 配置TCP负载的访问控制 卸载 使用JWT进行授权 部署 使用有效 ...
- 万字长文,一篇文章带你入门Python
注释 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这三类人,我给大家提供 ...
- Labview学习之路(十三)数组函数
本文章介绍一下函数选版中的数组函数,一眼看懂没什么特殊地方的就不说了 (一)数组大小 返回I32类型的数或者数组. 什么是I32类型?就是32位有符号整型, 有符号什么意思?就是在32位数据中最高做符 ...
- [U3D + GAD]Egametang开源服务器框架资源管理系统
Egametang开源服务器框架资源管理系统详解 http://m.gad.qq.com/article/detail/36409 ET GitHub https://github.com/egame ...
- java返回树形结构的正确姿势
业务场景 通常我们前端需要一个树形的导航菜单或者分类菜单,如后台权限管理中的权限树,亦或者下面例子中商城系统的商品分类多级菜单(一般为三级菜单) 数据库设计 数据库设计,采用parentId来指向自己 ...
- 手写mybatis框架
前言 很久没有更新mybatis的源码解析了,因为最近在将自己所理解的mybatis思想转为实践. 在学习mybatis的源码过程中,根据mybatis的思想自己构建了一个ORM框架 .整个代码都是自 ...
- Mybatis实例增删改查(二)
创建实体类: package com.test.mybatis.bean; public class Employee { private Integer id; private String las ...
- H5简单内容
1.简单认识H5 HTML5不仅仅是作为HTML标记语言的一个最新版本,更重要的是它指定了Web开发的一系列标准,成为第一个将Web作为应用开发平台的HTML语言. 我们日常讨论的H5其实是有一个泛称 ...