SpringCloud:注册中心,负载均衡两个组件

476 阅读19分钟

SpringCloud 微服务

认识微服务

什么是单体架构

  • 将所有的功能集中在一个项目开发,打成一个包部署

单体架构有什么优点

  • 架构简单
  • 部署成本低

单体架构有什么缺点

  • 耦合度高

什么是分布式架构

  • 根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务

分布式架构有什么优点

  • 耦合度低
  • 有利于服务的升级拓展

分布式架构有什么缺点

  • 架构复杂
  • 运维、监控、部署难度提高

分布式架构需要考虑什么问题

  • 服务拆分成什么粒度
  • 服务集群地址如何维护
  • 服务之间如何远程调用
  • 服务健康状态如何感知
  • 2022/05/16:目前最好的解决方法就是使用微服务架构

什么是微服务架构

  • 微服务是一种经过良好架构设计的分布式架构方案

微服务有什么特点

  • 单一职责:微服务拆分力度更小,每个服务对应一个唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

服务拆分及远程调用

服务拆分需要注意什么

  • 不同的微服务,不能开发重复的业务
  • 不同微服务应该有自己独立的数据库。微服务数据库相互独立,不能访问其他微服务的数据库
  • 需要将自己的服务(提供的功能)暴露成接口,提供给其他微服务调用

什么是远程调用

  • 微服务A向微服务B发送http请求(类似于浏览器发送请求到微服务B)来获取自己所需要的服务。

如何实现远程调用

  • RestTemplate类注入spring容器当中,通过调用RestTemplategetForObject方法发送get请求,在方法内传入被调用微服务的url 和返回的数据类型来获取数据。

  • 注入 spring 容器中

    /**
     * 创建 RestTemplate 并注入 spring 容器。
     * @return RestTemplate
     */
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
  • 调用getForObject方法发送get请求

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        // 2. 利用 RestTemplate 发送 http 请求到对应的微服务,并定义返回的数据类型。
        String url = "http://localhost:8081/user/" + order.getUserId();
        User user = restTemplate.getForObject(url, User.class);
        // 3. 封装 User 到 Order 中。
        order.setUser(user);
        // 4.返回
        return order;
    }
    

    image-20220516215202298

什么是提供者和消费者

  • 服务提供者:在一次业务中,被其他微服务调用的的服务(提供接口给其他服务)
  • 服务消费者:在一次业务中,调用其他微服务的服务。(调用其他微服务提供的接口)

如何判别提供者和消费者

  • 根据一次业务中的实际情况来判断提供者和消费者
  • 提供者和消费者是相对的。抛开业务不谈,一个服务既可以是提供者也可以是消费者。

服务和实例的区别是什么?

  • 服务包含了实例。一个服务可以有多个实例。每个实力的端口都是不一样的。如果不能理解,可以把服务比作是类,实例比作是实例。

Eureka 注册中心

硬编码服务调用会出现什么问题

  • 消费者不知如何获取服务提供者地址信息
  • 多个提供者不知如何选择
  • 无法了解提供者健康状态

Eureka 架构中微服务分为哪两类

  • EurekaServer 服务端,注册中心
  • EurekaClient 客户端

EurekaServer EurekaClient 关系

  • image-20220516235218172

EurakaServer 有什么作用

  • 记录每个微服务的服务信息,如访问功能的接口路径。
  • 心跳监控,监控提供者微服务的健康状态。

