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

这篇具有很好参考价值的文章主要介绍了Eureka 服务注册源码探秘——图解、源码级解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🍊 Java学习:社区快速通道

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

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


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


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


引言

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

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

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

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

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

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

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


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

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

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


Eureka 服务注册源码

寻找配置类

要使用Eureka,就需要在SpringBoot的启动类上添加 @EnableDiscoveryClient 注解,所以我们的源码解析,从启动类上的 @EnableDiscoveryClient 注解开始:
Eureka 服务注册源码探秘——图解、源码级解析

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

Eureka 服务注册源码探秘——图解、源码级解析
其中metadatamain函数里挂的注解:

Eureka 服务注册源码探秘——图解、源码级解析
attributes是会获得@EnableDiscoveryClientEnableDiscoveryClient注解,接下来读取注解里面的autoRegister属性,如果是true的话,会发现之后导入了一个配置类:

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

寻找服务注册的元数据

进入到该配置类:

Eureka 服务注册源码探秘——图解、源码级解析
继续进入到AutoServiceRegistrationProperties类里:
Eureka 服务注册源码探秘——图解、源码级解析
这些个属性一定会在某些配置项加载的流程中应用到,大家尝试找一下哪些类会引用它。

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

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

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

private final ServiceRegistry<R> serviceRegistry;

进入到ServiceRegistry的实现类EurekaServiceRegistry

Eureka 服务注册源码探秘——图解、源码级解析
进入到第一行的方法maybeInitializeClient里:

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

继续进入到getInfo

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

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


register方法

接下来继续执行EurekaServiceRegistryregister方法:

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

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


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

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


下一个流程

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

Eureka 服务注册源码探秘——图解、源码级解析
跟着断点继续往下走:
Eureka 服务注册源码探秘——图解、源码级解析
发现这里用的是SessionedEurekaHttpClient,接下来去找它的源码:

Eureka 服务注册源码探秘——图解、源码级解析
register方法在其父类EurekaHttpClientDecorator

Eureka 服务注册源码探秘——图解、源码级解析
通过一个子类实现的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去获取一个新的实例,但这里我们会发现其实并不为空:

Eureka 服务注册源码探秘——图解、源码级解析
这里调用了另一个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,下次不用了
Eureka 服务注册源码探秘——图解、源码级解析
此时还有好多层装饰器,这里直接快进跳到最后一层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里面存的是当前服务的所有信息

Eureka 服务注册源码探秘——图解、源码级解析
这一步结束之后就注册成功了

Eureka 服务注册源码探秘——图解、源码级解析文章来源地址https://www.toymoban.com/news/detail-437177.html

到了这里,关于Eureka 服务注册源码探秘——图解、源码级解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【SpringCloud】Eureka原理分析、搭建Eureka服务、服务注册、服务发现

    🐌个人主页: 🐌 叶落闲庭 💨我的专栏:💨 c语言 数据结构 javaEE 操作系统 Redis 石可破也,而不可夺坚;丹可磨也,而不可夺赤。 当有两个服务,第一个服务需要远程调用第二个服务,采用的方式是发起一次HTTP请求,在之前的代码中是将服务提供者的ip和端口号硬编码到

    2024年02月07日
    浏览(49)
  • eureka服务注册和服务发现

    我们要在orderservice中根据查询到的userId来查询user,将user信息封装到查询到的order中。 一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端

    2024年02月10日
    浏览(37)
  • 1.2 eureka注册中心,完成服务注册

    目录 环境搭建 搭建eureka服务 导入eureka服务端依赖 编写启动类,添加@EnableEurekaServer注解 编写eureka配置文件 启动服务,访问eureka Euraka服务注册 创建了两个子模块 在模块里导入rureka客户端依赖  编写eureka配置文件 添加Services 创建父工程,父工程中导入spring cloud的依赖,用来统

    2024年02月14日
    浏览(40)
  • 【1.2】Java微服务:eureka注册中心,完成服务注册

    目录 环境搭建 搭建eureka服务 导入eureka服务端依赖 编写启动类,添加@EnableEurekaServer注解 编写eureka配置文件 启动服务,访问eureka Euraka服务注册 创建了两个子模块 在模块里导入rureka客户端依赖  编写eureka配置文件 添加Services 创建父工程,父工程中导入spring cloud的依赖,用来统

    2024年02月14日
    浏览(38)
  • 什么是Eureka?以及Eureka注册服务的搭建

         导包  这是默认的Eureka server 的地址端口号为8761 如果我想用,子集的地址和自己的端口号,那么得在 yml配置文件里去写响应的配置,具体如下面的代码块实现  yml 主启动类 配置文件配置了,相当于把Eureka-server 那个类加载到IOC容器里供spring使用 然后允许就可以了,就

    2024年02月11日
    浏览(40)
  • 服务注册发现_搭建单机Eureka注册中心

    创建cloud-eureka-server7001模块 pom添加依赖 写yml文件 主启动类 测试 访问浏览器localhostL:7001 参数: Environment: 环境,默认为test,该参数在实际使用过程中,可以不用更改 Data center: 数据中心,使用的是默认的是 “MyOwn” Current time:当前的系统时间 Uptime:已经运行了多少时间

    2024年02月07日
    浏览(44)
  • 微服务Eureka注册中心

    目录 一、Eureka的结构和作用 二、搭建eureka-server 三、服务注册 四、服务发现 假如我们的服务提供者user-service部署了多个实例,如图: 存在的问题: order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口? 有多个user-service实例地址,order-service调用时该如

    2024年02月13日
    浏览(49)
  • 服务无法注册进Eureka

    相同的配置,在demo里能注册,在自己项目的无法注册,眼睛都快盯出老花眼了,还是不行,果然出现的问题只有在发现问题以后才觉得简单(虽然确实是小问题,但是排查了一整天,值得记录一下) 问题 :启动后不出现服务注册的日志,怀疑未发现eureka client的配置相关信息

    2024年02月12日
    浏览(28)
  • 【微服务】Eureka注册中心

    我们在前文的案例中,我们采取如下的方式发送http请求: 我们将user-service的ip地址和端口硬编码在了代码当中,这样的写法是有一定问题的。我们在公司开发中,可能会面临多个环境,开发环境、测试环境等等,每一次环境的变更可能服务的地址也会发生变化,使用硬编码显

    2024年01月15日
    浏览(35)
  • 微服务—Eureka注册中心

           eureka相当于是一个公司的管理人事HR,各部门之间如果有合作时,由HR进行人员的分配以及调度,具体选哪个人,全凭HR的心情,如果你这个部门存在没有意义,直接把你这个部门撤销,全体人员裁掉,所以不想被裁员,只能每天拼命的工作,做一个累死累活的打工人

    2024年02月12日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包