Eureka 服务注册源码探秘——图解、源码级解析

news2024/12/23 18:13:44

🍊 Java学习:社区快速通道

🍊 深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想

🍊 绝对不一样的职场干货:大厂最佳实践经验指南


📆 最近更新:2023年5月2日


🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力!


文章目录

  • 引言
  • Eureka 服务注册源码
    • 寻找配置类
    • 寻找服务注册的元数据
    • register方法
    • 下一个流程
    • 继续execute

引言

服务注册是为了解决各个微服务的“你是谁”这个问题,即获取所有服务节点的身份信息和服务名称,站在注册中心的角度来看,有以下两种比较直观的解决方案:

  1. 由注册中心主动访问网络节点中所有机器
  2. 注册中心等待服务节点主动进行注册

在这里插入图片描述

目前主流的注册中心(Nacos、Eureka)都选择了第二种方案,主要原因是第一种方案有很多弊端:

  • 模型复杂: 网络结点构成了一张复杂的网,结点与结点之间的关系错综复杂,轮询每个节点的做法通常是注册中心发局域网广播,客户端响应的方式。现实中对于跨局域网的分布式系统来说,响应模型会更加复杂。

  • 网络开销大: 整个网络环境里会掺杂大量非服务节点,这些节点无需对送达的广播请求做出响应,这种广播的模式无疑增加了网络通信成本。

  • 服务端压力增加: 不仅要求注册中心向网络中所有节点主动发送广播请求,还需要对客户端的应答做出响应。考虑到注册中心的节点数远远少于服务节点,所以要尽可能地减轻服务中心承载的业务。


一一对照着看,第二种实现方案就有如下优点:

  1. 注册中心压力小: 网络中其它非服务节点不会产生任何无效请求,也就不用做额外的判断
  2. 效率高: 省去了广播环节的时间,使注册效率大大提高
  3. 节省成本: 节省了大量网络请求的开销

下面就来探索一下经典注册中心微服务 Eureka 服务注册源码。


Eureka 服务注册源码

寻找配置类

要使用Eureka,就需要在SpringBoot的启动类上添加 @EnableDiscoveryClient 注解,所以我们的源码解析,从启动类上的 @EnableDiscoveryClient 注解开始:
请添加图片描述

在Eureka已经启动的状态下,以debug模式启动EurekaClientApplication,会来到这里面的断点:

请添加图片描述
其中metadatamain函数里挂的注解:

请添加图片描述
attributes是会获得@EnableDiscoveryClientEnableDiscoveryClient注解,接下来读取注解里面的autoRegister属性,如果是true的话,会发现之后导入了一个配置类:

请添加图片描述

寻找服务注册的元数据

进入到该配置类:

请添加图片描述
继续进入到AutoServiceRegistrationProperties类里:
请添加图片描述
这些个属性一定会在某些配置项加载的流程中应用到,大家尝试找一下哪些类会引用它。

其中你会找到AbstractAutoServiceRegistration,发现其在初始化的流程里使用到:

protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry, AutoServiceRegistrationProperties properties) {
    this.serviceRegistry = serviceRegistry;
    this.properties = properties;
}

同时还发现了一个服务注册属性serviceRegistry

private final ServiceRegistry<R> serviceRegistry;

进入到ServiceRegistry的实现类EurekaServiceRegistry

请添加图片描述
进入到第一行的方法maybeInitializeClient里:

private void maybeInitializeClient(EurekaRegistration reg) {
    reg.getApplicationInfoManager().getInfo();
    reg.getEurekaClient().getApplications();
}

继续进入到getInfo

请添加图片描述
请添加图片描述

发现这里面的信息其实就是我们要向服务中心注册的东西。


register方法

接下来继续执行EurekaServiceRegistryregister方法:

reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());

首先设置了instance的状态,这里reg.getInstanceConfig().getInitialStatus()是UP


这里的register并没有发起服务调用请求,所以还要通过调用栈来继续寻找。来到上一层EurekaAutoServiceRegistrationstart方法里:

请添加图片描述
停留在的这一行往上下文中发布了一个事件InstanceRegisteredEvent,但此时我们会发现服务其实并没有注册
请添加图片描述
说明在event发布前后肯定发生了什么事,让eureka服务提供者向注册中心发送了请求,既然event发布之后running的状态变为了true,那确实是运行起来了。


下一个流程

下一个流程在DiscoveryClient里,它封装了我们服务的client和注册中心之间的各种交互,里面有一个register方法

请添加图片描述
跟着断点继续往下走:
请添加图片描述
发现这里用的是SessionedEurekaHttpClient,接下来去找它的源码:

