【SpringCloud】Eureka源码解析 上

e246a1dda09849a5a89535a62441565d.png

Eureka是一个服务发现与注册组件,它包含服务端和客户端,服务端管理服务的注册信息,客户端简化服务实例与服务端的交互。我们结合源码来分析下eureka组件的实现原理,内容分为上下两章,第一章分析eureka的服务注册,第二章分析eureka的心跳机制,本章节是第一章。

参考源码:<spring-cloud.version>Hoxton.SR9</spring-cloud.version>

往期系列:

【SpringBoot】SpringBoot源码解析第一章 SpringBoot的构造方法-CSDN博客

【SpringBoot】SpringBoot源码解析第二章 SpringBoot的run方法-CSDN博客

【SpringBoot】SpringBoot源码解析第三章 SpringBoot的自动化配置-CSDN博客

【SpringBoot】SpringBoot源码解析第四章 SpringBoot的bean接口-CSDN博客

【SpringBoot】SpringBoot源码解析第五章 SpringBoot的beanDefinition收集过程-CSDN博客

【SpringBoot】SpringBoot源码解析第六章 SpringBoot的getBean方法-CSDN博客

【SpringBoot】SpringBoot源码解析第七章 SpringBoot的感悟-CSDN博客

1、注册服务

1.1 服务端接收注册信息

spring-cloud-netflix-eureka-server依赖包下有一个spring.factories文件,文件内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

根据springboot自动配置的原理可知,EurekaServerAutoConfiguration会被标记成了一个自动配置类。EurekaServerAutoConfiguration配置类中有一个jerseyApplication方法,这个方法会收集指定包下被Path或Provider注解标记的类的beanDefinition,这些类可以看作是Controller

// 扫描包路径
private static final String[] EUREKA_PACKAGES = 
	new String[]{"com.netflix.discovery", "com.netflix.eureka"};    


// 收集包下指定类的beanDefinition,放入application对象
@Bean
public Application jerseyApplication(Environment environment, ResourceLoader resourceLoader) {
	ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment);

	// 收集的对象要求被Path或Provider注解标记
	provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
	provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
	String[] var5 = EUREKA_PACKAGES;
	int var6 = var5.length;

	for(int var7 = 0; var7 < var6; ++var7) {
		String basePackage = var5[var7];

		// 扫描包路径,收集beanDefinition
		Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
		Iterator var10 = beans.iterator();

		while(var10.hasNext()) {
			BeanDefinition bd = (BeanDefinition)var10.next();
			Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(), resourceLoader.getClassLoader());
			classes.add(cls);
		}
	}

	...
	return rc;
}

// 获取到application,将beanDefinition置入servlet容器
@Bean
public FilterRegistrationBean<?> jerseyFilterRegistration(Application eurekaJerseyApp) {
	FilterRegistrationBean<Filter> bean = new FilterRegistrationBean();
	bean.setFilter(new ServletContainer(eurekaJerseyApp));
	bean.setOrder(Integer.MAX_VALUE);
	bean.setUrlPatterns(Collections.singletonList("/eureka/*"));
	return bean;
}

收集的beanDefinition会通过jerseyFilterRegistration方法放入servlet容器,这样接收请求时就能通过url映射给指定的bean来处理请求

com.netflix.eureka包下被扫描的类如下:

ApplicationResource类是Controller中的一员,它有一个addInstance方法,这个方法就是服务端响应服务注册的方法

@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-replication") String isReplication) {
		...
		
		// 执行注册
		this.registry.register(info, "true".equals(isReplication));
		return Response.status(204).build();
}
调用链:
-> ApplicationResource.addInstance
-> InstanceRegistry.register
-> PeerAwareInstanceRegistryImpl.register
-> AbstractInstanceRegistry.register

服务端使用currentHashMap来存储服务的信息,服务端响应注册的过程较为简单

// 用currentHashMap存储服务信息 
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
					 = new ConcurrentHashMap();    

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication)   
{
		...
		Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
		Lease<InstanceInfo> lease = new Lease(registrant, leaseDuration);
		if (existingLease != null) {
			lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
		}
		
		// 将服务信息放入map中
		((Map)gMap).put(registrant.getId(), lease);
		...
}

