SpringCloud笔记

一、SpringCloud初阶篇

1、从面试题开始

1.1什么是微服务?

1.2微服务之间是如何独立通讯的?

1.3SpringCloud和Dubbo有哪些区别?

1.4通信机制:Dubbo是通过RPC远程过程调用,微服务Cloud是基于rest调用

1.5SpringBoot和SpringCloud,请你谈谈对他们的理解?

SpringBoot专注于快速方便的开发单个个体微服务。

SpringCloud是关注全局的微服务协调整理治理框架,他将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局所、决策竞选、分布式会话等等集成服务

SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系。

总结:SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注与全局的微服务治理框架。

1.6什么是服务熔断?什么是服务降级?

1.7微服务的优缺点分别是什么?说下你在项目开发中碰到坑?

1.8你所知道的微服务技术栈有哪些?请列举一二

1.9Eureka和Zookeeper都可以提供服务注册与发现的功能,请说说两个的区别?

2、微服务概述

2.1 是什么?

微服务架构是一种架构模式或者说是一种架构风格,他提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通(Double是通过RPC远程通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以拥有自己独立的数据库。

通俗的讲:

微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个无服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够单独启动或销毁,拥有自己独立的数据库。

2.2微服务与微服务架构

  • 微服务

    微服务是一种软件开发方法,他将一个大型应用程序分解为许多小型、独立的服务。每个服务负责解决特定的问题或执行特定的工能。这些服务可以单独部署、维护和更新,而不影响其他服务。它具有可伸缩性、可维护性、可组合性等特点。通常采用轻量级通信机制,如HTTP REST API 或消息队列进行通信。

  • 微服务架构

    微服务架构是一种架构模式,它基于微服务的原则来设计和实现大型应用程序。在微服务架构中,应用程序有多个独立的服务组成,这些服务可以按照业务功能、领域或团队进行划分。每个服务可以独立地开发、部署和扩展,他们通过轻量级协议进行通信和协作。

2.3微服务优缺点

  • 优点

    模块化:微服务架构将一个大的应用分为多个小的服务,每个服务都专注于完成一项任务,这种模块化使得微服务更容易理解和维护。

    灵活性:微服务架构使用REST API与服务之间进行交互和通信,这使得微服务之间的协作更加灵活,可以根据需求选择不同的编程语言和框架来编写服务。

    持续部署:微服务架构允许持续部署独立的服务,这样就可以更加频繁地发布新功能,同时也更容易进行灰度发布。

    隔离性:微服务之间通过REST API相互隔离,这样即使其中一个服务发生故障,也不会影响其他服务。

  • 缺点

    复杂性:由于系统被分解成许多微服务,在开发和测试阶段容易发生依赖冲突,这会增加项目的复杂性

    运维难度:微服务架构使得技术栈更加庞大复杂,运维工程师需要监控更多的服务,这增加了运维的难度

    通信开销:微服务之间通过网络进行通信,这必然带来一定的性能开销,特别是高并发场景下会更加明显

    数据一致性:微服务架构下,不同的服务有各自的数据库,这可能导致数据一致性问题,需要借助分布式事务来解决

    重复开发:微服务架构可能会有许多重复的业务逻辑在不同的服务中实现,这增加了开发工作量

2.4微服务技术栈有哪些

image-20230904202538667

微服务条目落地技术
服务发现用于服务注册和发现,比较常用的有 Eureka、Consul、Zookeeper等
服务调用用于服务之间的调用,常用的有REST、RPC、gRPC等。其中RPC框架有Dubbo、Thrift、SpringCloud OpenFeign等。
负载均衡用于服务调用的负载均衡,常用的有Ribbon、Nginx等
服务网关用于同一 entrance,常用的有Zull、Gateway
配置中心用于集中管理配置,常用的有Spring Cloud Config、Apollo、Archaius、阿里的Diamond
熔断和降级用于保护服务不被过载,常用的有 Hystrix、Resilience4j、Envoy
服务监控用于服务监控,常用的有 SpringBoot Admin、Prometheus、Zipkin
分布式消息队列用于服务解耦和异步通信,常用的有RabbitMQ、Kafka、RocketMQ
API文档用于同一管理API文档,常用的有Swagger、SpringFox
分布式日志用于同一管理日志,常用的有ELK
持续集成用于自动化构建和部署,常用的有 Jenkins、GitLab CI
容器化部署用于快速高效的部署和扩展,常用的有 Docker、Kubernetes

2.5为什么选择SpringCloud作为微服务架构

  1. SpringCloud是Spring旗下的项目,与SpringBoot、SpringData等其他Spring项目完美整合,开发体验好。
  2. SpringCloud项目涵盖了微服务开发中的各个方面,如:服务注册与发现(Eureka、Consul)、负载均衡(Ribbon)、熔断器(Hystrix)、配置中心(Config)、API网关(Zuul)等,组件丰富,使用方便。
  3. SpringCloud项目基于Netflix的开源项目进行二次开发和封装,具有Netflix丰富的经验
  4. 社区活跃,发展迅速,各个组件不断更新迭代
  5. SpringCloud与Docker、Kubernetes等容器技术相结合,可以更容易部署和运维微服务应用
  6. SpringCloud提供了一套完整的微服务解决方案,从开发到部署forming了一条令服务,简化了微服务架构落地的难度

3、SpringCloud入门概述

3.1是什么

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SpringCloud,基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于Netflix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。

SpringCloud利用SpringBoot的开发遍历性巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括**配置管理、服务发现、断路器、路由、微代理、时间总线、全局锁、决策竞选、分布式会话**等等,它们都可以用SpringBoot的开发风格做到一建启动和部署。

一句话就是:SpringCloud=分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。

3.2能干嘛

3.3去哪下

官方网址

参考书:

API说明

SpringCloud中国社区

SpringCloud中文网

4、Rest微服务构建案例工程模块

4.1RestTemplate

RestTemplate是Spring中的一个HTTP客户端,用于简化RESTful Web Service的调用。他提供了一组易于使用的方法,可以发送HTTP请求并处理HTTP响应。使用RestTemplate,可以使用HTTP协议发送GET、POST、PUT、DELETE等请求,并将响应映射为Java对象。

RestTemplate提供了多种方法来发送HTTP请求,例如:

  • getForEntity()和getForObject()方法用于发送HTTP GET请求,并将响应映射为Java对象。
  • postForEntity()和postForObject()方法用于发送HTTP POST请求,并将请求体和响应映射为java对象
  • put() 和 delete() 方法用于发送HTTP PUT和DELETE请求。

RestTemplate还提供了一些其他的方法,例如:

  • exchange()方法用于发送任意类型的HTTP请求,并允许自定义请求头和请求体
  • execute()方法用于发送任意类型的HTTP请求,并允许自定义请求头、请求体和响应处理器

RestTemplate官网

使用restTemplate访问restful接口非常简单,(URL,requsetMap,ResponseBean.class)这三个参数分别代表REST请求地址、请求参数、HTTP响应转换被转换成的对象类型

5、Eureka服务注册与发现

5.1 概念

Eureka是Netflix的一个子模块,也是核心模块之一,现在已经成为了SpringCloud等微服务核心组件之一。Eureka的核心原则是基于REST的架构和AP(可用性和分区容错性)设计理念。Eureka通过心跳机制来检测服务实例的存活状态,并通过注册中心来管理服务实例的注册和发现。当一个服务实例出现故障或者下线时,Eureka会自动将其从注册中心中删除,并通知其他的服务实例重新选择可用的服务实例来进行负载均衡。

5.2 原理

5.2.1Eureka的基本架构

Eureka主要有两个组件组成:Eureka Server 和 Eureka Client。Eureka Server是服务注册中心,用于管理服务实例的注册和发现;而Eureka Client 是服务提供者或服务消费者,用于向Eureka Server注册自己,并从Eureka Server获取可用的服务实例信息。

  • Eureka Server原理

    Eureka Server采用了AP设计理念,即可用性和分区容错性。在Eureka Server中,有两个重要的组件:Registry 和 Peer

    • Registry:服务注册表,用于存储服务实例的注册信息,包括服务名、IP地址、端口号、健康状态等。
    • Peer:服务注册中心的集群节点,用于实现服务注册中心的高可用性。

    Eureka Server 在启动时,会像Peer结点发送心跳请求,已加入到服务注册中心的集群中。当服务实例启动时,它会向Eureka Server发送注册请求,Eureka Server会将服务实例的注册信息存储到Registry中。当服务实例出现故障或下线时,它会向Eureka Server发送取消注册请求,Eureka Server会将服务实例的注册信息从Registry中删除。

  • Eureka Client原理

    Eureka Client 是服务提供或服务消费者,它用于向Eureka Server注册自己,并从Eureka Server获取可用的服务实例信息。在Eureka Client中,有两个重要的组件:DiscoveryClient 和 Heartbeat。

    • DiscovertClient:服务发现客户端,用于从Eureka Server获取可用的服务实例信息。
    • Heartbeat:心跳机制,用于定期向Eureka Server 发送心跳请求,以保证服务实例的健康状态。

    当服务实例启动时,它会向Eureka Server发送注册请求,Eureka Server会将服务实例的注册信息存储到Registry中。同时,服务实例会定期向Eureka Server发送心跳请求,以保证自己的健康状态。当服务实例出现故障或下线时,它会向Eureka Server发送取消注册请求,Eureka Server会将服务实例的注册信息从Registry中删除。

    当服务消费者需要调用某个服务时,它会向Eureka Server发送查询请求,以获取可用的服务实例列表。Eureka Client会从获取到的服务实例列表中选择一个可用的服务实例,并通过负载均衡算法进行负载均衡。如果选择的服务实例出现故障或下线,Eureka Client会从服务实例列表中选择另一个可用的服务实例。

5.2.2 三大角色
  • Eureka Server 为客户端提供服务注册和发现
  • Service Provides 作为服务提供方,向Eureka Server注册自己提供的服务。
  • Service Consumers 作为服务消费方,从Eureka Server获取服务列表,然后去调用服务。

Eureka Server:提供服务注册和发现的功能。客户端向Eureka Server注册自己提供的服务,然后也从Eureka Server获取其他服务的信息。

Service Provider:就是提供各种服务的微服务。Service Provider在启动时,会向Eureka Server注册自己,告知Eureka我提供了什么服务。

Service Consumer:服务消费者。需要调用其他服务的微服务。服务消费者在启动时,也会从Eureka Server获取所有已注册的服务,然后根据需要去调用服务。

image-20230720214644462

5.3 构建步骤

5.3.1 服务注册中心

pom.xml 文件:

<!-- Eureka Server服务端 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>

application.yml 文件:

server:
  port: 7001

eureka:
  instance:
    hostname: localhost # Eureka服务端的实例名称
  client: # false表示不向注册中心注册自己
    register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # Eureka 客户端的默认注册中心地址,设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址

主启动类:

@SpringBootApplication
@EnableEurekaServer // 启用 Eureka Server 的功能,接收其他微服务注册进来。
public class EurekaServer7001_App {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001_App.class,args);
    }
}