EurekaClient 微服务分类和作用是什么

  • EurakeClient 微服务分为消费者和提供者。
  • 提供者
    • 提供者会注册自己的信息到 EurekaServer (将自己的服务信息给 EurekaServer
    • 提供者会每个30秒向EurekaServer发送心跳(想 EurekaServer 发送自己健康状态),不发送视为心跳不正常,信息会被剔除
  • 消费者
    • 消费者会根据服务名称从EurekaServer中拉去服务列表
    • 获取服务列表后做负载均衡,选中一个微服务后发起远程调用。

消费者如何获取服务提供者的具体信息

  • 服务提供者在启动时会像 Eureka 注册自己的信息。Eureka 保存这些信息。消费者根据服务名称像 Eureka 拉去提供者信息。

如果多个服务提供者,消费者如何选择

  • 消费者利用负载均衡算法,从服务列表中挑选一个

消费者如何感知提供者的健康状态

  • 提供者每隔30秒像 EurekaServer 发送心跳请求,报告健康状态。Eureka 会跟新服务列表信息,心跳不正常会被剔除。消费者就可以拉取到最新的信息。

消费者是否每次请求都向Eureka拉取服务

  • 不是的。如果每次远程调用都需要向Eureka拉取服务,那么就会对Eureka造成过大的压力。所以消费者会像Eureka定时拉提供者信息并放入服务列表缓存内。这样既即节约时间,也降低Eureka压力

如何搭建Eureka服务端(EurekaServer

  • 导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
  • 配置 Eureka

    端口、服务名称、地址信息可以根据自己的实际需求去进行修改

    server:
      # 注册端口是因为 eureka 自己也是微服务
      port: 10086
    
    # 这里是作为 eureka 微服务的注册,为了以后多个 eureka 微服务能互相通信
    spring:
      application:
        # 配置的是 eureka的服务名称
        name: eureka-server
    eureka:
      client:
        service-url:
          # 配置的是 eureka 的地址信息
          defaultZone: http://127.0.0.1:10086/eureka
    
  • 开启Eureka微服务

    在引导类上添加注解@EnableEurekaServer

    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServer {
        public static void main(String[] args) {
            SpringApplication.run(EnableEurekaServer.class, args);
        }
    
    }
    
  • 通过配置的地址跳转到对应的页面说明成功

    image-20220516233931679

EurekaServer 初始化会注册自己吗

  • 会的,EurekaServer 会注册自己。

    从下图可以看出,在初始话的时候会把自己注册到Eureka当中。

    image-20220516234357358

如何创建 Eureka 客户端(EurekaClient

  • 引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  • 设置配置

    # 这里是作为 eureka-client 的注册
    spring:
      application:
        # 配置的是 eureka的服务名称
        name: user-Service
    eureka:
      client:
        service-url:
          # 根据 url 注册到对应的 EurekaServer
          defaultZone: http://127.0.0.1:10086/eureka
    
  • 当我们在EurekaServe服务端看到对应的名称的服务已经完成注册即完成

    image-20220517110500467

提供者和消费者在 Eureka 中担任什么角色

  • 提供者和消费者在 Eureka 中都是 EurekaClient,都是属于客户端。
  • 只有 EurekaServer 是客户端,用来注册和监控以及查询的作用。

idea 一个服务如何启动多个实例

Eureka 如何体现服务队列

  • 通过服务名称来区分服务队列。image-20220517164921961

若干个相同服务名称不同功能的客户端服务在同一个服务端会怎么样

  • 他们会因为服务名称相同所以在同一个服务端以服务队列的形式体现。

  • 我们从此得出一个结论,服务队列的分配依据之一是根据客户端的服务名称来完成。

    image-20220517111820302

什么是服务的发现

  • 服务的发现也可以被称为服务的拉取,是消费者向 EurekaServer 拉去对应服务名称的提供者的信息。

如何实现服务的发现

  • url ip路径换为要拉取的提供者者的服务名称。

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        // 2. 利用 RestTemplate 发送 http 请求到对应的 微服务中
        String url = "http://user-service/user/" + order.getUserId();
        User user = restTemplate.getForObject(url, User.class);
        // 3. 封装 User 到 Order 中。
        order.setUser(user);
        // 4.返回
        return order;
    }
    

    image-20220517170656085

    image-20220517170739843

  • RestTemplate类上添加负载均衡注解@LoadBalanced

    /**
     * 创建 RestTemplate 并注入 spring 容器。
     * @return RestTemplate
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
  • 这样子就可以实现服务的发现。默认的负载均衡是两个提供者服务器轮询服务。

Eureka 服务分级存储模型是什么

  • Eureka注册器的存储模型是两层,第一层是服务,第二层是实例。一个服务可以包含一个或多个的实例。

总结 Eureka 总体流程

  • 搭建 Eureka Server

    • 引入 eureka-server 依赖

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </dependency>
      
    • 添加 @EnableEurekaServer注解

      @SpringBootApplication
      @EnableEurekaServer
      public class EurekaApplication {
          public static void main(String[] args) {
              SpringApplication.run(EurekaApplication.class, args);
          }
      }
      

      image-20220517171553932

    • 在配置文件中配置 eureka 地址

      # 这里是作为 eurekaServer 微服务的注册,为了以后多个 eureka 微服务能互相通信
      spring:
        application:
          # 配置的是 eureka的服务名称
          name: eureka-server
      eureka:
        client:
          service-url:
            # 配置的是 eureka 的地址信息
            defaultZone: http://127.0.0.1:10086/eureka
      
  • 服务注册

    • 引入 eureka-client 依赖

      <!--Eureka客户端-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      
    • 在配置文件中配置服务名称和 eureka 服务器地址

      # 配置服务名称
      spring:
        application:
          name: order-service
      # 配置 eureka 服务器地址    
      eureka:
        client:
          service-url:
            # 根据 url 注册到对应的 EurekaServer
            defaultZone: http://127.0.0.1:10086/eureka
      
  • 服务发现

    • 在提供者和消费者都已注册

    • 消费者通过服务提供者的服务名称远程调用

      通过 url 调用restTemplate.getForObject方法去远程调用

      public Order queryOrderById(Long orderId) {
          // 1.查询订单
          Order order = orderMapper.findById(orderId);
          // 2. 利用 RestTemplate 发送 http 请求到对应的 微服务中
          String url = "http://user-service/user/" + order.getUserId();
          User user = restTemplate.getForObject(url, User.class);
          // 3. 封装 User 到 Order 中。
          order.setUser(user);
          // 4.返回
          return order;
      }
      

      image-20220517171924997

    • RestTemplate类上添加负载均衡注解@LoadBalanced

      /**
       * 创建 RestTemplate 并注入 spring 容器。
       * @return RestTemplate
       */
      @Bean
      @LoadBalanced
      public RestTemplate restTemplate() {
          return new RestTemplate();
      }
      

Ribbon 负载均衡

负载均衡在微服务中起到了什么作用

  • 为消费者向注册器拉去提供者信息并根据负载均衡算法选择一个进行远程调用

    image-20220517175023642

负载均衡如何在微服务里面实现

  • 这是详解图,下面就让我们一起来看看如何实现

    image-20220517202825171

  1. 首先 LoadBalancerInterceptor类获取到消费者传入的uri,通过 uri获取到提供者的服务名称。将服务名称传入RibbonLoadBalancerClient类当中。

    image-20220517211040571

  2. RibbonLoadBalancerClient类获取到了服务名称,便向 Eureka注册器拉取对应提供者的服务列表,然后通过getServer传入ZoneAwareLoadBalancer类中。

    image-20220517205018391

    image-20220517200551151

  3. ZoneAwareLoadBalancer获取到服务列表之后,将其传至BaseLoadBalancer类中进行负载均衡选择。

    image-20220517205208152

  4. BaseLoadBalancer类中通过 IRule类型的 rule选择了对应的负载均衡规则并返回。

    image-20220517205556787

    image-20220517202357694

  5. 最后返回到LoadBalancerInterceptor类,修改url,拿到真实的ip地址和端口号来代替原来的服务名称,并对提供者发起请求。

负载均衡的用策略有哪些

  • 常用策略如下:

    内置负载均衡规则类规则描述
    RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
    AvailabilityFilteringRule对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。
    WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
    ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
    BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。
    RandomRule随机选择一个可用的服务器。
    RetryRule重试机制的选择逻辑
  • image-20220517212609386

如何自定义负载均衡的策略

  • 通过代码的方式进行调整

    通过配置类设置全局的负载均衡规则

    @Bean
    public IRule iRule() {
        // 根据自己的实际选择返回对应的负载均衡策略
        return new RoundRobinRule();
    }
    
  • 通过配置文件的方式进行调整

    通过配置文件设置单一服务的负载均衡规则

    # 服务名称
    userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 
    

代码实现和配置文件实现的自定义负载均衡策略有什么区别

  • 代码实现的负载均衡策略作用域是全体服务,即消费者调用任何服务
  • 代码方式配置灵活,但修改时需要重新打包发布
  • 配置文件的负载均衡策略作用域是指定服务,指消费者调用特定服务
  • 配置方式直观方便,无需重新打包发布,但是无法做到全局配置
  • 当两者共存的时候,必定会有冲突。所有的策略都是按照代码实现的负载均衡策略来实现。

Ribbon负载均衡的默认加载机制是什么

  • Ribbon默认的加载机制是懒加载,即当服务第一次访问的时候才会去创建LoadBalanceClient,请求时间长。

什么是饥饿加载

  • 饥饿加载不同于Ribbon默认的懒加载。当服务启动的时候便会去创建LoadBalanceClient,缩短第一次请求时间,但是增加设备负担。

如何开启饥饿加载

  • 通过配置配置文件开启指定服务名称的饥饿加载

    ribbon:
      eager-load:
        enabled: true # 开启饥饿加载
        clients:
          - userservice # 指定服务名称,这是个服务名称列表
    

Nacos 注册中心

如何启动Nacos

  • win10控制台进入Nacos安装地址的的bin,输入startup.cmd -m standalone

    startup.cmd -m standalone
    

为什么要学Nacos

  • Nacos是阿里巴巴的产品,在国内受欢迎程度比较高。而 Eureka也不是因为版本太老或者有重大缺陷所以不继续使用,而是相较于Nacos而言,使用人数还是有些差别。

Springcloud如何添加Nacos依赖

  • 客户端注入依赖

    <!-- Nacos客户端依赖包 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-Nacos-discovery</artifactId>
    </dependency>
    
  • 客户端配置

    spring:
      cloud:
        Nacos:
          # Nacos 服务地址
          server-addr: localhost:8848
    

    Nacos服务地址根据实际情况来调整

    image-20220518142235751

  • 结果

    当完成上述的步骤之后,就把对应的服务绑定在对应端口的Nacos注册器中。我们启动已绑定的服务,可以通过访问Nacos地址http://192.168.153.129:8848/Nacos/index.html来查看绑定服务名称和端口是否呈现

    image-20220518142756253

Nacos服务分级存储模型是什么

  • Nacos服务分级存储模型分为三层,第一层是服务,第二层是集群,第三层是实例。一个服务可以包含多个集群,一个集群可以包含多个实例。

    image-20220518192810903

Nacos为什么添加集群的概念

  • 尽可能避免跨集群调用
  • 服务调用尽可能选择本地集群的服务,跨集群调用延迟较高本地集群不可访问时,再去访问其它集群

什么是跨集群调用

  • 杭州的消费者跨集群去调用上海的提供者便是跨集群调用。大白话就是陀地不要偏要北姑。

    image-20220518193127385

如何给服务配置集群

  • 给服务配置集群,根据服务实例启动时配置文件内的集群名称来将服务划分到对应的集群。

    spring:
      cloud:
        Nacos:
          server-addr: localhost:8848
          discovery:
          	 # 配置集群名称,将服务配置到对应集群
            cluster-name: HZ
    
  • 完成配置后重启服务实例。

  • 重启后可以通过Nacos管理页面看到同一个服务的不同实例分配至不同的集群

    image-20220518205259538

**注意:**实例的分配的集群是服务实例启动的时候分配的。如果在一台设备上操作,打算将一个服务的不同实例分配至不同的集群,需要在每次启动服务实例的时候修改集群名称

NacosRule负载均衡策略是什么

  • 优先调用同集群服务实例列表。

  • 本地集群找不到提供者,才会去其他集群寻找,并且会报敬告

    image-20220518210222460

  • 确定了可用实例列表后,再采用随机负载均衡挑选实例

Nacos如何设置权重

  • 在同一集群下的不同实例对应的服务器可能有所不同,服务器设备性能有差异,部分机器性能较好,另一些较差,性能好的承担更多的用户请求。

    我们通过Nacos管理页面中对同一集群的不同实例的权重进行调整。权重从1~01为必定可以被调用,0.5为50%几率被调用,0为绝对不被调用。

    image-20220518222819108

    image-20220518222909719

Nacos有什么实践场景

  • 可以让服务平滑升级。

    当服务实例a要升级的时候,逐步下降他的权重至0,让服务分流到其他实例。升级完毕之后,逐步上升实例a的权重,可以进行少量的服务进行测试。最后将其的权重调整到适合的比例。

    这么做可以让用户不会感觉到服务的变化,对用户体验没有太大的影响。也能平滑的升级服务。

什么是命名空间(Namespace

  • 命名空间也称为Namespace,它用于在多环境下配置管理和服务隔离。在不同的命名空间下,可以存在相同过的 GroupData ID的配置。Namespace常用的场景之一就是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。使用一套 Nacos注册中心去管理多套不同的环境服务器,呈现出了Nacos一个平台的概念。

如何创建命名空间(Namespace

  • 我们可以在Nacos管理页面中创建命名空间

    image-20220518230835151

如何将实例放入对应的命名空间中

  • 将对应命名空间的命名空间 ID配置到对应服务的配置中

    image-20220518231100499

    spring:
      cloud:
        Nacos:
          discovery:
            # 命名空间 ID
            namespace: 0904c371-193a-4cd9-badc-3781e91a0bbe
    

    修改配置之后重启服务。

  • 我们可以通过Nacos管理页面查看到对应的服务已转移

    image-20220518231516034

如果不配置命名空间会怎样

  • 如果不配置命名空间的话,所有的服务默认放在 public命名空间当中

不同命名空间的服务能相互调用吗?

  • 不能! 不能! 不能!命名空间就是为了多环境下的服务和资源隔离的!

消费者是否每次请求都向Nacos拉取服务

  • 不是的。如果每次远程调用都需要向Nacos拉取服务,那么就会对Nacos造成过大的压力。所以消费者会像Nacos定时拉提供者信息并放入服务列表缓存内。这样既即节约时间,也降低Nacos压力

Nacos客户端服务实例如何如何区分

  • Nacos的客户端服务分为临时实例和非临时实例。我们可以通过Nacos管理页面查看

    image-20220519171030742

临时实例和非临时实例有什么区别

  • 当服务实例作为提供者的时候,临时实例采取的是心跳监控策略。每隔一段时间临时实例就会向Nacos汇报心跳。当心跳停止的时候,Nacos就会认为此实例出现异常,直接剔除出服务列表。
  • 当服务实例作为提供者的时候,非临时实例采取的是主动询问策略。每隔一段时间Nacos便会询问非临时实例的状态。当非临时实例状态不好的时候,Nacos会认为此实例出现异常,但不会剔除出服务列表,而是继续主动询问,至实例状态恢复或手动在Nacos管理页面删除服务。
  • 可以看出,临时实例像别人家的孩子,非临时实例像自家孩子。Nacos拟人化了。

Nacos如何设置服务为非临时服务

  • Nacos默认服务为临时服务,如果想将服务设置成非临时服务只需要修改服务配置即可。

    spring:
      cloud:
        nacos:
          discovery:
            # true设置为临时服务   false设置为非临时服务
            ephemeral: false
    

Nacos是否会主动推送信息给消费者

  • 会的。Nacos也考虑到了消费者会定期向Nacos服务端拉取服务并存入服务列表缓存。但是可能会出现在拉取的间隙提供者状态发生改变,从而导致消费者远程调用服务的时候出现异常。所以Nacos服务端提供了一种服务。每当服务的状态发生了改变的时候,就会为该服务的消费者主动推送变更信息。

NacosEureka之间有什么异同

  • Nacos相较于Eureka而言,有着更多的功能,有着更好的生态。但是两者之间的使用并没有太大的差距。可以实现无缝切换。若是日后工作中使用的是Eureka注册器,可以尝试推荐使用Nacos来代替。
  • 区别
    • 两者对于微服务提供注册这个功能而言没有太大的区别,但是两者使用的依赖和配置是有所差异的。
    • 两者服务分级存储模型有所不同,Eureka是两级,分别是:服务和实例。Nacos是三级,分别是:服务,集群和实例。
    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
  • 共同点
    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测