【云原生&微服务四】SpringCloud之Ribbon和Erueka/服务注册中心的集成细节(获取服务实例列表、动态更新服务实例信息、负载均衡出一个实例、IPing机制判断实例是否存活)

本文涉及的产品
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 【云原生&微服务四】SpringCloud之Ribbon和Erueka/服务注册中心的集成细节(获取服务实例列表、动态更新服务实例信息、负载均衡出一个实例、IPing机制判断实例是否存活)

@[toc]

一、前言

在前面的文章,博主聊了Ribbon如何与SpringCloud、Eureka集成,Ribbon如何自定义负载均衡策略、Ribbon如何和SpringCloud集成:

  1. 【云原生&微服务一】SpringCloud之Ribbon实现负载均衡详细案例(集成Eureka、Ribbon)
  2. 【云原生&微服务二】SpringCloud之Ribbon自定义负载均衡策略(含Ribbon核心API)
  3. 【云原生&微服务三】SpringCloud之Ribbon是这样实现负载均衡的(源码剖析@LoadBalanced原理)

【云原生&微服务三】SpringCloud之Ribbon是这样实现负载均衡的(源码剖析@LoadBalanced原理)一文中博主分析到了SpringCloud集成Ribbon如何获取到负载均衡器ILoadBalancer;本文我们接着分析如下问题:

  • ZoneAwareLoadBalancer(属于ribbon)如何与eureka整合,通过eureka client获取到对应注册表?
  • ZoneAwareLoadBalancer如何持续从Eureka中获取最新的注册表信息?
  • 如何根据负载均衡器ILoadBalancer从Eureka Client获取到的List<Server>中选出一个Server?
  • Ribbon如何发送网络HTTP请求?
  • Ribbon如何用IPing机制动态检查服务实例是否存活?

PS: 文章中涉及到的SpringBoot相关知识点,比如自动装配,移步博主的SpringBoot专栏:Spring Boot系列

PS--2:Ribbon依赖Spring Cloud版本信息如下:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.3.7.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--整合spring cloud-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR8</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--整合spring cloud alibaba-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

==下面以请求http://localhost:9090/say/saint为入口进行debug。==

二、Ribbon和Eureka

我们知道SpringCloud诞生之初,我们通常采用Eureka作为服务注册/发现中心、Ribbon作为负载均衡器,而Ribbon的诞生也正是因为要结合Eureka做负载均衡;而现在很多项目都是Nacos + OpenFeign的组合,不过OpenFeign的底层还是Ribbon,其很多便捷性体现在代理对象的封装,后续我们接着讨论。

1、Ribbon如何与eureka整合,通过eureka client获取到对应的注册表?

在博文:SpringCloud之Ribbon是这样实现负载均衡的(源码剖析@LoadBalanced原理)中我们知道了Ribbon默认的ILoadBalancer是ZoneAwareLoadBalancer;所以我们这里就看一下ZoneAwareLoadBalancer如何与eureka整合,通过eureka client获取到对应的注册表?

先看ZoneAwareLoadBalancer的类图:
在这里插入图片描述
ZoneAwareLoadBalancer的父类是DynamicServerListLoadBalancer,DynamicServerListLoadBalancer构造函数中会调用restOfInit()方法(其中会获取到所有的服务实例);

void restOfInit(IClientConfig clientConfig) {
    boolean primeConnection = this.isEnablePrimingConnections();
    // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
    this.setEnablePrimingConnections(false);
    // 感知新的服务实例的添加/移除,即动态维护服务实例列表
    enableAndInitLearnNewServersFeature();

    // 更新Eureka client 中所有服务的实例列表,即初始化服务实例列表
    updateListOfServers();
    if (primeConnection && this.getPrimeConnections() != null) {
        this.getPrimeConnections()
            .primeConnections(getReachableServers());
    }
    this.setEnablePrimingConnections(primeConnection);
    LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}

1)为什么是DynamicServerListLoadBalancer的restOfInit()方法?

第一次获取Eureka中服务实例列表的执行流程如下:
在这里插入图片描述
第一次从Ribbon的SpringClientFactory中获取GREETING-SERVICE服务对应的Spring子上下文时,获取不到,所以需要创建针对GREETING-SERVICE服务创建Spring子上下文;