请添加图片描述
register方法在其父类EurekaHttpClientDecorator

请添加图片描述
通过一个子类实现的execute方法,参数是由父类传入的一个代理delegateexecute是由SessionedEurekaHttpClient子类实现的

protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
    long now = System.currentTimeMillis();
    long delay = now - this.lastReconnectTimeStamp;
    if (delay >= this.currentSessionDurationMs) {
        logger.debug("Ending a session and starting anew");
        this.lastReconnectTimeStamp = now;
        this.currentSessionDurationMs = this.randomizeSessionDuration(this.sessionDurationMs);
        TransportUtils.shutdown((EurekaHttpClient)this.eurekaHttpClientRef.getAndSet((Object)null));
    }

    EurekaHttpClient eurekaHttpClient = (EurekaHttpClient)this.eurekaHttpClientRef.get();
    if (eurekaHttpClient == null) {
        eurekaHttpClient = TransportUtils.getOrSetAnotherClient(this.eurekaHttpClientRef, this.clientFactory.newClient());
    }

    return requestExecutor.execute(eurekaHttpClient);
}

这一段代码尝试从HttpClient里拿实例,如果实例为空则会调用一个工具类的方法getOrSetAnotherClient去获取一个新的实例,但这里我们会发现其实并不为空:

请添加图片描述
这里调用了另一个httpclient。其中SessionedEurekaHttpClient用到了装饰器模式,主要装饰的功能是delay时间过长时重新启动一个session


进入到下一层RetryableEurekaHttpClient,这一层装饰的功能是可以重试,默认最大重试次数为3:

protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
    List<EurekaEndpoint> candidateHosts = null;
    int endpointIdx = 0;

    for(int retry = 0; retry < this.numberOfRetries; ++retry) {
        EurekaHttpClient currentHttpClient = (EurekaHttpClient)this.delegate.get();
        EurekaEndpoint currentEndpoint = null;
        if (currentHttpClient == null) {
            if (candidateHosts == null) {
                candidateHosts = this.getHostCandidates();
                if (candidateHosts.isEmpty()) {
                    throw new TransportException("There is no known eureka server; cluster server list is empty");
                }
            }

            if (endpointIdx >= candidateHosts.size()) {
                throw new TransportException("Cannot execute request on any known server");
            }

            currentEndpoint = (EurekaEndpoint)candidateHosts.get(endpointIdx++);
            currentHttpClient = this.clientFactory.newClient(currentEndpoint);
        }

        try {
            EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient);
            if (this.serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) {
                this.delegate.set(currentHttpClient);
                if (retry > 0) {
                    logger.info("Request execution succeeded on retry #{}", retry);
                }

                return response;
            }

            logger.warn("Request execution failure with status code {}; retrying on another server if available", response.getStatusCode());
        } catch (Exception var8) {
            logger.warn("Request execution failed with message: {}", var8.getMessage());
        }

        this.delegate.compareAndSet(currentHttpClient, (Object)null);
        if (currentEndpoint != null) {
            this.quarantineSet.add(currentEndpoint);
        }
    }

    throw new TransportException("Retry limit reached; giving up on completing the request");
}

其中this.getHostCandidates();获取的是注册中心:

private List<EurekaEndpoint> getHostCandidates() {
    List<EurekaEndpoint> candidateHosts = this.clusterResolver.getClusterEndpoints();
    this.quarantineSet.retainAll((Collection)candidateHosts);
    int threshold = (int)((double)((List)candidateHosts).size() * this.transportConfig.getRetryableClientQuarantineRefreshPercentage());
    if (threshold > ((List)candidateHosts).size()) {
        threshold = ((List)candidateHosts).size();
    }

    if (!this.quarantineSet.isEmpty()) {
        if (this.quarantineSet.size() >= threshold) {
            logger.debug("Clearing quarantined list of size {}", this.quarantineSet.size());
            this.quarantineSet.clear();
        } else {
            List<EurekaEndpoint> remainingHosts = new ArrayList(((List)candidateHosts).size());
            Iterator var4 = ((List)candidateHosts).iterator();

            while(var4.hasNext()) {
                EurekaEndpoint endpoint = (EurekaEndpoint)var4.next();
                if (!this.quarantineSet.contains(endpoint)) {
                    remainingHosts.add(endpoint);
                }
            }

            candidateHosts = remainingHosts;
        }
    }

    return (List)candidateHosts;
}

如果坏注册中心节点的数量超过了阈值(66%),则要重启。quarantineSet存储的是失败的注册中心,remainingHosts存储的是成功的注册中心。


继续execute