启动服务后,浏览器访问localhost:7001 如果出现下图则说明成功:

image-20230720223504219

5.3.2 将已有服务注册进Eureka注册中心

pom.xml 文件:

<!-- 将微服务provider侧注册进eureka -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

application.yml 文件

eureka:
  client: # 客户端注册进eureka服务列表内
    service-url:
      defaultZone: http://localhost:7001/eureka

主启动类:

@SpringBootApplication
@EnableEurekaClient // 启用 Eureka Client 的功能。本服务启动后会自动注册进Eureka服务中
public class DeptProvider8001_App {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider8001_App.class,args);
    }
}

启动服务端,再启动客户端,浏览器发送localhost:7007请求,如下图:

可以看到有一个服务注册进来了,而这个服务的名称正是注册的微服务的应用名称即对外暴露的微服务名称。

image-20230720230612094

5.3.3 Actuator与注册微服务信息完善
  1. 配置服务实例信息

在客户端pom文件中加入一下配置:

eureka:  
	instance:
    instance-id: microservicecloud-dept8001 # 自定义服务名名称信息
    prefer-ip-address: true     # 访问路径可以显示IP地址

image-20230722204031615

其他实例配置:

  • instanceId:服务实例的唯一标识符,通常由主机名、IP地址、端口号等组成
  • hostName:服务市里的主机名
  • appname:服务实例所属的应用名称
  • ip-address:服务实例的IP地址
  • secure-port:服务实例的安全端口号
  • initial-status:服务实例的状态,UP(运行中)、DOWN(不可用)、STARTING(正在启动)、OUT_OF_SERVICE(服务停机)
  1. Info 元数据信息

在客户端pom文件中加入以下配置:

<!-- actuator:用于监控和管理Spring Boot应用的端点(endpoint)和健康检查(health check)功能 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

父工程pom文件中加入以下配置:

<build>
  <finalName>microservicecloud</finalName>
  <!-- 指定Maven构建过程中需要处理的资源目录 -->
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <!-- 对资源进行过滤处理 -->
      <filtering>true</filtering>
    </resource>
  </resources>
  <!-- 指定Maven构建过程中需要使用的插件 -->
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-resources-plugin</artifactId>
      <!-- 指定插件的配置信息 -->
      <configuration>
        <!-- 指定占位符的分隔符 -->
        <delimiters>
          <!-- 指定具体的分隔符。此处指定了 $ 作为占位符的分隔符 -->
          <delimit>$</delimit>
        </delimiters>
      </configuration>
    </plugin>
  </plugins>
</build>

在客户端的yml文件中加入以下配置:

info: # 指定应用程序的元数据。
  app.name: atpl-microservicecloud # 指定应用程序的名称,可以用来标识应用程序在整个系统中的作用
  company.name: www.atpl.com # 指定应用程序所属的公司名称
  build.artifactId: ${project.artifactId} # 指定应用程序的构件ID,通常是Maven项目中的artifactId
  build.version: ${project.version} #指定应用程序的版本号,通常是Maven项目中的version

访问在上一小节图中左下角出现的IP地址(192.168.1.16:8001/info)

image-20230723000410553

可以看到应用的元数据。

5.3.4 服务发现

对于注册进Eureka的服务,可以通过服务发现来获得该服务的信息。

只需要在服务主启动类上加上@EnableDiscoveryClient注解即可开启服务发现功能。

image-20230723141147542

在8001的服务端的DeptController类中加入以下方法:

/**
     * 服务发现,获取当期应用程序的注册信息
     *
     * @return
     */
@GetMapping("/discoveryServer")
public Object discoveryClientList() {
  List<String> list = discoveryClient.getServices();
  logger.info("-----微服务个数------>:" + list);
  List<ServiceInstance> instances = discoveryClient.getInstances("MICROSERVICECLOUD-DEPT");

  if (instances.size() > 0 && instances != null) {
    for (ServiceInstance instance : instances) {
      logger.info("----服务实例信息----> 【 IP:" + instance.getHost() +", 端口号:"+ instance.getPort() +", URI:"+ instance.getUri() +", 服务ID:"+ instance.getServiceId()+", 元数据"+instance.getMetadata()+" 】");
    }
  }
  return this.discoveryClient;
}

启动8001,浏览器访问http://localhost:8001/dept/discoveryServer 可以获得当前应用服务注册信息

image-20230723142044189

也可以通过8001的客户端:80 进行访问,在客户端的DeptConsumerController类中加入以下代码:

/**
     * 获取当前应用程序的注册信息
     *
     * @return
     */
@RequestMapping("/discovery")
public Object disCoveryServer() {
  return restTemplate.getForObject(REST_URL_PRIFIX + "/dept/discoveryServer", Object.class);
}

启动80微服务,浏览器发送http://localhost/dept/consumer/discovery 请求:

image-20230723142326110

都可以获得服务的注册信息。

5.4 介绍Eureka的自我保护机制

image-20230720230612094

上图中出现 ’EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.‘意思是:Eureka可能会声明已经不存在的实例,刷新数小于阈值时,为了安全起见不会剔除过期的实例。

Eureka的自我保护机制是为了防止误杀服务。当注册中心发生故障,服务不能够正常的发送心跳请求,但是服务运行正常,默认情况下,Eureka会将超过90s未发送心跳请求的服务进行移除。这样做明显不合理,所以Eureka提供了一个自我保护模式。自我保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护,一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务 。

首先说一下Eureka的默认阈值为:85%(90秒)

比如目前有10个微服务,只有8个有心跳反应时,(8/10=80%<85%)Eureka就会开启保护机制,过期的实例不会立马剔除。并且出这个紧急警告,在搭建Eureka Server时,比如我们搭建了2个Eureka Server,并且禁止自注册,Eureka Server自身算一个服务,那么其中任意一个Eureka,只能获得一个心跳,1/2=50%。那么也会出现这个警告。

这种情况如果未禁止自注册的话是不会出现的,因为本机不会有什么网络问题,肯定是百分百。

博主这里测过,只有当我开启7台及以上的Eureka Server服务(关闭Eureka Server自注册)的时候,才不会出这个警告。

因为 5/6≈83.3%<85% 6/7≈85.7%>85%。
那么当不想有这个红色警告是,本机自测可以关闭Eureka保护配置。生产环境下不要关。

在application.yml文件中配置:

  server:
    enable-self-preservation: false #关闭注册中心的自我保护机制,保证不可用服务被及时提出
    eviction-interval-timer-in-ms: 2000 #用于驱逐(移除)的间隔计时器

测试:浏览器访问localhost:7001,很明显说自我保护已经关闭。

客户端服务配置文件application.yml

eureka:
  instance:
    prefer-ip-address: true
    instance-id: provider8001
    #Eureka服务端在收到最后一次心跳后等待时间上限,默认为90秒,超时将服务提出,这里值为2,意思是如果在2秒内没有收到心跳,注册中心将认为该服务实例已过期,可能会将该实例从注册表中清除
    lease-expiration-duration-in-seconds: 2 
    #Eureka客户端向服务端发送心跳的时间间隔,默认30秒,意思是每个1秒发送心跳,以保持自己的活跃状态
    lease-renewal-interval-in-seconds: 1 

假设客户端服务宕机,看一下测试结果:可以看到客户端实例已经被清除掉

image-20230910182350666

5.5 Eureka集群配置

5.5.1 简单说下什么是集群

集群是由多台计算机组成的一个整体,他们作为一个整体向用户提供一组网络资源,这些单个的计算机系统就是集群的结点(node)。集群提供了一下关键的特性:

  • 可扩展性:集群的性能不限于单一的服务实体,新的服务实体可以动态地加入到集群中,而不会影响到进群的新能。
  • 高可用性:在集群中,如果某个节点出现故障,其他节点可以接管其工作,从而保证了整个系统的可用性。
  • 数据共享:在集群中,多个节点可以共享同一份数据,这样可以避免数据冗余和重复存储。

简单理解集群注册原理:

image-20230910120708214

互相注册,相互守望。

5.2.2 搭建
  1. 修改hosts文件

    image-20230723213708029

  2. 在原来的基础上创建7002、7003服务注册中心。

image-20230723212425336

  1. 在7001、7002、7003的application.yml文件中配置服务注册中心的信息:

    7001:

    
    eureka:
      instance:
        hostname: eureka7001.com # Eureka服务端的实例名称
      client: # false表示不向注册中心注册自己
        register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
        service-url:
          defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    

    7002:

    eureka:
      instance:
        hostname: eureka7002.com # Eureka服务端的实例名称
      client: # false表示不向注册中心注册自己
        register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
    

    7003:

    eureka:
      instance:
        hostname: eureka7003.com # Eureka服务端的实例名称
      client: # false表示不向注册中心注册自己
        register-with-eureka: false # false表示自己端就是注册中心,我的职责是维护服务实例,并不需要检索服务
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    

    即在三个Eureka服务中心都要相互注册另外两个服务为服务注册中心集群节点

  2. 启动三个Eureka服务,他们就可以相互注册另外两个为集群节点,新城服务注册中心集群

  3. 其他微服务在注册时只需要注册到集群中的任意一个节点即可,Eureka会同步服务信息。

6、Ribbon负载均衡

官网地址

6.1 概念

​ Ribbon是Netflix的开源项目,他提供了一组云中间件开发组件,主要用于通过HTTP和TCP协议进行服务之间的调用、负载均衡以及故障处理。

​ Ribbon主要包括一下几个核心功能:

  • 负载均衡:Ribbon是一个客户端负载均衡器,它可以根据某种规则(如简单轮询、随机连接等)在服务器实例结合中选择一个运行实例。在微服务架构中,由于服务实例在动态变化,因此需要一种机制实时更新服务器实例的变化情况,Ribbon就能满足这样的需求。
  • 服务调用:Ribbon提供了一套完整的HTTP和TCP客户端行为封装。他通过与SpringCloud的整合,使用注解的方式就可以实现一个HTTP客户端的创建并发送HTTP请求,非常方便。
  • 故障处理:在一个分布式系统中,服务可能因为各种原因调用失败,Ribbon提供了多种故障处理策略,包括重试、回退等。

6.2 负载均衡器(Load Balancer,简称LB)