最终进入到NamedContextFactory#createContext(String name)方法中,方法的最后会调用AnnotationConfigApplicationContext#refresh()方法,refresh()方法中会对ZoneAwareLoadBalancer进行初始化;又由于DynamicServerListLoadBalancer是ZoneAwareLoadBalancer的父类,所以初始化ZoneAwareLoadBalancer时也会执行DynamicServerListLoadBalancer的构造函数,进而会执行DynamicServerListLoadBalancer的restOfInit()方法;

在这里插入图片描述

2)DynamicServerListLoadBalancer#restOfInit()

在这里插入图片描述

restOfInit()中主要做两件事:

  1. 启动一个定时任务,定时更新服务实例列表。
  2. 初始化服务实例列表List<Server>,并感知服务实例信息的变更;

下面我们分开来看:

0> 初始化服务实例列表流程图

在这里插入图片描述

1> 初始化服务实例列表

updateListOfServers()方法负责初始化服务实例列表,代码执行流程如下:
在这里插入图片描述
最终进入到DiscoveryEnabledNIWSServerListobtainServersViaDiscovery()方法从Eureka Client本地缓存的服务注册表中获取到服务的全部实例信息;

我们debug过程中,知道了updateListOfServers()方法中涉及到的ServerList<T> serverListImpl是,那么serverListImpl是从哪来的?
在这里插入图片描述
==PS:如果使用Nacos作为服务注册中心,这里的ServerList是NacosServerList。==

ServerList从哪来的?

serverListImpl是和Eureka相关的,我们去找eureka相关的jar包,最终找到spring-cloud-netflix-eureka-client jar包,其中有个org.springframework.cloud.netflix.ribbon.eureka目录,目录中有个EurekaRibbonClientConfiguration类:

在这里插入图片描述

其中负责实例化ServerList<T>DomainExtractingServerList,然而我们调用DomainExtractingServerListgetUpdatedListOfServers()方法获取服务的所有实例时,实际是交给其组合的DiscoveryEnabledNIWSServerList类成员的getUpdatedListOfServers()方法去执行;

至此,我们知道了ZoneAwareLoadBalancer(属于ribbon)如何与eureka整合,通过eureka client获取到对应注册表?

  • 其实就是从eureka client里去获取一下注册表,然后更新到LoadBalancer中去。

2、 动态更新服务实例列表

1)流程图

在这里插入图片描述

2)流程解析

enableAndInitLearnNewServersFeature()方法负责定时更新服务实例列表,代码执行逻辑如下:
在这里插入图片描述
PollingServerListUpdater#start(UpdateAction)方法中会启动一个延迟1S并每间隔3S执行一次的定时任务去执行DynamicServerListLoadBalancer#doUpdate()方法去动态更新服务实例列表。

再看DynamicServerListLoadBalancer#doUpdate()方法:
在这里插入图片描述
方法内部其实就是调用初始化服务实例类别的那个方法:updateListOfServers(),也就是我们上面聊的。

至此,我们也就知道了ZoneAwareLoadBalancer如何持续从Eureka中获取最新的注册表信息?

3、 如何根据负载均衡规则从List<Server>中选出一个Server?

回到RibbonLoadBalancerClient#execute()方法中:
在这里插入图片描述
进入到getServer()方法,看如何选择一个服务的?
在这里插入图片描述

核心逻辑如下:

  1. 会进入到BaseLoadBalancer的chooseServer()方法中,用IRule来选择了一台服务器;
  2. IRule是RibbonClientConfiguraiton中实例化的ZoneAvoidanceRule,调用它的choose()方法来选择一个server,最终是用的ZoneAvoidanceRule的父类PredicateBasedRule#choose()方法:

    先执行过滤规则,过滤掉一批server,再根据我们自己指定的filter规则,然后用round robin轮询算法选择一个Server。

下面看看负载均衡的轮询算法是怎么做的?

1)轮询算法

在这里插入图片描述
在这里插入图片描述

轮询算法很简单,重点在于通过AtomicInteger原子类型变量 + 死循环 CAS操作实现,每次返回原子类型变量nextIndex的当前值,因为原子类型变量nextIndex可能超过服务实例数,所以每次对原子类型变量nextIndex赋值时,都会对其做取余运算。

4、如何发送网络HTTP请求?

1)流程图

在这里插入图片描述

2)流程解析

getServer()方法选择出一个服务实例之后,进入到execute()重载方法去执行HTTP请求:
在这里插入图片描述
代码整体执行流程如下:
在这里插入图片描述