1.2 客户端发送注册信息
1.2.1 client客户端

spring-cloud-netflix-eureka-client依赖包下也有一个spring.factories文件,文件内容如下

...
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
...

EurekaClientAutoConfiguration被标记成自动配置类,它里面有一个创建EurekaClient类对象的bean方法,看类的名称我们知道这是一个客户端

@Bean(
	destroyMethod = "shutdown"
)
@ConditionalOnMissingBean(
	value = {EurekaClient.class},
	search = SearchStrategy.CURRENT
)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance, @Autowired(required = false) HealthCheckHandler healthCheckHandler) {
	ApplicationInfoManager appManager;
	if (AopUtils.isAopProxy(manager)) {
		appManager = (ApplicationInfoManager)ProxyUtils.getTargetObject(manager);
	} else {
		appManager = manager;
	}

	// 创建客户端
	CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager, config, this.optionalArgs, this.context);
	cloudEurekaClient.registerHealthCheck(healthCheckHandler);
	return cloudEurekaClient;
}
调用链:
-> EurekaAutoServiceRegistration.eurekaClient
-> new CloudEurekaClient(appManager, config, this.optionalArgs, this.context);
-> CloudEurekaClient.super(applicationInfoManager, config, args);
-> DiscoveryClient.DiscoveryClient

... 构造方法重载

-> DiscoveryClient.DiscoveryClient
-> initScheduledTasks

跟踪EurekaClient类的构造方法找到DiscoveryClient类,DiscoveryClient类的构造方法调用了initScheduledTasks方法,初始化了一个定时任务

private void initScheduledTasks() {
	...
		// 添加状态变更监听器
		this.statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
			public String getId() {
				return "statusChangeListener";
			}

			public void notify(StatusChangeEvent statusChangeEvent) {
				if (statusChangeEvent.getStatus() == InstanceStatus.DOWN) {
					DiscoveryClient.logger.error("Saw local status change event {}", statusChangeEvent);
				} else {
					DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
				}

				// 监听器被通知后调用onDemandUpdate方法
				DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
			}
		};
	...

}

定时任务内添加了一个状态修改监听器,监听器调用notify方法时会回调onDemandUpdate方法,追踪这个回调方法

调用链:
-> InstanceInfoReplicator.onDemandUpdate
-> InstanceInfoReplicator.this.run
-> this.discoveryClient.register
-> this.eurekaTransport.registrationClient.register(this.instanceInfo)
-> AbstractJerseyEurekaHttpClient.register

进入到AbstractJerseyEurekaHttpClient类的register方法

public EurekaHttpResponse<Void> register(InstanceInfo info) {
	String urlPath = "apps/" + info.getAppName();
	ClientResponse response = null;

	EurekaHttpResponse var5;
	try {
		// 向注册中心发送http请求
		WebResource.Builder resourceBuilder = this.jerseyClient
				.resource(this.serviceUrl)
				.path(urlPath)
				.getRequestBuilder();
		this.addExtraHeaders(resourceBuilder);
		response = (ClientResponse)((WebResource.Builder)((WebResource.Builder)((WebResource.Builder)resourceBuilder.header("Accept-Encoding", "gzip")).type(MediaType.APPLICATION_JSON_TYPE)).accept(new String[]{"application/json"})).post(ClientResponse.class, info);
		var5 = EurekaHttpResponse.anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
	} finally {
		if (logger.isDebugEnabled()) {
			logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", new Object[]{this.serviceUrl, urlPath, info.getId(), response == null ? "N/A" : response.getStatus()});
		}

		if (response != null) {
			response.close();
		}

	}

	return var5;
}

注册方法写得很直白了:客户端拿到注册中心地址,然后携带服务元数据,发送请求完成注册。不过还有一个问题,之前我们提到定时任务内初始化了一个监听器,这个监听器只有被通知了才会执行后续的注册方法,那么监听器是如何被通知的?它的触发时机又在何时?

1.2.2 监听器

EurekaClientAutoConfiguration配置类还有一个创建EurekaAutoServiceRegistration类的bean方法

// 创建服务注册客户端 
@Bean
@ConditionalOnBean({AutoServiceRegistrationProperties.class})
@ConditionalOnProperty(
	value = {"spring.cloud.service-registry.auto-registration.enabled"},
	matchIfMissing = true
)
public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry registry, EurekaRegistration registration) {
	return new EurekaAutoServiceRegistration(context, registry, registration);
}