​ 在微服务分布式架构中,Load Balancer(负载均衡器)是一种将用户的请求平摊的分发到多个服务实例上,从而达到系统的高可用性(HA)的技术。Load Balancer可以根据不同的负载均衡策略来选择服务实例,例如轮询、随机、最小连接数。常见的负载均衡有软件Nginx、LVS,硬件F5。

6.2.1 集中式Load Balancer(LB)

集中式LB是指在服务器群集前(服务的消费方和提供方之间)设置一个独立的LB设备,负责接收用户请求,然后根据负载均衡算法将请求转发给后端的不同服务器。在集中式Load Balancer架构中,用户只需要知道Load Balancer的地址,然后将请求发送到改地址即可,而无需关心后端服务实例的具体地址。常见的集中式负载均衡器有硬件F5,软件Nginx。

优点:部署简单、支持各种负载均衡算法

缺点:LB设备单点故障、性能和扩展能力受LB设备限制

6.2.3 进程内Load Balancer(LB)

进程内LB是一种在应用程序内部实现的负载均衡机制,不依赖外部独立的负载均衡设备,负载均衡逻辑内置在应用进程内。应用进程之间通过进程内通信来实现请求的负载分发,如RPC调用、消息队列等。

Ribbon就是一个进程内LB

优点:去中心化、避免了集中式负载均衡的单点故障问题、很容易通过增加应用进程实例来进行扩展

缺点:由于负载均衡在进程内增加了应用的复杂度,需要处理RPC或队列、增加了开发难度、无法做全局优化

6.3 Ribbon的配置

修改80客户端微服务模块:

pom文件中加入Ribbon相关以来坐标

<!-- Ribbon相关 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

在application.yml文件中加入以下内容:

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: 					http://erureka7001.com:7001,http://eureka7002.com:7002,http://eureka7003.com:7003

在ConfigBean中的restTemplate方法上加上@LoadBalanced 注解,如下:

@Bean
@LoadBalanced // 表示这个RestTemplate对象开启了负载均衡功能
public RestTemplate restTemplate(){
  return new RestTemplate();
}

启动类加上@EnableEurekaClient注解

@SpringBootApplication
@EnableEurekaClient // 开启Eureka客户端
public class DeptConsumer80_APP {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer80_APP.class,args);
    }
}

修改DeptConsumerController类:

在这之前客户端能够访问服务提供端都是绕过了Eureka。什么意思呢?如下面代码所示,访问路径是写死的。

@RequestMapping("/dept/consumer")
@RestController
public class DeptConsumerController {

    /**
     * 服务提供端的IP
     */
    public static final String REST_URL_PRIFIX = "http://localhost:8001";

   // ....
}

修改后:

@RequestMapping("/dept/consumer")
@RestController
public class DeptConsumerController {

    /**
     * 服务提供端的IP
     */
    //public static final String REST_URL_PRIFIX = "http://localhost:8001";
  
		// 使用微服务的名字
    public static final String REST_URL_PRIFIX = "http://MICROSERVICECLOUD-DEPT";
  
}

先启动三个Eureka服务端(注册中心:7001/7002/7003),再启动8001微服务,将8001注册进注册中心,然后在启动80微服务,80微服务在yml文件中设置了本身不注册进注册中心,然后在浏览器发送请求进行测试。

浏览器输入http://localhost/dept/consumer/get/list出现下图即表示成功。

image-20230725234646136

Ribbon和Eureka整合后Consumer可以直接调用服务而不用在关心地址和端口号

image-20230725235041841

关键之处是:

image-20230725235136125

使用Ribbon的过程大致如下:

  • 首先,使用Ribbon的客户端会获取服务端列表。这个列表可以来自与Eureka等服务注册中心,或直接在配置文件中指定。
  • 然后,Ribbon从这个列表中按照某种策略选择一个地址。例如,它可以使用Round-Robin(轮询)策略。
  • 最后,Ribbon的客户端会向这个地址发送HTTP请求。

6.4 @LoadBalanced注解

在SpringCloud中,@LoadBalanced是一个重要的注解,它用于实现客户端负载均衡。这个注解通常与RestTemplate 或 WebClient 一起使用。下面是一些详细的解释。

  • 解释

@LoadBalanced注解的主要用途是提供一种简单的方式来将负载均衡策略应用到指定的服务消费者。换句话说,它允许消费者在多个服务提供者实例之间分配请求,而不需要明确知道他们的存在。

  • 工作原理

当你在RestTemplate或WebClient bean上使用@LoadBalanced注解时,SpringCloud会创建一个特殊的代理,该代理将拦截所有的HTTP(S)请求。这个代理会使用Ribbon或其他的客户端负载均衡器来选择一个服务实例,并将请求重定向到这个实例。

  • 案例

下面是一个使用@LoadBalanced的简单实例:

@Configuration
public class MyConfiguration {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

在上面的代码中,@LoadBalanced注解用于一个新创建的RestTemplate bean。现在当你使用这个RestTemplate发送一个HTTP请求时,请求会被发送到通过Ribbon选择的服务实例。例如,你可以将请求发送到‘my-service’,而不需要知道具体的URL:

String response = restTemplate.getForObject("http://my-service/some-endpoint", String.class);

在这种情况下,Ribbon会从Eureka或其他服务发现系统中获取‘my-service’的所有实例,并选择一个来发送请求。

注意:在新的SpringCloud版本中,RestTemplate已经被弃用,推荐使用WebClient。

6.5 负载均衡案例

  1. 架构说明:

image-20230727210710239

  1. 扩展微服务工程:

参考微服务提供者dept-8001创建dept-8002/dept-8003微服务

  1. 新建数据库sringCloud02、springcloud03,各微服务分别连接自己的数据库。这里只是简单的创建两个库来模拟主从同步,读写分离。
#建库脚本
CREATE DATABASE `springcloud01`
CREATE DATABASE `springcloud02`
CREATE DATABASE `springcloud03`
#建表脚本,需要在三个库中都执行一下
CREATE TABLE `dept` (
  `deptNo` int(11) NOT NULL AUTO_INCREMENT,
  `deptName` varchar(255) DEFAULT NULL,
  `dbSource` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`deptNo`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
#插入数据脚本,只需要改一下‘springcloud03’
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '张三', 'springcloud03');
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '李四', 'springcloud03');
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '王五', 'springcloud03');
INSERT INTO `dept` ( `deptName`, `dbSource`) VALUES ( '赵六', 'springcloud03');
  1. 修改8002/8003各自YML文件

image-20230727220457717

8003也是同样的方式修改。

  1. 启动3个eureka集群

  2. 启动3个dept微服务进行自测

  3. 分别自测三个微服务提供端

image-20230727223833700

发送8001的请求

image-20230727231703604

发送8002的请求

image-20230727231746843

发送8003的请求

image-20230727231822944

  1. 启动客户端80,通过注册中心进行访问。

image-20230727232151816

可以看到每次点击刷新按钮,都会获取到不同的相应数据。这就是Ribbon的负载均衡(轮询算法)。

6.6 Ribbon核心组件IRule

根据特定算法从服务列表中选取一个要访问的服务。

默认情况下会使用轮询算法分发请求,即依次向可用服务实例发送请求。

通过实现Rule接口,可以自定义请求路由规则。主要有以下几种:

  • RoundRobinRule(轮询【默认】):轮询所有可用服务实例,依次向每个实例发送请求。
  • RandomRule(随机规则):随机选择一个可用服务实例发送请求。
  • WeightedResponseTimeRule(加权响应时间规则):按服务实例的权重分配请求。权重大的实例处理更多请求。
  • AvailabilityFilterRule(可用性筛选规则):先过滤掉当前不可用的服务实例,然后根据轮询、随机等策略分发请求。
  • BestAvailableRule(最佳可用规则):选择一个最少活跃的服务实例发送请求,分散负载均衡压力。
  • RetryRule(重试规则):首先使用某个IRule路由,如果请求失败则会重新请求,如果再次失败则结合断路器进行处理。
  • ZoneAvoidanceRule(区域回避规则):考虑服务实例所在区域的性能和访问性,尽量避免高延迟的区域。

所以通过实现自定义的IRule,我们可以按照自己的需求实现复杂的请求路由策略。

IRule很重要的一点是它决定着服务调用的稳定性和性能。选择一个合适的路由策略可以有效地分散负载,提高吞度量和成功率。

IRule

6.6.1 更换Ribbon负载均衡算法

ConfigBean.java配置类中定义算法

/**
     * 更换算法
     */
    @Bean
    public IRule myRule(){
        // 随机算法
        return new RandomRule();
    }

重启80服务,发送请求

image-20230730193321856

可以看到每次刷新,所请求的服务是随机的。

特别地(重试规则):

/**
     * 更换算法
     */
    @Bean
    public IRule myRule(){
        // 随机算法
        //return new RandomRule();
        // 重试算法
        return new RetryRule();
    }

重启80 服务,此时我们将8001服务提供短down掉,来模拟服务挂掉的场景。(如果没有down掉的服务的话,会按照某个算法,一般是轮询,来分发请求)

发送80客户端请求:

image-20230730194757876

可以看到,不在去找8002的服务实例了。

其他的分发算法不在一一演示。有兴趣的伙伴可以自己测试一下,来加深印象!

image-20230730195056691

只需要按这个格式去定义就行了。

6.6.2 自定义Ribbon负载均衡策略

现在需求是不使用Ribbon提供的负载均衡算法,需要开发人员自定义。

在开始前先了解一下@RibbonClient注解,该注解是用于自定义Ribbon客户端配置。在启动该微服务的时候就能去加载我们的自定义的Ribbon配置类,从而是配置生效

@RibbonClient(name = "serviceName", configuration = MyClientConfig.class)
  • name:Ribbon客户端名称,即服务名称,用于标识这个Ribbon客户端。
  • configuration:自定义的Ribbon配置类。

案例1:使用最少连接算法实现自定义Ribbon负载均衡策略

算法实现:

public class CustomRule extends AbstractLoadBalancerRule {

    // 声明一个map类型的成员变量,用来存放每个服务器的连接数
    private ConcurrentHashMap<Server, AtomicInteger> connectionCountMap;

    //
    public CustomRule() {
        connectionCountMap = new ConcurrentHashMap<>();
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // 不需要额外的配置,留空
    }

