五、Eureka服务注册、续约、剔除、下线源码分析

2024-01-05 18:40

本文主要是介绍五、Eureka服务注册、续约、剔除、下线源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Eureka 概念的理解

1 服务的注册

当项目启动时(eureka 的客户端),就会向 eureka-server 发送自己的元数据(原始数据)(运行的 ip,端口 port,健康的状态监控等,因为使用的是 http/ResuFul 请求风格),eureka-server 会在自己内部保留这些元数据(内存中)。(有一个服务列表)(restful 风
格,以 http 动词的请求方式,完成对 url 资源的操作)

2 服务的续约

项目启动成功了,除了向 eureka-server 注册自己成功,还会定时的向 eureka-server 汇报自己,心跳,表示自己还活着。(修改一个时间)

3 服务的下线(主动下线)

当项目关闭时,会给 eureka-server 报告,说明自己要下机了。

4 服务的剔除(被动下线,主动剔除)

当项目超过了指定时间没有向 eureka-server 汇报自己,那么 eureka-server 就会认为此节点死掉了,会把它剔除掉,也不会放流量和请求到此节点了。

Eureka 源码分析

为什么要学源码? 答:了解他的原理 出了问题排查 bug,优化你的代码

1 Eureka 运作原理的特点

Eureka-server 对外提供的是 restful 风格的服务以 http 动词的形式对 url 资源进行操作 get post put delete

http 服务 + 特定的请求方式 + 特定的 url 地址

只要利用这些 restful 我们就能对项目实现注册和发现,只不过,eureka 已经帮我们使用 java 语言写了 client,让我们的项目只要依赖 client 就能实现注册和发现!
只要你会发起 Http 请求,那你就有可能自己实现服务的注册和发现。不管你是什么语言!

2 服务注册的源码分析【重点】

在这里插入图片描述

2.1 Eureka-client 发起注册请求

2.1.1 源码位置

在这里插入图片描述

2.1.2如何发送信息注册自己

在这里插入图片描述

2.1.3 真正的注册 AbstractJerseyEurekaHttpClient

在这里插入图片描述
总结:

当 eureka 启动的时候,会向我们指定的 serviceUrl 发送请求,把自己节点的数据以 post
请求的方式,数据以 json 形式发送过去。 当返回的状态码为 204 的时候,表示注册成功。

2.2 Eureka-server 实现注册+保存

2.2.1 接受客户端的请求

com.netflix.eureka.resources.ApplicationResource
在这里插入图片描述

2.2.2 源码位置

在这里插入图片描述

2.2.3 接受 client 的注册请求

在这里插入图片描述

2.2.4 处理请求(注册自己,向其他节点注册)

在这里插入图片描述

2.2.5 真正的注册自己

在这里插入图片描述

2.2.6 具体源码分析

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {try {read.lock();//通过服务名称得到注册的实例Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());REGISTER.increment(isReplication);//因为之前没有实例,肯定为 nullif (gMap == null) {//新建一个集合来存放实例final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = newConcurrentHashMap<String, Lease<InstanceInfo>>();gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);if (gMap == null) {gMap = gNewMap;}
}//gMap 就是该服务的实例Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
if (existingLease != null && (existingLease.getHolder() != null)) {Long existingLastDirtyTimestamp =existingLease.getHolder().getLastDirtyTimestamp();Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();logger.debug("Existing lease found (existing={}, provided={}",existingLastDirtyTimestamp, registrationLastDirtyTimestamp);// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
// InstanceInfo instead of the server local copy.if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {logger.warn("There is an existing lease and the existing lease's dirty timestamp
{} is greater" +
" than the one that is being registered {}", existingLastDirtyTimestamp,
registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as
the registrant");
registrant = existingLease.getHolder();
}
} else {
// The lease does not exist and hence it is a new registration
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to register it, increase the number of clients
sending renews
this.expectedNumberOfClientsSendingRenews =
this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
logger.debug("No previous lease information found; it is new registration");
}//新建一个服务的实例节点Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}//放到注册 map 的列表里gMap.put(registrant.getId(), lease);recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
// This is where the initial state transfer of overridden status happens
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
logger.debug("Found overridden status {} for instance {}. Checking to see if needs
to be add to the "
+ "overrides", registrant.getOverriddenStatus(),
registrant.getId());
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it",
registrant.getId());
overriddenInstanceStatusMap.put(registrant.getId(),
registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap =
overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
// Set the status based on the overridden status rules
InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant,
existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// If the lease is registered with UP status, set lease service up timestamp
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(ActionType.ADDED);
recentlyChangedQueue.add(new RecentlyChangedItem(lease));//设置心跳时间等参数registrant.setLastUpdatedTimestamp();
invalidateCache(registrant.getAppName(),
registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {} (replication={})",
registrant.getAppName(), registrant.getId(), registrant.getStatus(),
isReplication);
} finally {
read.unlock();
}
}