EurekaAutoServiceRegistration类实现了SmartLifecycle接口。当spring容器加载完所有bean后会调用SmartLifeCycle接口实现类的start方法,start方法调用EurekaServiceRegistry类的regiser方法

public class EurekaAutoServiceRegistration implements 
    AutoServiceRegistration, 
    SmartLifecycle, 
    Ordered, 
    SmartApplicationListener 
{


    public void start() {
        if (this.port.get() != 0) {
            if (this.registration.getNonSecurePort() == 0) {
                this.registration.setNonSecurePort(this.port.get());
            }

            if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
                this.registration.setSecurePort(this.port.get());
            }
        }

        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
            
            // 调用EurekaServiceRegistry的regiser
            this.serviceRegistry.register(this.registration);
            this.context.publishEvent(new InstanceRegisteredEvent(this, 
            this.registration.getInstanceConfig()));
            this.running.set(true);
        }

    }
}

EurekaServiceRegistry类的regiser方法会设置实例的状态。进入ApplicationInfoManager类的setInstanceStatus方法

// 设置实例状态        
reg.getApplicationInfoManager().setInstanceStatus(
		  reg.getInstanceConfig().getInitialStatus());
      

setInstanceStatus方法触发了一个状态修改事件,并且通知了监听器

public synchronized void setInstanceStatus(InstanceInfo.InstanceStatus status) {
	InstanceInfo.InstanceStatus next = this.instanceStatusMapper.map(status);
	if (next != null) {
		InstanceInfo.InstanceStatus prev = this.instanceInfo.setStatus(next);
		if (prev != null) {
			Iterator var4 = this.listeners.values().iterator();

			while(var4.hasNext()) {
				StatusChangeListener listener = (StatusChangeListener)var4.next();

				try {
					// 通知监听器
					listener.notify(new StatusChangeEvent(prev, next));
				} catch (Exception var7) {
					logger.warn("failed to notify listener: {}", listener.getId(), var7);
				}
			}
		}

	}
}

这里的监听器和上面提到的状态修改监听器其实是同一个监听器,在调用EurekaAutoServiceRegistration对象的start方法后,监听器会收到通知然后调用客户端的register方法,这就是发送注册服务请求的执行时机

2、拉取服务

2.1 初次拉取

客户端第一次拉取服务和DiscoveryClient类的构造方法有关,详情如下:

@Inject
DiscoveryClient(...){
	...
	// 调用fetchRegistry方法,拉取服务
	boolean primaryFetchRegistryResult = this.fetchRegistry(false);
	if (!primaryFetchRegistryResult) {
		 logger.info("Initial registry fetch from primary servers failed");
	}
	...
}  

private boolean fetchRegistry(boolean forceFullRegistryFetch) {
	...
	// 调用getAndStoreFullRegistry方法,拉取全部服务
	this.getAndStoreFullRegistry();
	...
}

private void getAndStoreFullRegistry() throws Throwable {
	...
	long currentUpdateGeneration = this.fetchRegistryGeneration.get();
	// 启动时会打印这行日志
	logger.info("Getting all instance registry info from the eureka server");
	Applications apps = null;
	// 发送http请求
	EurekaHttpResponse<Applications> httpResponse = this.clientConfig.getRegistryRefreshSingleVipAddress() == null ? this.eurekaTransport.queryClient.getApplications((String[])this.remoteRegionsRef.get()) : this.eurekaTransport.queryClient.getVip(this.clientConfig.getRegistryRefreshSingleVipAddress(), (String[])this.remoteRegionsRef.get());
	if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
		apps = (Applications)httpResponse.getEntity();
	}
	...
}
2.2 定时拉取

为了保证服务信息真实可信,客户端会定时拉取远程注册列表更新本地数据。提到到定时任务,自然的联想到DiscoveryClient类的initScheduledTasks方法(1.2.1的内容)