流程说明:

  1. LoadBalancerInterceptor#intercept()方法中通过LoadBalancerRequestFactory工厂封装了HTTP请求为LoadBalancerRequest;
    在这里插入图片描述
  2. 在RibbonLoadBalancerClient的execute()方法中,调用了T returnVal = request.apply(serviceInstance);,进入到LoadBalancerRequest的apply()方法中,传入了选择出来的server,对这台server发起一个指定的一个请求。

    1. 将LoadBalancerRequest和server再次封装为了一个ServiceRequestWrapper;
    2. 然后通过ServiceRequestWrapper#getURI()方法,基于选择出来的server的地址,重构请求URI;即:将服务名替换为具体的IP:Port地址。
    3. 最后将将真正的请求URL交给spring-web下的负责底层的http请求的组件ClientHttpRequestExecution去执行,发起了一次真正的HTTP请求。

5、ping机制如何检查服务实例是否还存活?

RibbonClientConfiguration类中会注入IPing类型的实例DummyPing,其中isAlive()方法直接返回TRUE;

public class DummyPing extends AbstractLoadBalancerPing {

    public DummyPing() {
    }

    public boolean isAlive(Server server) {
        return true;
    }
}

这里是Ribbon默认的IPing,但是Eureka和Ribbon整合之后,EurekaRibbonClientConfiguration(spring-cloud-netflix-eureka-client包下)类中新定义了一个IPing。

@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
    if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
        return this.propertiesFactory.get(IPing.class, config, serviceId);
    }
    NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
    ping.initWithNiwsConfig(config);
    return ping;
}

IPing的实例为NIWSDiscoveryPing;NIWSDiscoveryPing的isAlive()方法会检查某个server对应的eureka client中的InstanceInfo的状态,看看服务实例的status是否还正常;

public boolean isAlive(Server server) {
    boolean isAlive = true;
    if (server!=null && server instanceof DiscoveryEnabledServer){
        DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
        // 获取服务实例信息                
        InstanceInfo instanceInfo = dServer.getInstanceInfo();
        if (instanceInfo!=null){    
           // 获取实例状态                
            InstanceStatus status = instanceInfo.getStatus();
            if (status!=null){
                // 服务实例状态是否为UP
                isAlive = status.equals(InstanceStatus.UP);
            }
        }
    }
    return isAlive;
}

1)哪里使用到了IPing的isAlive()方法?

在ZoneAwareLoadBalancer实例构造时(进入到父类BaseLoadBalancer中),会启动一个定时调度的任务,每隔10s,就用IPing组件对server list中的每个server都执行一下isAlive()方法,判断服务实例是否还存活。

public BaseLoadBalancer() {
    this.name = DEFAULT_NAME;
    this.ping = null;
    setRule(DEFAULT_RULE);
    // 启动一个每10s执行一次的定时任务,最IPing#isAlive()操作
    setupPingTask();
    lbStats = new LoadBalancerStats(DEFAULT_NAME);
}

void setupPingTask() {
    if (canSkipPing()) {
        return;
    }
    if (lbTimer != null) {
        lbTimer.cancel();
    }
    lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
                                       true);
    // 执行BaseLoadBalancer的内部类PingTask#run(),默认每10s执行一次
    lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
    // 快速进行一次IPing
    forceQuickPing();
}

class PingTask extends TimerTask {
    public void run() {
        try {
            new Pinger(pingStrategy).runPinger();
        } catch (Exception e) {
            logger.error("LoadBalancer [{}]: Error pinging", name, e);
        }
    }
}

进入到PingTask#run()方法之后,下面看一下Pinger(pingStrategy).runPinger()的执行流程:
在这里插入图片描述
注意看canSkipPing()方法:

private boolean canSkipPing() {
    if (ping == null
            || ping.getClass().getName().equals(DummyPing.class.getName())) {
        // default ping, no need to set up timer
        return true;
    } else {
        return false;
    }
}

默认IPing的实现是DummyPing,在启动做Ping的Timer会通过BaseLoadBalancer#canSkipPing()方法判断是否要跳过Timer的启动,当ping为空或者为DummyPing是跳过,所以默认不会启动Timer去每10s做一次IPing操作;然而Eureka和Ribbon整合之后,EurekaRibbonClientConfiguration(spring-cloud-netflix-eureka-client包下)类中新定义了一个IPing(NIWSDiscoveryPing),此时会启动Timer每10s做一次ping操作。

三、总结 以及 后续文章