    @Override
    public Server choose(Object key) {
        // 获取所有可用服务器列表
        List<Server> servers = getLoadBalancer().getAllServers();

        // 存储连接数最少的服务器
        Server leastConnectionsServer = null;
        // 初始化整形最大值,记录当前最少连接数
        int leastConnections = Integer.MAX_VALUE;

        // 遍历服务器列表
        for (Server server : servers) {
            // 根据服务器从connectionCountMap中获取对应的连接数。
            AtomicInteger connections = connectionCountMap.get(server);
            // 连接数为空
            if (connections == null) {
                // 初始化连接数为0
                connections = new AtomicInteger(0);
                // 将服务其和对应的连接数存入map中
                connectionCountMap.put(server, connections);
            }
            // 获取当前服务器的连接数
            int currentConnections = connections.get();
            // 如果当前连接数小于最小连接数
            if (currentConnections < leastConnections) {
                // 更新最小连接数为当前连接数
                leastConnections = currentConnections;
                // 更新连接数最小的服务器为当前服务器
                leastConnectionsServer = server;
            }
        }
        // 如果当前连接数最小的服务器不为空
        if (leastConnectionsServer != null) {
            // 将连接数最少的服务器连接数加一
            connectionCountMap.get(leastConnectionsServer).incrementAndGet();
        }
        // 返回连接数最小的服务器
        return leastConnectionsServer;
    }
}

案例2:使用随机规则,只不过要求每个服务被调用5次

public class RewritePolling extends AbstractLoadBalancerRule {


    private int total = 0; // 记录已调用次数

    private int currentIndex = 0; // 当前选择的服务实例索引

    @Override
    public Server choose(Object key) {
        // 获取负载均衡器
        ILoadBalancer lb = getLoadBalancer();
        if (lb == null) {
            return null;
        }

        Server server = null;
        while (server == null) {
            // 检查线程是否被中断
            if (Thread.interrupted()) {
                return null;
            }

            // 获取可到达的服务实例列表
            List<Server> upList = lb.getReachableServers();
            // 获取所有的服务实例列表
            List<Server> allList = lb.getAllServers();
            // 服务实例总数
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
            // Github源码:不是每个服务调用5次
            //int index = chooseRandomInt(serverCount);
            //server = upList.get(index);
            if (total < 5) {
                // 选择当前索引对应的服务实例
                server = upList.get(currentIndex);
                // 让调用次数加一
                total++;
            } else {
                // 调用次数大于了5次后,将调用次数置为0
                total = 0;
                // 调用下一个服务实例
                currentIndex++;
                // 当索引超出范围时,重新从头开始选择
                if(currentIndex >= upList.size()){
                    currentIndex = 0;
                }
            }
            if (server == null) {
                // 放弃当前线程的CPU时间片,让其他线程执行
                Thread.yield();
                continue;
            }
            // 检查服务实例是否存活
            if (server.isAlive()) {
                return (server);
            }
            server = null;
            Thread.yield();
        }
        return server;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // 初始化操作
    }
}

将自定义算法应用到负载均衡器中

@Bean
public IRule ribbonRule(){
  // 最少连接算法
  //return new CustomRule(); 
  
  // 随机算法,每个服务被调用5次
  return new RewritePolling();
}

主启动类上加上@RibbonClient注解

@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration = ConfigBean.class) // 加载自定义Ribbon客户端配置
public class DeptConsumer80_APP {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer80_APP.class,args);
    }
}

注意点:@RibbonClient注解和负载均衡规则类需要位于不同的包中,主要有以下两个原因:

  • 在Spring初始化阶段,@RibbonClient所在的类会优先初始化。它用来配置Ribbon客户端,指定负载均衡规则类。
  • 如果@RibbonClient和负载均衡规则类位于同一包中,在Spring初始化阶段会产生循环依赖。@RibbonClient 初始化时需要加载负载均衡规则类,但是负载均衡规则类还没有初始化。

所以为了避免初始化顺序问题和循环依赖,@RibbonClient所在类的包不能与负载均衡规则类所在的包相同。

简单来说就是:

  • @RibbonClient负责配置ribbon客户端以及制定负载均衡规则。
  • 负载均衡规则类实现负载均衡算法逻辑。

7、Feign负载均衡

官方地址

源码地址

7.1 概念

​ Feign是一个声明式的Web服务客户端,使用Feign能让编写Web服务客户端更加简单,他的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

说白了就是Feign是一个Web服务客户端,是的编写Web服务客户端变得非常容易。

只需创建一个接口,然后在上面添加注解即可

7.2 为什么使用Feign

​ 开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。

Feign继承了Ribbon

​ 利用Ribbon维护了注册中心的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

​ Feign通过接口的方法调用Rest服务,(之前是Ribbon + RestTemplate),该请求发送给Eureka注册中心,通过Feign直接找到服务接口,由于在进行服务调用的时候融合了Ribbon技术,所以也支持负载均衡。

7.3 Feign的特点

  1. 简化HTTP客户端的创建:Feign可以自动创建并绑定接口到HTTP请求,这大大简化了HTTP客户端的创建和使用。
  2. 集成Ribbon和Hystrix:Feign可以与Neflix的其他开源项目Ribbon(客户端负载均衡器)和Hystrix(断路器)结合使用,从而提供负载均衡和容错能力。
  3. 支持插件化和可扩展:Feign支持插件化,开发者可以通过实现特定的接口或者注解来扩展Feign的功能。
  4. 使用注解来定义HTTP请求:Feign使用注解来定义HTTP请求,这使得代码更加清晰和易于理解。
  5. SpringCloud中的一部分:Feign是SpringCloud的一部分

7.4 搭建

导入依赖坐标

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

定义接口

@RequestMapping("/dept")
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService {

    @PostMapping("/add")
    boolean add(Dept dept);

    @GetMapping("/get/{id}")
    Dept get(@PathVariable("id") Long id);

    @GetMapping("/get/list")
    List<Dept> list();
}

客户端通过接口调用

@RestController
public class DeptConsumerController {

    public static final Logger logger = LoggerFactory.getLogger(DeptConsumerController.class);

    @Autowired
    DeptClientService deptClientService;

    @PostMapping("/add")
    public boolean add(@RequestBody Dept dept){
        boolean flag = deptClientService.add(dept);
        if(flag){
            logger.info("增加成功");
        }
        return flag;
    }

    @GetMapping("/get/{id}")
    public Dept get(@PathVariable("id") Long id){
        return deptClientService.get(id);
    }

    @GetMapping("/get/list")
    public List<Dept> list(){
        return deptClientService.list();
    }
}

测试:POSTMAN发送请求http://localhost/get/list

image-20230810205714414

为什么接口上加了@FeignClien注解就可以访问到远程的服务了呢?

本案例中@FeignClient(value = "MICROSERVICECLOUD-DEPT")表示DeptClientService接口是一个Feign客户端代理接口,他将用于访问名为“MICROSERVICECLOUD-DEPT”的远程服务。通过该注解,Feign将根据接口定义自动创建一个代理类实现,用于与远程服务进行通信。我这里调用的是DeptClientService接口中list()方法,Feign将自动将请求发送到"MICROSERVICECLOUD-DEPT/dept/get/list" 路径上.

那么接口是如何找到远程服务的呢?

这是因为主启动类上标注了@EnableFeignClients(basePackages = {"com.atpl.springcloud"}),该注解需要制定basePackages参数,用于指定要扫描的包路径,即扫描有@FeignClient注解的接口,并为其生成代理实现。

8、Hystrix断路器

官网地址

8.1 概念

Hystrix断路器是一种用于构建韧性和弹性分布式系统的开源库。它最初由Netflix开发,并广泛应用于微服务架构中,以帮助应对服务间的故障和延迟。

Hystrix的主要目的是防止级联故障。在分布式系统中,一个服务的失败或延迟可能会导致对其依赖的其他服务也出现问题,从而引发级联故障。Hystrix通过实施断路器模式来解决这个问题。断路器模式允许在服务出现故障或延迟时,通过断路器的故障监控向调用方返回一个快速失败并提供备用的响应,而不是一直等待或继续尝试请求。

8.2 工作原理

  1. 当一个服务的请求频率达到一定的阈值时,Hystrix会监视该服务的状态。
  2. 如果服务的错误率超过预设的阈值时,Hystrix会打开断路器,将请求转发到备用的服务或返回预设的默认响应。
  3. 在一段时间内,Hystrix会定期尝试发出一些试探性请求到原始服务,已检查其是否恢复正常。
  4. 如果试探性请求成功,Hystrix会关闭断路器,继续将请求转发到原始服务。
  5. 如果试探性请求失败,Hystrix会继续保持断路器打开状态,并延迟一段时间后再次尝试试探性请求。

Hystrix还提供了其他功能,包括请求缓存、请求合并、线程池隔离和实时的监控和指标收集。这些功能使得开发人员能够更好地理解和管理服务之间的依赖关系,并在故障发生时提供更好的容错能力。

8.3 服务熔断

熔断机制是应对雪崩效应的一种微服务链路保护机制。

当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该结点微服务的调用,快速返回“错误”的响应信息(并返回一个事先定义好的备选响应或错误结果 FallBack)。当检测到该结点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand

@HystrixCommand注解解释:

该注解由Hystrix提供,用于实现服务的容错和隔离。**当使用该注解时,Hystrix会将被注解的方法封装在一个独立的线程池中,并未该方法设置一个超时时间。**如果该方法执行时或发生异常,Hystrix会自动触发服务降级,从而保证系统的可用性和稳定性。此外,@HystrixCommand 还支持实现服务熔断、请求缓存等功能。

@EnableCircuitBreaker注解解释:

该注解由SpringCloud提供,用于断路器功能,通过监控和控制对远程服务的调用来实现容错和故障恢复的机制。该注解主要开启断路器功能,会自动为使用断路器模式的Bean创建代理,断路器代理会监控这些方法的调用情况。如果调用远程服务出现故障或超市,断路器会阻止对该服务的进一步调用。然后通过故障恢复的机制,调用备用服务或者返回预先定义的错误消息。

@GetMapping("/get/{id}")
    // 一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注的fallBackMethod调用类中指定的方法
@HystrixCommand(fallbackMethod = "processHystrix_Get")
public Dept get(@PathVariable Long id) {
  Dept dept = deptService.get(id);
  if (dept == null) {
    // 抛出自定义的异常
    throw new CustomException("dept is null");
  }
  return dept;
}
// 服务熔断后处理方法 (fallback方法)
public Dept processHystrix_Get(@PathVariable Long id) {
  // 处理服务异常或超时情况下的逻辑
  return new Dept().setDeptNo(id).setDeptName("");
}

注意:

标记了@HystrixCommand注解的额方法与fallback方法的返回值类型与参数列表不一定保持一致。需要遵守一些要求:

  • 返回值类型:fallback方法的返回值类型应该与标记了@HystrixCommand方法的返回值类型兼容。比如,标记了@HystrixCommand方法返回一个String类型,那么fallback方法也要返回一个String类型或其子类。

8.4 服务降级

​ 服务降级是一种应对系统故障、资源不足或异常情况下的策略,旨在保证系统的可用性和稳定性。当系统出现故障、性能下降或资源紧张时,服务降级通过临时屏蔽某些功能或切换到备用方案来减少对系统的负载,确保核心功能的正常运行,并提供用户友好的响应。