private void initScheduledTasks() {
	int renewalIntervalInSecs;
	int expBackOffBound;
	if (this.clientConfig.shouldFetchRegistry()) {
		renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
		expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();

		// 定时刷新本地服务列表任务,具体任务在CacheRefreshThread内
		this.cacheRefreshTask = new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new 
		// 执行任务的线程
		CacheRefreshThread());
		this.scheduler.schedule(this.cacheRefreshTask, (long)renewalIntervalInSecs, TimeUnit.SECONDS);
	}
}

class CacheRefreshThread implements Runnable {
	CacheRefreshThread() {
	}

	public void run() {
		// 刷新服务列表
		DiscoveryClient.this.refreshRegistry();
	}
}

@VisibleForTesting
void refreshRegistry() {
	...
	// 获取服务列表
	boolean success = this.fetchRegistry(remoteRegionsModified);
	...
}

3、总结

eureka服务端启动后通过自动配置加载com.netflix.eureka包下的处理器,处理器会响应注册、拉取、剔除服务等http请求

eureka客户端启动后会发送注册请求,并定时更新服务列表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/761421.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【每日一练】python if选择判断结构应用

应用类 1. 计算面积 编写一个Python程序&#xff0c;计算矩形的面积。要求用户输入矩形的宽和高&#xff0c;然后计算并打印面积。 width float(input("请输入矩形的宽&#xff1a;")) height float(input("请输入矩形的高&#xff1a;")) if width &…

《数字图像处理与机器视觉》案例三 (基于数字图像处理的物料堆积角快速测量)

一、前言 物料堆积角是反映物料特性的重要参数&#xff0c;传统的测量方法将物料自然堆积&#xff0c;测量物料形成的圆锥表面与水平面的夹角即可&#xff0c;该方法检测效率低。随着数字成像设备的推广和应用&#xff0c;应用数字图像处理可以更准确更迅速地进行堆积角测量。 …

Visual Studio 设置回车代码补全

工具 -> 选项 -> 文本编辑器 -> C/C -> 高级 -> 主动提交成员列表 设置为TRUE

萨科微slkor金航标kinghelm的品牌海外布局

萨科微slkor&#xff08;www.slkormicro.com&#xff09;金航标kinghelm宋仕强在介绍品牌的海外布局时说&#xff0c; 萨科微目前在土耳其、印度、新加坡均有代理商&#xff0c;海外代理商还在不断的发展和筛选中&#xff01;欢迎公司有资质&#xff0c;在当地有一定客户关系的…

Axure 中继器 实现选取表格行、列交互

Axure 中继器 实现选取表格行、列交互 引言 在办公软件或富文本编辑器中插入表格的时候我们经常可以通过在表格上移动鼠标&#xff0c;可以选取想要插入的表格行、列数。 本文分享如何通过 Axure 实现这个交互。 放入中继器 这个交互的用到了中继器&#xff0c;所以首先在…

WPF/C#:BusinessLayerValidation

BusinessLayerValidation介绍 BusinessLayerValidation&#xff0c;即业务层验证&#xff0c;是指在软件应用程序的业务逻辑层&#xff08;Business Layer&#xff09;中执行的验证过程。业务逻辑层是应用程序架构中的一个关键部分&#xff0c;负责处理与业务规则和逻辑相关的…

【C++】继承(详解)

前言&#xff1a;今天我们正式的步入C进阶内容的学习了&#xff0c;当然了既然是进阶意味着学习难度的不断提升&#xff0c;各位一起努力呐。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习 &#x1f448; &#…

2065. 最大化一张图中的路径价值 Hard

给你一张 无向 图&#xff0c;图中有 n 个节点&#xff0c;节点编号从 0 到 n - 1 &#xff08;都包括&#xff09;。同时给你一个下标从 0 开始的整数数组 values &#xff0c;其中 values[i] 是第 i 个节点的 价值 。同时给你一个下标从 0 开始的二维整数数组 edges &#xf…

电子技术基础(模电部分)笔记

终于整理出来了&#xff0c;可以安心复习大物线代了&#xff01;&#xff01; 数电部分预计7.10出

007-GeoGebra基础篇-构建等边三角形

今天继续来一篇尺规作图&#xff0c;可以跟着操作一波&#xff0c;刚开始我写的比较细一点&#xff0c;每步都有截图&#xff0c;后续内容逐渐复杂后我就只放置算式咯。 目录 一、先看看一下最终效果二、本次涉及的内容三、开始尺规画图1. 绘制定点A和B2. 绘制线段AB3. 以点A为…