到这里针对Ribbon的整个执行流程我们也就讨论完了,大体执行流程图下:
在这里插入图片描述

后文章,我们将讨论Ribbon内置的那些负载均衡算法是如何实现的?

相关实践学习
小试牛刀,一键部署电商商城
SAE 仅需一键,极速部署一个微服务电商商城,体验 Serverless 带给您的全托管体验,一起来部署吧!
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
2月前
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
38 0
|
2月前
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
在微服务架构中,随着业务复杂度增加,项目可能需要调用多个微服务。为避免使用`@Value`注解逐一引入配置的繁琐,可通过定义配置类(如`MicroServiceUrl`)并结合`@ConfigurationProperties`注解实现批量管理。此方法需在配置文件中设置微服务地址(如订单、用户、购物车服务),并通过`@Component`将配置类纳入Spring容器。最后,在Controller中通过`@Resource`注入配置类即可便捷使用,提升代码可维护性。
43 0
|
4月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
178 7
|
6月前
|
存储 Prometheus 运维
在云原生环境中,阿里云ARMS与Prometheus的集成提供了强大的应用实时监控解决方案
在云原生环境中,阿里云ARMS与Prometheus的集成提供了强大的应用实时监控解决方案。该集成结合了ARMS的基础设施监控能力和Prometheus的灵活配置及社区支持,实现了全面、精准的系统状态、性能和错误监控,提升了应用的稳定性和管理效率。通过统一的数据视图和高级查询功能,帮助企业有效应对云原生挑战,促进业务的持续发展。
175 3
|
7月前
|
人工智能 自然语言处理 关系型数据库
阿里云云原生数据仓库 AnalyticDB PostgreSQL 版已完成和开源LLMOps平台Dify官方集成
近日,阿里云云原生数据仓库 AnalyticDB PostgreSQL 版已完成和开源LLMOps平台Dify官方集成。
|
7月前
|
存储 数据采集 分布式计算
Hadoop-17 Flume 介绍与环境配置 实机云服务器测试 分布式日志信息收集 海量数据 实时采集引擎 Source Channel Sink 串行复制负载均衡
Hadoop-17 Flume 介绍与环境配置 实机云服务器测试 分布式日志信息收集 海量数据 实时采集引擎 Source Channel Sink 串行复制负载均衡
117 1
|
8月前
|
运维 Cloud Native Devops
云原生时代的DevOps实践:自动化、持续集成与持续部署
【9月更文挑战第3天】未来,随着人工智能、大数据等技术的不断融入,DevOps实践将更加智能化和自动化。我们将看到更多创新的技术和工具涌现出来,为软件开发和运维带来更多便利和效益。同时,跨团队协作和集成也将得到进一步加强,推动软件开发向更加高效、可靠和灵活的方向发展。
|
10月前
|
Kubernetes Cloud Native 持续交付
云原生架构的核心组成部分通常包括容器化(如Docker)、容器编排(如Kubernetes)、微服务架构、服务网格、持续集成/持续部署(CI/CD)、自动化运维(如Prometheus监控和Grafana可视化)等。
云原生架构的核心组成部分通常包括容器化(如Docker)、容器编排(如Kubernetes)、微服务架构、服务网格、持续集成/持续部署(CI/CD)、自动化运维(如Prometheus监控和Grafana可视化)等。
|
11月前
|
负载均衡 算法 Java
Spring Cloud Netflix 之 Ribbon
Spring Cloud Netflix Ribbon是客户端负载均衡器,用于在微服务架构中分发请求。它与RestTemplate结合,自动在服务发现(如Eureka)注册的服务之间进行调用。配置包括在pom.xml中添加依赖,设置application.yml以连接Eureka服务器,并在配置类中创建@LoadBalanced的RestTemplate。通过这种方式,当调用如`/user/userInfoList`的接口时,Ribbon会自动处理到多个可用服务实例的负载均衡。
|
10月前
|
SQL Cloud Native 关系型数据库
云原生数据仓库使用问题之实例查询变慢该如何排查
阿里云AnalyticDB提供了全面的数据导入、查询分析、数据管理、运维监控等功能,并通过扩展功能支持与AI平台集成、跨地域复制与联邦查询等高级应用场景,为企业构建实时、高效、可扩展的数据仓库解决方案。以下是对AnalyticDB产品使用合集的概述,包括数据导入、查询分析、数据管理、运维监控、扩展功能等方面。