2.3 服务注册总结

重要的类:

DiscoveryClient 里面的 register()方法完后注册的总体构造

AbstractJerseyEurekaHttpClient 里面的 register()方法具体发送注册请求(post)

InstanceRegistry 里面 register()方法接受客户端的注册请求

PeerAwareInstanceRegistryImpl 里面调用父类的 register()方法实现注册

AbstractInstanceRegistry 里面的 register()方法完成具体的注册保留数据到 map 集合

保存服务实例数据的集合:

第一个 key 是应用名称(全大写) spring.application.name
Value 中的 key 是应用的实例 id eureka.instance.instance-id
Value 中的 value 是 具体的服务节点信息

private final ConcurrentHashMap<String, Map<String,Lease<InstanceInfo>>> registry= new ConcurrentHashMap<String, Map<String,Lease<InstanceInfo>>>();

3 服务续约的源码分析

3.1 Eureka-client 发起续约请求

3.1.1 如何发请求续约自己

DiscoveryClient 的 renew()方法
在这里插入图片描述

3.1.2 真正的请求续约自己(AbstractJerseyEurekaHttpClient)

在这里插入图片描述

3.2 Eureka-server 实现续约操作

3.2.1 接受续约的请求

在这里插入图片描述

3.2.2 真正的续约

在这里插入图片描述

3.2.3 续约的本质

续约的本质就是修改了服务节点的最后更新时间
在这里插入图片描述
duration:代表注册中心最长的忍耐时间:
并不是 30s 没有续约就里面剔除,而是 30 +duration(默认是 90s) 期间内没有续约,才剔除服务

在这里插入图片描述
Volatile 标识的变量是具有可见性的,当一条线程修改了我的剔除时间,其他线程就可以立马看到(应用场景:一写多读),后面在剔除里面有一个定时任务,去检查超时从而判断某一个服务是否应该被剔除。

4 服务剔除的源码分析(被动下线)

4.1 Eureka-server 实现服务剔除

4.1.1 在 AbstractInstanceRegistry 的 evict()方法中筛选剔除的节点

public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// We collect first all expired items, to evict them in random order. For large
eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks
in. By randomizing it,
// the impact should be evenly distributed across all applications.
//创建一个新的集合来存放过期的服务实例List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry :
registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
//循环for (Entry<String, Lease<InstanceInfo>> leaseEntry :
leaseMap.entrySet()) {
Lease<InstanceInfo> lease = leaseEntry.getValue();
//判断过期,加入集合中if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null)
{expiredLeases.add(lease);}
}
}
}
// To compensate for GC pauses or drifting local time, we need to use current
registry size as a base for
// triggering self-preservation. Without that we would wipe out full registry.
int registrySize = (int) getLocalRegistrySize();
int registrySizeThreshold = (int) (registrySize *
serverConfig.getRenewalPercentThreshold());
int evictionLimit = registrySize - registrySizeThreshold;
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict,
expiredLeases.size(), evictionLimit);
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < toEvict; i++) {
// Pick a random item (Knuth shuffle algorithm)
int next = i + random.nextInt(expiredLeases.size() - i);
Collections.swap(expiredLeases, i, next);
Lease<InstanceInfo> lease = expiredLeases.get(i);
String appName = lease.getHolder().getAppName();
String id = lease.getHolder().getId();
EXPIRED.increment();
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
//这整个方法并没有真的杀死过期的服务节点//下面这个方法才是真正干掉过期的服务internalCancel(appName, id, false);}
}
}

4.1.2 在 internalCancel 方法里面真正实现剔除

在这里插入图片描述

4.1.3 在服务剔除中涉及到哪些重要的点

怎么删除一个集合里面过期的数据?

Redis 怎么清除过期的 key LRU(热点 key)

1 定时(k-thread)

2 惰性 (在再次访问该 key 时有作用)

3 定期 (使用一个线程来完成清除任务)

定期(实时性差) + 惰性

4.1.4 什么时候执行服务剔除操作呢?

查看 evict()方法在哪里调用的
在这里插入图片描述
具体查看多久执行一次呢?
在这里插入图片描述
发现默认是 60s 执行一次
在这里插入图片描述
当然我们也可以自定义检测定时器的执行时间

在这里插入图片描述

服务下线的源码分析

5.1 Eureka-client 发起下线请求

5.1.1 如何发起下线请求

在这里插入图片描述

5.1.2 真正的发请求下线 AbstractJerseyEurekaHttpClient

在这里插入图片描述

5.2 Eureka-server 处理下线请求

5.2.1 接受下线请求

在这里插入图片描述

5.2.2 真正的下线服务

在这里插入图片描述

这篇关于五、Eureka服务注册、续约、剔除、下线源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/573801

相关文章

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实