【日记】度过了一个堕落的周末……(184 字)

正文 昨天睡了一天觉&#xff0c;今天看了一天《三体》电视剧。真是堕落到没边了呢&#xff08;笑。本来想写代码完成年度计划&#xff0c;或者多写几篇文章&#xff0c;但实在不想写&#xff0c;也不想动笔。 感觉这个周末什么都没做呢&#xff0c;休息倒是休息好了。 今天 30…

基于x86/ARM+FPGA+AI工业相机的智能工艺缺陷检测,可以检测点状,线状,面状的缺陷

应用场景 缺陷检测 在产品的制造生产环节中发挥着极其重要作用。智能工业缺陷检测能够替代传统的人工检测&#xff0c;降低人为判断漏失&#xff0c;使得产品质量大幅提升的同时降低了工厂的人力成本。智能工艺缺陷检测技术可以检测点状&#xff0c;线状&#xff0c;面状的缺陷…

UnityUGUI之四 Mask

会将上级物体遮盖 注&#xff1a; 尽量不使用Mask&#xff0c;因为其会过度消耗运行资源&#xff0c;可以使用Rect 2DMask&#xff0c;但容易造成bug&#xff0c;因此最好实现遮罩效果的方式为自己写一个mask物体

用易查分下发《致家长一封信》,支持在线手写签名,一键导出PDF!

暑假来临之际&#xff0c;学校通常需要下发致家长信&#xff0c;以正式、书面的形式向家长传达重要的通知或建议。传统的发放方式如家长签字后学生将回执单上交&#xff0c;容易存在丢失、遗忘的问题。 那么如何更高效、便捷、安全地将致家长一封信送达给每位家长呢&#xff1f…

项目方案:社会视频资源整合接入汇聚系统解决方案(八)---视频监控汇聚应用案例

目录 一、概述 1.1 应用背景 1.2 总体目标 1.3 设计原则 1.4 设计依据 1.5 术语解释 二、需求分析 2.1 政策分析 2.2 业务分析 2.3 系统需求 三、系统总体设计 3.1设计思路 3.2总体架构 3.3联网技术要求 四、视频整合及汇聚接入 4.1设计概述 4.2社会视频资源分…

多片体育场地建球馆,就选气膜球馆—轻空间

随着现代社会对健康生活的追求日益增加&#xff0c;体育场地的需求也在不断增长。尤其是羽毛球作为一项受欢迎的全民运动&#xff0c;其场地需求量更是与日俱增。在众多的球馆建设方案中&#xff0c;气膜球馆因其独特的优势&#xff0c;正逐渐成为多片体育场地建设的最佳选择。…

微尺度气象数值模拟—大涡模拟技术

原文链接&#xff1a;微尺度气象数值模拟—大涡模拟技术https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247607904&idx4&snc02e7b7b104c500626cc7456c819112a&chksmfa826787cdf5ee91860e66b4f45532b995760edc1780cdde52bb8b36349b9b997392d21aed18&…

为什么安装了SSL证书还是不能HTTPS访问?

即便是正确安装了SSL证书&#xff0c;有时网站仍然无法通过HTTPS正常访问&#xff0c;这背后可能隐藏着多种原因。以下是一些常见的问题及解决方案&#xff0c;帮助您排查并解决这一困扰。 PC点此申请&#xff1a;SSL证书申请_https证书下载-极速签发 注册填写注册码230918&a…

mysql-5.6.26-winx64免安装版本

mysql为什么要使用免安装 MySQL 提供免安装版本主要有以下几个原因和优势&#xff1a; 便捷性&#xff1a;用户无需经历安装过程&#xff0c;直接解压即可使用。这对于需要快速部署环境或者在不支持安装权限的系统上使用MySQL非常有用。灵活性&#xff1a;免安装版允许用户将…

基于SSM的挂号系统(简单)

设计技术&#xff1a; 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SSMJSP 工具&#xff1a;IDEA、Maven、Navicat 主要功能&#xff1a; 首页 登录 查看医生信息 挂号 挂号记录查看 个人信息查看 需要可加V分享源码 package com.hg.controller;impor…