服务降级的主要目标是在面对异常情况时保持系统的可用性,避免因单个组件的故障而导致整个系统的崩溃。

image-20230813162641275

fallback方法与业务逻辑方法在一起,或者有很多方法都需要服务熔断,那么每个方法都要加上@HystrixCommand注解,这是一种高耦合的表现。

优化:所有要加@HystrixCommand的方法都是来源于接口中,那么将接口进行熔断岂不是每个方法发不用再一一的添加@HystrixCommand

实现:

/**
 * @author cpl
 * 服务降级处理
 * 将其从业务逻辑中拆分出来,降低耦合
 */
@Component
public class DeptClientServiceFallBackFactory implements FallbackFactory<DeptClientService> {

    @Override
    public DeptClientService create(Throwable cause) {
        return new DeptClientService() {
            @Override
            public Dept get(Long id) {
                return new Dept().setDeptNo(id).setDeptName("");
            }
        };
    }
}

DeptClientService接口:

@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory = DeptClientServiceFallBackFactory.class) 
public interface DeptClientService {
    @GetMapping("/get/{id}")
    Dept get(@PathVariable("id") Long id);
}
# 启用feign客户端熔断器
feign:
  hystrix:
    enabled: true

这样就将fallback方法与业务逻辑分离开,从而也实现服务降级。可以发现服务降级是在客户端实现的。另外客户端服务降级及时服务关闭也能看到提示信息而不会挂起耗死服务器。

8.5 服务降级与服务熔断的区别

  1. 概念

服务熔断(Circuit Breaking)是一种用于处理故障的模式,它旨在防止故障在整个系统中扩散。当服务发生故障或超过一定阈值时,熔断器会打开并快速拒绝后续的请求,从而减轻对故障服务的压力。服务熔断的核心目标是保护系统免受不可用服务的影响,提高系统的容错性。一旦熔断器打开,可以采取一些措施,如返回缓存数据、返回默认值或使用备用服务等。

服务降级(Fallback)是一种在服务不可用或性能下降时提供有限但可用功能的策略。当服务发生故障、性能下降或超过一定的响应时间时,可以通过服务降级来返回预先定义的默认值、缓存数据或备用结果。服务降级的目标是确保调用方在面对异常情况时仍能够获得响应,而不会因为服务不可用而导致整个系统的不可用。服务降级可以在单个服务内部实现,也可以由调用方在失败时采取备用策略。

  1. 区别

    目的:服务熔断的目标是防止故障在整个系统中扩散,保护系统免受不可用服务的影响;服务降级的目标识在服务不可用或性能下降时提供有限但可用的功能,确保调用方仍能够获得相应。

    触发条件:服务熔断触发条件通常是错误率超过阈值或请求失败次数超过阈值;服务降级触发条件通常是服务不可用、性能下降或超过一定的响应时间。

    动作:服务通断打开后,可以采取一些措施,如返回缓存数据、返回默认值或使用备用服务等;服务降级时,可以返回预先定义的默认值、缓存数据或备用结果。

    范围:服务熔断通常是在服务间的通信层面进行,以防止故障在整个系统中扩散;服务降级可以在单个服务内部实现,也可以有调用方在失败时采用备用策略。

8.6 服务监控

pom文件新增依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>

主启动类新增注解@EnablebHystrixDashboard

image-20230813185315646

application.yml

server:
  port: 9001

浏览器输入http://localhost:9001/hystrix出现如下界面:

image-20230813185508643

监控测试:

浏览器输入http://localhost/8001/hystrix.stream

image-20230813185627455

此界面会一直刷新。

也可以通过Dashboard界面测试:

image-20230813190211817

Delay:控制服务器上轮询监控信息的延迟时间,默认2000毫秒,可通过配置改属性降低客户端的网络和CPU的消耗。

Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。

点击Monitor Stream后,跳转到如下界面:

image-20230813192124987

9、Zuul路由网关

gateway

9.1 概念

​ Zull是Netflix开源的一个基于JVM的边缘服务网关,用于在微服务架构中提供动态路由、过滤器、负载均衡、安全性能等功能。他作为应用程序的前置网关,接收所有的客户端请求,并将请求路由到适当的微服务实例

注意:Zull服务最终还是会注册进Eureka

提供=代理+路由+过滤三大功能

9.2 主要功能

  • 路由(Routing):Zull可以根据配置将请求路由到不同的微服务实例或后端服务。他支持基于RUL路径、主机名、请求参数等条件进行灵活的路由规则配置。
  • 过滤器(Filtering):Zull的过滤器机制可以在请求进入和响应返回时进行预处理和后处理。通过变细自定义的过滤器,可以实现身份验证、请求转换、日志记录、请求限流等工能。
  • 负载均衡(Load Balancing):Zull集成了负载均衡功能,可以根据配置将请求均匀的分发到多个微服务实例,以提高系统的可伸缩性和可用性。
  • 安全性(Security):Zull可以通过与认证和授权服务进程,实现对请求的安全验证和访问控制。可以拦截请求畸形身份验证,确保只有经过验证的请求才能访问后端服务。
  • 监控和日志(Monitoring and Logging):Zull支持继承监控和日志系统,可以记录请求的状态、响应事件等指标,帮助开发人员进行故障排查和性能优化。

9.3 路由的基本配置

  1. 导入依赖
<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
  </dependency>
</dependencies>
  1. 主启动类加上
@EnableZuulProxy
  1. application.yml
server:
  port: 9527


spring:
  application:
    name: microservicecloud-zull-gateway # 微服务实例名称


eureka:
  client:
    service-url:
      defaultZone: http://eureka7001:7001.com/eureak,http://eureka7002:7002/eureka,http://eureka7003:7003/eureka
  instance: # 配置Zull的实例
    instance-id: gateway-9527.com # Zull实例唯一标识符
    prefer-ip-address: true # 是否优先使用Ip地址进行路由,值为ture时,会优先使用服务实例的IP地址进行访问


访问Eureka的注册中界面:

image-20230813205405982

测试zull:

  1. 普通访问

image-20230813205736339

  1. 通过网关访问

image-20230813211214263

9.5 路由的访问映射规则

application.yml文件中:

zuul:
  routes:
    agent.serviceId: microservicecloud-dept # 指定agent路由规则将转发到的服务的服务ID
    agent.path: /agent/** # 路径匹配规则,指定匹配该路由规则的请求路径 (配置服务的匿名),本例中所有以"/agent/"开头的路径都会匹配该规则。
  ignored-services: microservicecloud-dept # 忽略的服务,如果要忽略多个可以使用 * 号
  prefix: /atpl # 前缀配置,指定了Zuul路由的前缀路径。本例中,Zuul将会将所有的路由请求的路径加上“/atpl”前缀

image-20230814204102087

可以看到上图中,请求路径中是使用的路由规则所以能访问到。

然而下图中,由于配置了 ignored-services: microservicecloud-dept,所以访问路径中含有"microservicecloud-dept",则该请求将会被忽略,不会进行路由转发。

image-20230814204222303

使用prefix: /atpl配置了前缀

访问原来的路径:

image-20230814205715634

访问带有前缀的路径:

image-20230814205758042

10、SpringCloud Config分布式配置中心

10.1 服务端

分布式系统面临的—配置问题

​ 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题。每个服务都有一个application.yml文件,那么上百个服务呢?岂不悲剧了!

10.1.1 是什么

SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置

10.1.2 怎么玩

image-20230816215130904

SpringCloud Config分为服务端和客户端两部分

服务端也称为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。

客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。

10.1.3 能干嘛
  • 集中化配置管理:SpringCloud Config 提供了一个集中化的配置存储库,可以将应用程序的配置信息存储在版本控制系统(如Git)或其他后端存储中。通过集中管理配置,可以实现配置的版本控制、审计和变更历史记录。
  • 配置文件的外部化:SpringCloud Config 允许将应用程序的配置文件从代码中分离出来,已实现配置与代码的解耦。配置文件可以根据环境、应用程序实例等进行不同的配置,而无需重新打包和部署应用程序。
  • 动态刷新配置:SpringCloud Config 支持配置的动态刷新,即在应用程序运行时可以重新加载配置信息,而无需重启应用程序。进而避免重新部署应用程序或重启服务的操作。
  • 多环境支持:SpringCloud Config支持多环境配置,可以为不同的环境(开发、测试、生产)提供不同的配置信息。通过使用不同的配置文件或配置文件的目录结构,轻松管理和区分不同环境的配置。
  • 安全性和权限控制:SpringCloud Config 支持对配置信息进行安全性管理和权限控制。可以通过身份验证和授权机制,限制对配置信息的访问权限,确保配置的机密性和完整性。
10.1.4 Config服务端与Github的通信

SpringCloud Config 与 GitHub整合步骤:

  1. 在GitHub上新建一个仓库用于存储配置文件。可以按照应用程序、环境或其他自定义方式组织配置文件。

image-20230816222301177

  1. 在本地创建本地仓库并克隆GitHub上的microservicecloud-config仓库

image-20230816222908776

image-20230816223335539

  1. 然后再此文件夹中新建application.yml文件:要保存成UTF-8的格式!要保存成UTF-8的格式!
spring:
  profiles:
    active:
      - dev
---
spring:
  profiles: dev
  application:
    name: microservicecloud-config-dev
---
spring:
  profiles: test
  application:
    name: microservicecloud-config-test
---
spring:
  profiles: prod
  application:
    name: microservicecloud-config-prod
#编码格式保存为UTF-8
  1. 将文件推到远程的GitHub从库中:

image-20230816225228041

远程库:

image-20230816225957956

  1. 创建SpringCloud Config 微服务,如下:

application.yml文件:

server:
  port: 3344

spring:
  application:
    name: microservicecloud-config # 应用程序名,用于从SpringCloud Config 服务器获取配置
  cloud:
    config:
      server:
        git:
          uri: https://github.com/pinlu-0/microservicecloud-config.git # 指定远程GitHub存储库的URI
          username: #GitHub用户名
          password: #Github密码
          default-label: main # 分支

主启动类:

@SpringBootApplication
/**
 * 开启配置服务器的功能
 */
@EnableConfigServer
public class Config_App_3344 {
    public static void main(String[] args) {
        SpringApplication.run(Config_App_3344.class);
    }
}

pom文件:

<!-- SpringCloud Config 依赖-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>

启动配置中心服务:http://localhost:3344/application-dev.yml

image-20230820110052568

但是这种是通过https的形式访问的远程库

image-20230902205502253

使用SSH秘钥远程访问比较坑,下面介绍怎么使用SSH远程访问:

首先生成秘钥并添加到GitHub中:

#生成秘钥
ssh-keygen -t rsa -b 4096 -C "youremail@example.com"

#测试SSH连接,如果出现如下信息则表示成功
ssh -T git@github.com
> Hi USERNAME! You've successfully authenticated, but GitHub does not
> provide shell access.

按道理应该是可以访问了的,但是出现了报错,大概意思是github不支持RSA秘钥,它认为这种算法的秘钥不安全。
所以使用 ssh-keygen -t ed25519 -C “yourmail@example.com” 命令生成秘钥,但是呢,这种生成的秘钥是
OPENSSH类型的,而com.jcraft.jsch不兼容OPENSSH所以。做在这只是简单试了一下,应为可能需要更换版本比较麻烦,也没有深入解决。后续可能会解决!

10.1.5访问方式

10.2 客户端

10.2.1 单机版

image-20230902200312203

配置客户端:

  1. pom文件
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
  1. 测试类
@RestController
public class ConfigClientController {

    @Value("${spring.application.name}")
    private String applicationName;

    @Value("${eureka.client.service-url.defaultZone}")
    private String eurekaServers;

    @Value("${server.port}")
    private String port;

    @RequestMapping("/config")
    public String getConfig() {
        String str = "applicationName: " + applicationName + "\n eurekaServers:" + eurekaServers  +"\n port: " +port ;
        System.out.println("******str: " + str);
        return "applicationName: " + applicationName + "\n eurekaServers:" +eurekaServers + "\n port: " + port ;
    }
}
  1. bootstrap.yml文件
spring:
  cloud:
    config:
      name: microservicecloud-config-client #
      profile: test
      label: master
      uri: http://config-3344.com:3344 # 指定配置服务端的URI

测试:启动服务端,启动客户端。

测试访问服务端:http://localhost:3344/microservicecloud-config-client-dev.yml

image-20230902203354733

测试访问客户端:http://localhost:8201/config,可能回报以下错误,原因是服务端不能够访问到数据,可以单独测试一下服务端是否能访问到数据。

Could not resolve placeholder 'eureka.client.service-url.defaultZone' in value "${eureka.client.service-url.defaultZone}"

浏览器打印数据:

image-20230902205957466

控制台打印数据:

image-20230902210046463

通过修改bootstrap.pom文件测试8201端口:

image-20230902210318956

image-20230902210445786

image-20230902210455967

现在我们做一个通过Eureka服务+一个Dept客户端访问的服务,将两个微服务的配置统一由github获得实体统一配置分布式管理,完成多环境的变更。

10.2.2 访问注册中心版
  1. 新建配置版Eureka注册中心

image-20230903181217778

bootstrap.yml内容:

spring:
  cloud:
    config:
      name: microservicecloud-config-eureka-client # 需要从github上读取的资源名称
      profile: dev
      label: master
      uri: http://config-3344.com:3344 # 服务端地址

服务端存放在配置中心(GitHub上)的文件:microservicecloud-config-eureka-client

spring:
  profiles:
    active:
      - dev
---
server:
  port: 7001
spring:
  profiles: dev
  application:
    name: microservicecloud-config-eureka-client

eureka:
  instance:
    hostname: eureka7001.com
  client:
    register-with-eureka: false # 当前的Eureka-server自己不注册到服务列表中
    fetch-registry: false # 不通过Eureka获取注册信息
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
---
server:
  port: 7001
spring:
  profiles: test
  application:
    name: microservicecloud-config-eureka-client

eureka:
  instance:
    hostname: eureka7001.com # 冒号后面必须有空格
  client:
    register-with-eureka: false # 当前的Eureka-server自己不注册到服务列表中
    fetch-registry: false # 不通过Eureka获取注册信息
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  1. 新建配置客户端(可参考之前的)

image-20230903181505755

bootstrap.yml内容:

spring:
  cloud:
    config:
      name: microservicecloud-config-dept-client # 需要从github上读取的资源名称
      profile: dev
      label: master
      uri: http://config-3344.com:3344 # config服务端的访问地址

客户端存放在配置中心(GitHub)的文件内容:microservicecloud-config-dept-client

spring:
  profiles:
    active:
      - dev
---
server:
  port: 8001
spring:
  profiles: dev
  application:
    name: microservicecloud-config-dept-client
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.jdbc.Driver             # mysql驱动包
    url: jdbc:mysql://localhost:3306/springcloud01    # 数据库名称
    username: root
    password: root
    dbcp2:
      min-idle: 5                                           # 数据库连接池的最小维持连接数
      initial-size: 5                                       # 初始化连接数
      max-total: 5                                          # 最大连接数
      max-wait-millis: 200
mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml        # mybatis配置文件所在路径
  type-aliases-package: com.atpl.springcloud.entities    # 所有Entity别名类所在包
  mapper-locations:
    - classpath:mybatis/mapper/**/*.xml

eureka:
  client: # 客户端注册进eureka服务列表内
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
      # 单机版 defaultZone: http://localhost:7001/eureka
  instance:
    instance-id: dept-8001.com # 自定义服务名名称信息
    prefer-ip-address: true
info: # 指定应用程序的元数据
  app.name: atpl-microservicecloud # 指定应用程序的名称,可以用来标识应用程序在整个系统中的作用
  company.name: www.atpl.com # 指定应用程序所属的公司名称
  build.artifactId: ${project.artifactId} # 指定应用程序的构件ID,通常是Maven项目中的artifactId
  build.version: ${project.version} #指定应用程序的版本号,通常是Maven项目中的version

---

server:
  port: 8001
spring:
  profiles: test
  application:
    name: microservicecloud-config-dept-client
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.jdbc.Driver             # mysql驱动包
    url: jdbc:mysql://localhost:3306/springcloud02   # 数据库名称
    username: root
    password: root
    dbcp2:
      min-idle: 5                                           # 数据库连接池的最小维持连接数
      initial-size: 5                                       # 初始化连接数
      max-total: 5                                          # 最大连接数
      max-wait-millis: 200
mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml        # mybatis配置文件所在路径
  type-aliases-package: com.atpl.springcloud.entities    # 所有Entity别名类所在包
  mapper-locations:
    - classpath:mybatis/mapper/**/*.xml

eureka:
  client: # 客户端注册进eureka服务列表内
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
      # 单机版 defaultZone: http://localhost:7001/eureka
  instance:
    instance-id: dept-8001.com # 自定义服务名名称信息
    prefer-ip-address: true
info: # 指定应用程序的元数据
  app.name: atpl-microservicecloud # 指定应用程序的名称,可以用来标识应用程序在整个系统中的作用
  company.name: www.atpl.com # 指定应用程序所属的公司名称
  build.artifactId: ${project.artifactId} # 指定应用程序的构件ID,通常是Maven项目中的artifactId
  build.version: ${project.version} #指定应用程序的版本号,通常是Maven项目中的version

测试:优先启动3344,他启动的过程bootstrap.yml会优先加载,会去GitHub上获取配置信息,然后启动7001服务注册中心,在启动8001客户端。

image-20230903182249473

浏览器发送请求:http://localhost:8001/dept/get/list

image-20230903183539197

http://eureka7001.com:7001/

image-20230903185337116

11、补充知识点:

11.1 Maven的几个命令的用处:

mvn clean:清除项目中的构建产物,例如target目录和生成的jar、war等文件。

mvn compile:编译项目中的Java源文件。

mvn test:运行项目中的测试用例。

mvn package:生成项目的可部署的打包文件,例如jar、war等。

mvn install:将项目的打包文件安装到本地Maven仓库中,以便其他项目可以使用该依赖。

mvn deploy:将项目的打包文件部署到远程Maven仓库中,以便其他项目可以使用该依赖。

mvn dependency:tree:打印项目依赖树,显示项目中所有的依赖关系。

mvn help:describe:列出Maven命令的详细信息和用法。

11.2 分布式数据库中的CAP原理(CAP+BASE)

11.2.1 传统的ACID分别是什么

ACID是传统关系型数据库Transaction处理的四个基本属性:

  • A : 原子性; 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • C : 一致性; 事务必须使数据库从一个一致性状态变换到另一个一致性状态。
  • I : 独立性; 事务的隔离性有多种级别,最常见的是READ-COMMITTED隔离级别,即一个事务内的更新在提交之前对其他事务是不可见的。
  • D : 持久性; 已提交的事务对数据库的修改应该永久保存在数据库中。
11.2.2 CAP

CAP 是分布式系统设计中需要考虑的三个指标:

  • C :强一致性;所有节点访问同一份最新的数据副本
  • A :可用性;非故障节点在合理的时间内返回合理的响应
  • P :分区容错性;系统还能正常工作即使发生网络分区

根据CAP定理,这三个指标不可能同时满足,最多只能同时满足其中两个,即3进2(要么AP要么CP要么CA,不可能三者兼得。P一定是占有的,因为网络可能出现延迟丢包等问题。)

CAP 理论的核心思想是:一个分布式系统不可能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)这三项属性。

在分布式系统设计中,不同系统可以根据业务需求,在AP和CP之间做不同的权衡:

  • 追求一致性和分区容错的系统,通常需要牺牲可用性,比如采用单主模式的数据库集群。
  • 追求可用性和分区容错的系统,通常需要牺牲一致性,实现异步复制和事件最终一致性,比如NoSQL数据库。
  • 追求一致性和可用性的系统,通常需要牺牲分区容错,采用单实例或者传统集群,比如Core Banking系统。

下面列举出一些场景使用的CAP组合:

  1. 对强一致性要求高的场景,如金融账务处理,应选择 CP。可以采用主备模式,牺牲系统可用性来保证一致性。
  2. 对高可用性要求高的场景,如电商网站,应选择 AP。可以使用 NoSQL 数据库,通过副本和异步复制实现高可用。
  3. 对网络分区故障敏感的场景,如分布式游戏,应选择 CP。要求系统在分区故障时牺牲可用性而不破坏一致性。
  4. 对实时性要求较高的场景,如即时通信,可选择 AP,通过快速响应保证可用性和实时性。
  5. 对临时不一致性可以容忍的场景,如非关键流量分析,可选择 AP,牺牲强一致来获得系统可用性。
  6. 基础设施类服务,如 DNS,注册中心等,需要选择 CP,确保服务地址等元数据一致性。
  7. 数据处理类系统,对一致性要求较低,可选择 AP,用重复计算等方式容忍短暂不一致。
11.2.3 经典CAP图

image-20230724220516901

11.2.4 BASE 是什么
11.2.5 分布式+集群简介
11.2.6 Eureka比Zookeeper好在哪里