回到上面的execute方法里,如果重试的索引大于候选注册中心的size时,就表示已知的所有注册中心都不能处理注册请求,此时会抛一个异常出来:

if (endpointIdx >= candidateHosts.size()) {
    throw new TransportException("Cannot execute request on any known server");
}

currentEndpoint = (EurekaEndpoint)candidateHosts.get(endpointIdx++);
currentHttpClient = this.clientFactory.newClient(currentEndpoint);

如果在某一层execute成功了,则会将deligate设置为当前的client,如果不成功则会通过CAS操作将currentHttpClient设置为空,然后放置到失效的EurekaEndpoint加入到quarantineSet,下次不用了
请添加图片描述
此时还有好多层装饰器,这里直接快进跳到最后一层AbstractJerseyEurekaHttpClient中的register方法:

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

    EurekaHttpResponse var5;
    try {
        Builder resourceBuilder = this.jerseyClient.resource(this.serviceUrl).path(urlPath).getRequestBuilder();
        this.addExtraHeaders(resourceBuilder);
        response = (ClientResponse)((Builder)((Builder)((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;
}

在这里发送了http请求,info里面存的是当前服务的所有信息

请添加图片描述
这一步结束之后就注册成功了

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NPOI导出word文档中插入公式总结

1. XWPFOMath类 XWPFDocument doc new XWPFDocument(); //创建新行 XWPFParagraph p doc.CreateParagraph(); //创建空的公式 XWPFOMath math p.CreateOMath();通过XWPFParagraph的扩展方法创建 方法名备注CreateAcc();创建XWPFAcc类&#xff0c;实现字符在文字上面的类Cr…

【前端】2.HTML基础知识

文章目录 1. 基本概念1.1 HTML是什么1.2 HTML的作用1.3. 学习导引1.4 开发工具 2. HTML 基础语法2.1 demo2.1.1 HTML 详述2.1.2 HTML标签2.1.3 HTML网页结构2.1.4HTML版本 2.2 常用元素2.3 属性2.4 文本相关语法2.5 链接相关语法2.6 头部相关语法 3. 总结3.1 HTML 基础语法总结…

什么是VLAN?为什么要划分VLAN?

VLAN(Virtual Local Area Network)即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。每个VLAN是一个广播域&#xff0c;VLAN内的主机间可以直接通信&#xff0c;而VLAN间则不能直接互通。这样&#xff0c;广播报文就被限制在一个VLAN内。 一、为…

如何简单快速搭建自己的云对象存储服务(OSS)

简单来说&#xff0c;其实我们只需要有一台服务器&#xff0c;利用服务器的各种资源&#xff0c;搭配其它厂商开发的软件&#xff0c;就能很轻易拥有自己的云对象存储服务。不需要在阿里云上花钱买什么服务&#xff0c;甚至还能自己给别人提供服务&#xff0c;真的是太爽了。 云…

五一创作【Android构建篇】MakeFile语法

前言 对于一个看不懂Makefile构建文件规则的人来说&#xff0c;这个Makefile语法和shell语法是真不一样&#xff0c;但是又引用了部分shell语法&#xff0c;可以说是shell语法的子类&#xff0c;Makefile语法继承了它。 和shell语法不一样&#xff0c;这个更难一点&#xff0…

云原生架构的发展历史

目录 1 单机小型机时代2 垂直拆分3 集群化负载均衡架构4 服务化改造架构5 服务治理6 微服务时代7 服务网格新时期 &#xff08;service mesh&#xff09;7.1 背景7.2 SideCar7.3 Linkerd7.4 istio7.5 什么是服务网格7.6 什么是Service Mesh7.7 CNCF云原生组织发展和介绍7.8 国内…

C++11--线程库的认识

目录 thread 线程的构造方式 相关成员函数 join与detach 线程传参 互斥量mutex mutex Locks 原子性操作库 条件变量 thread 线程的构造方式 它是不支持拷贝构造&#xff0c;赋值的&#xff0c;但是可以支持移动构造&#xff0c;移动赋值。还可以直接创建无参的对象。 …

存储器(一)

目录 一、存储器的分类 1.按介质分类 1.1半导体存储器 1.2磁表面存储器 1.3光盘存储器 2.按存取方式分类 2.1随机存储器(RAM) 2.2只读存储器(ROM) 2.3串行访问存储器 3.按在计算机中的作用分类 ​编辑 二、存储器的层次结构 1.存储器的主要性能指标: 2.存储系统体系…

模式识别是什么意思

模式识别是一种通过分析数据特征、模型、算法等手段&#xff0c;从数据中寻找规律、发现隐藏的模式或结构的技术。通常是从某些对象、场景、过程等方面入手&#xff0c;对数据进行处理&#xff0c;以便于对这些对象、场景、过程进行分类、检测、识别、分割、分析等目的。 模式…

HJ20 密码验证合格程序

写在前面&#xff1a; 题目链接&#xff1a;牛客网 华为机试题 HJ20 密码验证合格程序 题目难度&#xff1a;中等 编程语言&#xff1a;C 一、题目描述 描述 密码要求: 1.长度超过8位 2.包括大小写字母.数字.其它符号,以上四种至少三种 &#xff08;注&#xff1a;其他符号不…

死信队列

死信队列 死信的概念 先从概念解释上搞清楚这个定义&#xff0c;死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;字面意思可以这样理解&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或者直接到queue 里了&#xff0c;consumer 从 queue 取出消息…

[Pandas] 构建DataFrame数据框

DataFrame是二维数据结构&#xff0c;数据以行和列的形式排列 构建DataFrame最基本的定义格式如下 df pd.DataFrame(dataNone, indexNone, columnsNone) 参数说明 data: 具体数据 index: 行索引&#xff0c;如果没有指定&#xff0c;会自动生成RangeIndex(0,1,2,...,n) colu…

mongoose使用详细 -- 如何通过mongoose搭建服务器

前言 授人以鱼不如授人以渔&#xff0c;这篇文章详细介绍了&#xff0c;对于一个从来没有听说过mongoose的小菜鸟如何快速了解和上手mongoose 其他一些开源库可以借助类似的方法进行学习 提前需要准备的工具 1.官网文档 Mongoose :: Documentation 官网提供了很多例子讲解&am…

[Pandas] 查看DataFrame的常用属性

导入数据 import pandas as pddf pd.DataFrame([[L123,A,0,123],[L456,A,1,456],[L437,C,0,789],[L112,B,1,741],[L211,A,0,852],[L985,B,1,963]],columns[Material,Level,Passing,LT]) df 1.dtypes: 查看DataFrame中各列的数据类型 df.dtypes会返回每个字段的数据类型及Da…

C++练级之初级:第六篇

类和对象入门级&#xff1a;第六篇 1.类的引入2.类的定义2.1类的访问限定符2.2类的封装2.3类的实例化 3.如何计算类或者对象的大小4.this指针 总结 我们知道&#xff0c;C在C语言的基础上引入了对象的概念&#xff0c;那么从本篇开始进入类和对象&#xff1b; 1.类的引入 &…

【JavaEE】_1.多线程(1)

目录 1.操作系统 2. 进程 3. CPU分配——进程调度 3.1 操作系统对进程的管理 3.2 PCB的属性 3.2.1 基础属性 3.2.2 实现进程调度的属性 4. 内存分配——内存管理 4.1 虚拟地址空间 4.2 进程间通信 5. 线程 5.1 线程的概念 5.2 创建与使用多线程 5.2.1 方式1&a…

【数据结构】八大排序(一)

&#x1f61b;作者&#xff1a;日出等日落 &#x1f4d8; 专栏&#xff1a;数据结构 珍惜自己的时间&#xff0c;利用好每一份每一秒。做事不放过没一个细节&#xff0c;小心谨慎&#xff0c;细致&#xff0c;能够做到这些&#xff0c;还有什么是不可能的呢? 目录 ​编辑 ✔…

【刷题之路Ⅱ】LeetCode 61. 旋转链表

【刷题之路Ⅱ】LeetCode 61. 旋转链表 一、题目描述二、解题1、方法1——移动部分链表1.1、思路分析1.2、代码实现 2、方法1——闭合为环2.1、思路分析2.2、代码实现 一、题目描述 原题连接&#xff1a; 61. 旋转链表 题目描述&#xff1a; 给你一个链表的头节点 head &#x…

【Python | matplotlib】matplotlib.cm的理解以及举例说明

文章目录 一、模块介绍二、颜色举例 一、模块介绍 matplotlib.cm是Matplotlib中的一个模块&#xff0c;它提供了一组用于处理颜色映射&#xff08;colormap&#xff09;的函数和类。颜色映射是一种将数值映射到颜色的方法&#xff0c;常用于制作热力图、等值线图、散点图等。 …

软件工程实验:原型设计

目录 前言实验目的实验要求实验过程系统原型绘制生成html代码 总结 前言 本次实验的主题是原型设计&#xff0c;即根据用户需求和系统功能&#xff0c;设计一个简单的软件原型&#xff0c;展示系统的界面和交互方式。原型设计是软件工程中的一种重要技术&#xff0c;它可以帮助…