Zookeeper与Eureka都是分布式系统中的重要协调组件,主要区别如下:

  1. 用途不同

    Zookeeper是一个协调服务,提供分布式锁、集群管理、元数据存储等功能。

    Eureka是一个服务注册中心,用于服务发现与注册。

  2. 数据存储方式不同

    Zookeeper采用属性结构存储数据,支持嵌套,适合保存配置、状态等复杂信息。

    Eureka采用扁平的Key-Value结构,主要存储服务实例信息。

  3. 节点角色不同

    Zookeeper节点通过Leader选举产生,节点角色对等。

    Eureka各节点地位平等,可独立对外提供服务。

  4. 一致性保证不同

    Zookeeper通过ZAB协议保证一致性。

    Eureka为了可用性,保证最终一致性。

  5. 容错机制不同

    Zookeeper通过Leader选举恢复可用性。

    Eureka通过自我保护机制防止网络分区(网络中的某些节点因为网络故障而无法与其他节点通信)是的误删。

  6. 语言不同

    Zookeeper使用Java语言实现。

    Eureka使用Java和Scala语言实现。

总体来说说,Zookeeper偏向于强一致性,Eureka偏向于高可用性。

11.3 @Mapper注解与@MapperScan注解

这两个注解标注的位置不一样,@Mapper注解标注在Mapper接口上,表示该接口为Mapper接口,Mybatis会自动扫描并注册该接口。

而@MapperScan是标注在主启动类上的注解,它是扫描某个包下所有的Mapper接口。这种适用于某个包下有很多Mapper接口。

11.4 application.yml与bootstrap.yml的区别

首先application.yml与bootstrap.yml都是SpringBoot应用程序中常用的配置文件,用于配置应用程序的属性和设置。

  1. application.yml:是主要的配置文件,定义程序的**一般(用户级别)**属性和设置。
  2. bootstrap.yml:是在应用程序启动阶段加载的配置文件,用于配置一些系统级别的属性和配置。它优先于application.yml加载,主要配置一些在程序启动前需要提前配置的属性,

SpringCloud在程序初始化过程中创建了一个‘Bootstrap Context’ 上下文,它充当了Spring应用的‘Application Context’的父上下文。初始化的时候,‘Bootstrap Context’负责从外部资源加载配置属性,并解析配置。这两个上下文共享一个从外部获取的‘Environment’环境。‘Bootstrap’ 属性有高优先级,默认情况下,他们不会被本地配置覆盖。‘Bootstrap Context’和‘Application Context’有着不同的约定。

所以新增了一个‘bootstrap.yml’文件,保证’Bootstrap Context’和’Application Context’配置的分离。

二、SpringCloud高阶篇

1. 前言

高阶篇的技术与初阶篇技术用较新版本的SpringBoot与SpringCloud:

下图是SpringCloud与SpringBoot的版本映射关系:

image-20230904215008548

具体版本,浏览器输入https://start.spring.io/actuator/info ,会返回一个JSON数据,来显示版本信息。

{
  "git": {
    "branch": "53acf2ef792d1b167c889ab4f19501c7acf730b5",
    "commit": {
      "id": "53acf2e",
      "time": "2023-08-29T07:26:28Z"
    }
  },
  "build": {
    "version": "0.0.1-SNAPSHOT",
    "artifact": "start-site",
    "versions": {
      "spring-boot": "3.1.3",
      "initializr": "0.20.1-SNAPSHOT"
    },
    "name": "start.spring.io website",
    "time": "2023-08-29T07:27:38.085Z",
    "group": "io.spring.start"
  },
  "bom-ranges": {
    "codecentric-spring-boot-admin": {
      "2.6.8": "Spring Boot >=2.6.0 and <2.7.0-M1",
      "2.7.4": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
      "3.0.4": "Spring Boot >=3.0.0-M1 and <3.1.0-M1",
      "3.1.5": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"
    },
    "hilla": {
      "2.1.4": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"
    },
    "sentry": {
      "6.28.0": "Spring Boot >=2.7.0 and <3.2.0-M1"
    },
    "solace-spring-boot": {
      "1.2.2": "Spring Boot >=2.6.0 and <3.0.0-M1",
      "2.0.0": "Spring Boot >=3.0.0-M1"
    },
    "solace-spring-cloud": {
      "2.3.2": "Spring Boot >=2.6.0 and <3.0.0-M1",
      "3.0.0": "Spring Boot >=3.0.0-M1"
    },
    "spring-cloud": {
      "2021.0.8": "Spring Boot >=2.6.0 and <3.0.0",
      "2022.0.4": "Spring Boot >=3.0.0 and <3.2.0-M1",
      "2023.0.0-M1": "Spring Boot >=3.2.0-M1 and <3.2.0-SNAPSHOT",
      "2023.0.0-SNAPSHOT": "Spring Boot >=3.2.0-SNAPSHOT"
    },
    "spring-cloud-azure": {
      "4.11.0": "Spring Boot >=2.6.0 and <3.0.0-M1",
      "5.5.0": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
    },
    "spring-cloud-gcp": {
      "3.6.3": "Spring Boot >=2.6.0 and <3.0.0-M1",
      "4.7.2": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
    },
    "spring-cloud-services": {
      "3.4.0": "Spring Boot >=2.6.0 and <2.7.0-M1",
      "3.5.0": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
      "4.0.3": "Spring Boot >=3.0.0 and <3.2.0-M1"
    },
    "spring-modulith": {
      "1.0.0": "Spring Boot >=3.1.0 and <3.2.0-M1"
    },
    "spring-shell": {
      "2.1.12": "Spring Boot >=2.7.0 and <3.0.0-M1",
      "3.0.7": "Spring Boot >=3.0.0 and <3.1.0-M1",
      "3.1.3": "Spring Boot >=3.1.0 and <3.2.0-M1",
      "3.2.0-M1": "Spring Boot >=3.2.0-M1"
    },
    "vaadin": {
      "23.2.15": "Spring Boot >=2.6.0 and <2.7.0-M1",
      "23.3.21": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
      "24.0.13": "Spring Boot >=3.0.0-M1 and <3.1.0-M1",
      "24.1.7": "Spring Boot >=3.1.0-M1 and <3.2.0-M1"
    },
    "wavefront": {
      "2.2.2": "Spring Boot >=2.6.0 and <2.7.0-M1",
      "2.3.4": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
      "3.0.1": "Spring Boot >=3.0.0-M1 and <3.1.0-M1"
    }
  },
  "dependency-ranges": {
    "okta": {
      "2.1.6": "Spring Boot >=2.6.0 and <3.0.0-M1",
      "3.0.5": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
    },
    "mybatis": {
      "2.2.2": "Spring Boot >=2.6.0 and <2.7.0-M1",
      "2.3.1": "Spring Boot >=2.7.0-M1 and <3.0.0-M1",
      "3.0.2": "Spring Boot >=3.0.0-M1"
    },
    "pulsar": {
      "0.2.0": "Spring Boot >=3.0.0 and <3.2.0-M1"
    },
    "pulsar-reactive": {
      "0.2.0": "Spring Boot >=3.0.0 and <3.2.0-M1"
    },
    "camel": {
      "3.14.9": "Spring Boot >=2.6.0 and <2.7.0-M1",
      "3.20.6": "Spring Boot >=2.7.0.M1 and <3.0.0-M1",
      "4.0.0": "Spring Boot >=3.0.0-M1 and <3.2.0-M1"
    },
    "picocli": {
      "4.7.4": "Spring Boot >=2.6.0 and <3.1.0-M1"
    }
  }
}

2. 高阶篇技术选型:

  • 开发工具:IntelliJ IDEA 2023.1.3

  • SpringCloud:Hoxton.SR1

  • SpringBoot:2.2.2.RELEASE

  • SpringCloud Alibaba :2.1.0.RELEASE

  • Java :Java8

  • Maven:3.5及以上

  • MySQL:5.7及以上

3. 实战

在实战过程中,出现的BUG或者坑都会在’‘知识拓展’'小节说明

3.1 搭建父工程

  • IDEA字符编码统一为UTF-8

  • 创建一个Maven项目

image-20230905231206886

  • pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atpl.springcloud</groupId>
    <artifactId>springcloud02</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <!-- dependencyManagement:集中管理依赖的版本号,当其他子模块引入想用的依赖时,不需要在指定版本号 -->
    <dependencyManagement>
        <!-- 注意:dependencyManagement里的依赖可能下载不下来,需要将下载不下来的依赖放到改标签外下载好,在放到该标签里
                    也可以不处理,在子工程需要使用相同的依赖时,会自动下载-->
        <dependencies>
            <!--spring boot 2.2.2-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud Hoxton.SR1-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud alibaba 2.1.0.RELEASE-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <!-- 该插件的版本与SpringBoot的版本对应 -->
                <version>2.2.2.RELEASE</version>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2 REST微服务工程构建

3.2.1 创建8001模块工程

pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.atpl.springcloud</groupId>
        <artifactId>springcloud02</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cloud-provider-payment8001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!--包含了sleuth+zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <!--<dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--mysql-connector-java-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
3.2.2 创建逆向工程模块

pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.atpl.springcloud</groupId>
        <artifactId>springcloud02</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>generator</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- 依赖 MyBatis 核心包 -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.4</version>
            </dependency>
    </dependencies>
    <!-- 控制 Maven 在构建过程中相关配置 -->
    <build>
        <!-- 构建过程中用到的插件 -->
        <plugins>
            <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.0</version>
                <!-- 插件的依赖 -->
                <dependencies>
                    <!-- 逆向工程的核心依赖 -->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.5</version>
                    </dependency>
                    <!-- 数据库连接池 -->
                    <dependency>
                        <groupId>com.mchange</groupId>
                        <artifactId>c3p0</artifactId>
                        <version>0.9.2</version>
                    </dependency>
                    <!-- MySQL 驱动 -->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.47</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

generatorConfig.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!-- mybatis-generator:generate -->
    <context id="atcplTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是;false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/springcloud02"
                        userId="root" password="root">
        </jdbcConnection>
        <!-- 默认 false,把 JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true 时把
        JDBC DECIMAL
        和 NUMERIC 类型解析为 java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!-- targetProject:生成 Entity 类的路径 -->
        <javaModelGenerator targetProject=".\src\main\java"
                            targetPackage="com.plcao.springcloud.entities">
            <!-- enableSubPackages:是否让 schema 作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- targetProject:XxxMapper.xml 映射文件生成的路径 -->
        <sqlMapGenerator targetProject=".\src\main\resources" targetPackage="mapper">
            <!-- enableSubPackages:是否让 schema 作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>
        <!-- targetPackage:Mapper 接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER" targetProject=".\src\main\java" targetPackage="com.plcao.springcloud.dao">
            <!-- enableSubPackages:是否让 schema 作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>
        <!-- 数据库表名字和我们的 entity 类对应的映射指定 -->
        <!--<table tableName="inner_account_type_cert" domainObjectName="AccountTypeCert"/>-->
        <!--<table tableName="t_account_type" domainObjectName="AccountType"/>-->

        <table tableName="payment" domainObjectName="Payment"></table>

    </context>
</generatorConfiguration>
3.2.3 开启热部署:

父工程pom文件加入:

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <!-- 该插件的版本与SpringBoot的版本对应 -->
      <version>2.2.2.RELEASE</version>
      <configuration>
        <fork>true</fork>
        <addResources>true</addResources>
      </configuration>
    </plugin>
  </plugins>
</build>

要使用热部署的工程pom文件:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
  <scope>runtime</scope>
  <optional>true</optional>
</dependency>

设置IDEA:

image-20230907203417107

然后在要开启热部署的工程的pom文件中:ctrl+shift+alt+/

image-20230907204009572

选中如下图:

image-20230907204112985

3.2.4 创建80模块工程

pom文件:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

该工程需要访问8001服务,需要使用到RestTemplate

介绍RestTemplate:

RestTemplate是Spring提供的一个类,用于进行HTTP通信和访问RESTful Web服务。

创建配置类:

@Configuration
public class ApplicationContextConf {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}

客户端访问8001:

@RestController
@Slf4j
public class OrderController {

    private static final String PAYMENT_URI = "http://127.0.0.1:8001";

    @Autowired
    RestTemplate restTemplate;


    @GetMapping("/consumer/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URI+"/get/payment/+"+id,CommonResult.class);
    }

    @GetMapping("/consumer/insert")
    public CommonResult<Payment> insertPayment(@RequestBody Payment payment){
        return restTemplate.postForObject(PAYMENT_URI+"/insert/payment",payment ,CommonResult.class);
    }
}

测试:POSTMAN发送请求后,可以得到响应请求的响应。

image-20230910162942819

3.2.5 重构项目

80模块和8001模块都有重复的代码,将其提取成公共部分:

创建cloud-api-common模块:

image-20230909124818532

pom文件

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>

使用maven命令将该模块进行install,然后在需要用到的模块的pom文件中引入该模块。

即现在80和8001的pom文件都要加上:

      <dependency>
        <groupId>com.atpl.springcloud</groupId>
        <artifactId>cloud-api-common</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>

进行复测,结果显示跟重构前一样。

3.2.6 创建7001服务注册中心工程

pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.atpl.springcloud</groupId>
        <artifactId>springcloud02</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>cloud-eureka-server7001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

application.yml文件

server:
  port: 7001
eureka:
  instance:
    hostname: eureka7001.com #服务端实例名称, eureka7001.com已在hosts文件中配置映射了
  client:
    fetch-registry: false #自己就是服务中心,不需要检索服务
    register-with-eureka: false #不想注册中心注册自己
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

主启动类:

image-20230910163241136

修改8001、80模块工程的主启动类:

image-20230910163423142

image-20230910163502963

启动7001工程,再启动80、8001工程,浏览器访问http://localhost:7001,如下图所示,说明80、8001已经注册进注册中心。

3.2.7 搭建服务集群和服务提供集群

创建7002、7003服务注册工程,与7001类似,配置文件需要修改一下:

7001:application.yml

server:
  port: 7001
eureka:
  instance:
    hostname: eureka7001.com #服务端实例名称
  client:
    fetch-registry: false #自己就是服务中心,不需要检索服务
    register-with-eureka: false #不想注册中心注册自己
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

7002:application.yml

server:
  port: 7002
eureka:
  instance:
    hostname: eureka7002.com
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/

7003:application.yml

server:
  port: 7003
eureka:
  instance:
    hostname: eureka7003.com
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

创建8002服务提供工程,与8001类似,区别端口号即可:

server:
  port: 8002
spring:
  application:
    name: cloud-payment-service #服务名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://127.0.0.1:3306/springcloud02?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.plcao.springcloud.entities #实体类所在包,配置别名

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  instance: 
    prefer-ip-address: true
    instance-id: provider8002

启动7001、7002、7003、80、8001,浏览器访问eureka7001.com:7001eureka7002.com:7002eureka7003.com:7003如下图所示:

image-20230910162456691

上图中有两处标识:红色是没有配置,虽然不影响程序,但是在实际开发中,数不允许出现主街名称的,一般都是ip

微服务信息完善:

instance: 
    prefer-ip-address: true
    instance-id: provider8002 #访问路径可以看见ip地址

修改:ApplicationContextConf.java

image-20230910170243142

修改:80客户端的Controller

image-20230910170346710

因为服务提供搭建了集群,客户端不应该是再把访问的服务提供的端口写死,而是写成服务提供的实例名称,但是服务集群中有多个服务提供,客户端不知道具体访问哪个,所以需要开启RestTemplate的负载均衡。

3.2.8 服务发现 DiscorveryClient

这里只简单测试一下,详细内容请看后续章节。在这里只拿8001服务做测试。

@DiscoveryClient注解可以将服务注册到服务发现组建中,它允许服务实例注册自己并提供有关其位置和可用性信息,以便其他服务可以动态地发现和调用他们。

该注解通常用于服务提供者,它会告诉SpringCloud将当前服务注册到服务发现组件中。

主启动类上加上注解:@DiscoveryClient

image-20230910172859184

业务逻辑:

image-20230910173930589

启动服务,浏览器访问http://localhost:8001/payment/discovery,可以看到控制台打印信息:

image-20230910174214226

到这Eureka基本已经都涉及到了,但是Eureka官网已经停更了。下文将选用Zookeeper作为注册中心技术。

Zookeeper相关教程连接:

3.2.9 新建8004微服务模块

使用Zookeeper作为服务注册中心

4. 知识拓展

4.1 Maven相关知识

pom文件中的dependencyManagement dependencies dependency 的区别:

Maven使用dependencyManagement 元素集中管理项目中的依赖版本号。通常会在项目的最顶层的父工程POM文件中看到。它允许您在一个地方定义依赖项及其版本,然后在项目的其他模块中引用这些依赖项,而无需在每个模块中重复定义版本号。通过使用 dependencyManagement,您可以确保项目中的所有模块使用相同的依赖版本,从而提供了更好的一致性和可维护性。

Maven会沿着父子增次向上走,指导找到一个拥有dependencyManagement 元素的项目,然后他就会使用这个元素中指定的版本号。

例如在父工程中:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
          // ...

然后在子工程中:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

如果不在子工程中声明以来,是不会从父项目中继承下来的;只有在子项目中写了该依赖并没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom。也就是说只要子工程不指定版本号,子工程就会与父工程的版本号保持一致。如果子工程的某个依赖需要用到别的版本,只需要声明版本即可。

!!!注意:dependencyManagement 标签里知识声明依赖,并不是引入以来,因此子工程需要显式的声明需要的依赖。

例如:要导入的新以来在本地库没有,那么该依赖在dependencyManagement 标签中就会爆红,只需要将该依赖拿到dependencyManagement 标签外引入后再放进改标签即可。

跳过单元测试:

image-20230905230843171

locations: classpath:mapper/*.xml
type-aliases-package: com.plcao.springcloud.entities #实体类所在包,配置别名

eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
instance:
prefer-ip-address: true
instance-id: provider8002


启动7001、7002、7003、80、8001,浏览器访问`eureka7001.com:7001`、`eureka7002.com:7002`、`eureka7003.com:7003`如下图所示:

[外链图片转存中...(img-RedL8PVB-1701690304462)]

上图中有两处标识:红色是没有配置,虽然不影响程序,但是在实际开发中,数不允许出现主街名称的,一般都是ip

微服务信息完善:

```yml
instance: 
    prefer-ip-address: true
    instance-id: provider8002 #访问路径可以看见ip地址

修改:ApplicationContextConf.java

[外链图片转存中…(img-VEa0dDhU-1701690304463)]

修改:80客户端的Controller

[外链图片转存中…(img-B46Murwm-1701690304464)]

因为服务提供搭建了集群,客户端不应该是再把访问的服务提供的端口写死,而是写成服务提供的实例名称,但是服务集群中有多个服务提供,客户端不知道具体访问哪个,所以需要开启RestTemplate的负载均衡。

3.2.8 服务发现 DiscorveryClient

这里只简单测试一下,详细内容请看后续章节。在这里只拿8001服务做测试。

@DiscoveryClient注解可以将服务注册到服务发现组建中,它允许服务实例注册自己并提供有关其位置和可用性信息,以便其他服务可以动态地发现和调用他们。

该注解通常用于服务提供者,它会告诉SpringCloud将当前服务注册到服务发现组件中。

主启动类上加上注解:@DiscoveryClient

[外链图片转存中…(img-YwW8bwN9-1701690304465)]

业务逻辑:

[外链图片转存中…(img-CSDPsf1P-1701690304466)]

启动服务,浏览器访问http://localhost:8001/payment/discovery,可以看到控制台打印信息:

[外链图片转存中…(img-BYhlgALo-1701690304467)]

到这Eureka基本已经都涉及到了,但是Eureka官网已经停更了。下文将选用Zookeeper作为注册中心技术。

Zookeeper相关教程连接:

3.2.9 新建8004微服务模块

使用Zookeeper作为服务注册中心

4. 知识拓展

4.1 Maven相关知识

pom文件中的dependencyManagement dependencies dependency 的区别:

Maven使用dependencyManagement 元素集中管理项目中的依赖版本号。通常会在项目的最顶层的父工程POM文件中看到。它允许您在一个地方定义依赖项及其版本,然后在项目的其他模块中引用这些依赖项,而无需在每个模块中重复定义版本号。通过使用 dependencyManagement,您可以确保项目中的所有模块使用相同的依赖版本,从而提供了更好的一致性和可维护性。

Maven会沿着父子增次向上走,指导找到一个拥有dependencyManagement 元素的项目,然后他就会使用这个元素中指定的版本号。

例如在父工程中:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
          // ...

然后在子工程中:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

如果不在子工程中声明以来,是不会从父项目中继承下来的;只有在子项目中写了该依赖并没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom。也就是说只要子工程不指定版本号,子工程就会与父工程的版本号保持一致。如果子工程的某个依赖需要用到别的版本,只需要声明版本即可。

!!!注意:dependencyManagement 标签里知识声明依赖,并不是引入以来,因此子工程需要显式的声明需要的依赖。

例如:要导入的新以来在本地库没有,那么该依赖在dependencyManagement 标签中就会爆红,只需要将该依赖拿到dependencyManagement 标签外引入后再放进改标签即可。

跳过单元测试:

[外链图片转存中…(img-eEOpjgLN-1701690304469)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值