## 一、SpringCloud简介 #### 1、概念 Spring Cloud 提供一站式的微服务架构解决方案。也就是说:Spring Cloud 提供了一整套的微服务架构解决方案:服务注册与发现,服务调用,服务保护与熔断,网关,统一消息中间件、配置管理、链路追踪等。 SpringCloud是在SpringBoot的基础上,增加了很多微服务相关的规范。目前,SpringCloud规范已经由Spring官方、SpringCloud Netflix、SpringCloud Alibaba等实现。通过组件化的方式,SpringCloud将这些实现整合起来,构成全家桶式的微服务技术栈。 #### 2、SpringCloud与SpringBoot的关系 Spring Cloud 强烈依赖于 Spring Boot。 * **基础框架**:Spring Cloud 的所有组件(如 Eureka Client, Config Client, Gateway 等)本身都是独立的 Spring Boot 应用程序。它们通过 `@SpringBootApplication` 启动,遵循 Spring Boot 的约定和配置方式。 * **简化配置**:Spring Cloud 利用 Spring Boot 的自动配置机制和 Starter 理念,提供了自己的 `spring-cloud-starter-*` 依赖(如 `spring-cloud-starter-netflix-eureka-client`)。你只需要引入这些 Starter,大部分复杂的配置(如连接注册中心、启用熔断器等)就自动完成了。 * **统一体验**:开发者可以使用相同的开发、测试和部署方式来对待 Spring Cloud 中的每个微服务,因为它们都是基于 Spring Boot 的。这大大降低了学习和开发成本。 #### 3、SpringCloud体系架构图 (图片来自Nuesoft)  1. 基于RestTemplate的微服务调用 2. Eureka服务注册与发现,Eureka高可用集群 3. 基于HTTP的客户端负载均衡器Ribbon,在Ribbon基础上的改进Feign组件 4. Hystrix延迟和容错 5. 基于Spring Cloud Gateway的微服务网关 6. 基于Spring Cloud Config的集中配置管理 7. 基于Spring Cloud Bus的配置刷新 ## 二、微服务调用 在单体应用中,模块间调用是简单的方法调用。但在微服务中,服务是独立的、网络隔离的进程。因此,微服务调用的本质是**网络通信**。 Spring Cloud 的核心贡献在于,它将复杂的网络调用(包括服务发现、负载均衡、容错等)封装起来,让开发者能够以**类似本地方法调用**的体验进行远程服务调用。 #### 1、主要角色与要素 **服务消费者**:需要调用其他服务的应用程序。 **服务提供者**:提供接口供其他服务调用的应用程序。 **服务注册中心**:所有服务实例都会向这里注册自己的元数据(如服务名、IP、端口)。服务消费者从这里查询服务提供者的网络地址。代表组件:**Eureka**, **Nacos**, **Consul**。 **负载均衡器**:当一个服务有多个实例时,负载均衡器决定将请求分发到哪一个健康的实例上。这通常发生在客户端(即服务消费者一侧)。 #### 2、调用方式 ##### 方式一:RestTemplate + @LoadBalanced 这是 Spring Cloud 早期的主流方式。 - **RestTemplate**:是 Spring 提供的用于同步 REST 调用的模板类。 - **@LoadBalanced**:这是一个关键注解。当你使用它来修饰一个 `RestTemplate` 的 Bean 时,它就从一个普通的 REST 客户端转变为了一个具有**客户端负载均衡**能力的客户端。 **工作原理:** * 服务提供者启动,向注册中心(如 Eureka)注册。 * 服务消费者使用被 `@LoadBalanced` 注解的 `RestTemplate`。 * 当消费者使用 `restTemplate.getForObject("http://USER-SERVICE/user/1", User.class)` 进行调用时: - **拦截**:Spring Cloud 会拦截这个请求。 - **服务发现**:从 URL 中的服务名 `USER-SERVICE` 出发,向注册中心查询该服务所有可用的实例列表。 - **负载均衡**:根据内置的负载均衡算法(默认为轮询),从实例列表中选择一个。 - **重构请求**:将原始的 `http://USER-SERVICE/user/1` 重构成真实的实例地址,如 `http://192.168.1.100:8080/user/1`。 - **发起调用**:最后,由 `RestTemplate` 向这个真实地址发起 HTTP 请求。 ##### 方式二:OpenFeign OpenFeign 是一个声明式的、模板化的 HTTP 客户端。它让远程调用更加优雅,就像调用本地接口一样。 - **声明式**:你只需要定义一个接口,并使用注解来描述需要调用的远程服务信息(如服务名、API路径、参数等)。Feign 会在运行时为你自动实现这个接口。 - **集成度更高**:它天然集成了 Ribbon(负载均衡)和 Hystrix(熔断器,虽然新版本中已不推荐,但可集成其他容错组件),开箱即用。 **工作原理:** * 在启动时,Feign 会扫描所有被 `@FeignClient` 注解的接口。 * 为这些接口创建动态代理对象,并注入到 Spring 容器中。 * 当你在代码中调用接口方法时,实际上调用的是动态代理对象。 * 代理对象会: - 根据注解信息(如 `@RequestMapping`)构建出 HTTP 请求的 URL 和参数。 - 结合 Ribbon,通过服务名从注册中心获取实例列表并进行负载均衡。 - 最终发起 HTTP 请求并处理响应。 #### 3、创建多模块项目 ##### Maven **创建父级项目** 主要用来提供统一的依赖版本信息,创建一个普通的Maven项目,不要选择任何骨架 构建完成后进入项目删除`/src`目录并修改`pom.xml`配置,完成后重新构建项目 ```xml pom 17 2025.0.0 3.5.8 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.boot spring-boot-maven-plugin ``` **注意**:不同的JDK版本需要修改不同的SpringBoot版本,不同的SpringBoot版本对应不同的SpringCloud版本。 后续添加子模块仅需要在左侧项目文件树中右键父模块后,选择新建模块  需要注意的是新建的模块父项必须为父模块 ##### Gradle #### 4、Eureka服务 ##### 新建子模块 名为`eureka-server`,子模块文件结构  ##### 修改`pom.xml`文件 添加依赖 ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-server org.springframework.boot spring-boot-devtools runtime true ``` ##### 创建`EurekaApplication.java` ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } } ``` **注意**:`@EnableEurekaServer`是用来启动Eureka服务,必须添加。 ##### 创建配置文件 在资源目录下创建`application.yml` ```yml server: port: 13000 #eureka配置 eureka: instance: hostname: localhost #Eureka Server的主机名(单机版写法) client: register-with-eureka: false #是否将自己注册到eureka 服务当中(默认true). fetch-registry: false #是否启用从注册中心拉取服务列表的功能(默认true). service-url: #Eureka Server提供给客户端的访问地址(要加上/eureka/,不然会与原本服务信息页面冲突)。 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ server: enable-self-preservation: true #开启自我保护模式(生产环境建议开启) eviction-interval-timer-in-ms: 60000 #清理无效节点的时间间隔(毫秒),默认60秒 ``` **注意**: - 默认情况下,Eureka在90秒内没有接收到某个微服务节点的心跳,就会认为该微服务节点已宕机,并将其从服务列表中剔除。 - 但是,当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为就会非常危险:因为微服务本身是健康的,此时不应该注销这个微服务。 - Eureka通过自我保护机制来解决这个问题。当Eureka Server的某节点发现在短时间内丢失过多客户端时(可能发生了网络分区故障),Eureka会将当前的实例注册信息保护起来,不在注销任何微服务。 - 具体来说:如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制(在单机状态下很容易进入自我保护机制)。此时,Eureka会将当前的实例注册信息保护起来,同时提示下图警告。 - 当网络故障恢复后,该Eureka Server节点会自动退出自我保护机制。  在开发环境可以将其设置为`false` ```yml #eureka server配置 eureka: client: ... server: enable-self-preservation: false #关闭自我保护机制 ``` ##### 启动项目 打开`http://localhost:13000`可以看到下图,表明服务启动成功  #### 5、服务提供者 ##### 新建子模块 名为`provider`,子模块文件结构  ##### 修改`pom.xml` 添加依赖 ```xml org.springframework.boot spring-boot-starter-web org.projectlombok lombok annotationProcessor org.springframework.boot spring-boot-devtools runtime true org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` ##### 创建`ProviderApplication.java` ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } } ``` **注意**: 从SpringCloud的`Edgware`版本开始,工程中只要添加了`Eureka Client`依赖,就成为`Eureka Client`工程。已经不需要在主启动类上配置`@EnableEurekaClient`或`@EnableDiscoveryClient` 注解了。 ##### 创建配置文件 在资源目录下创建`application.yml` ```yml spring: application: name: provider server: port: 11000 #eureka配置 eureka: client: serviceUrl: #eureka服务端地址 defaultZone: http://localhost:13000/eureka/ instance: #使用ip地址向Eureka注册(默认为主机名) prefer-ip-address: true #上面的配置已经可以使用ip注册了,但显示的还是主机名,所以这里设置显示的注册名 instance-id: ${spring.cloud.client.ip-address}:${server.port} ``` ##### 创建`po/User.java` ```java import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer userId; private String userName; private String password; } ``` ##### 创建`po/CommonResult.java` ```java import lombok.Data; import java.io.Serializable; @Data public class CommonResult implements Serializable { private Integer code; private String message; private T result; public static CommonResult success(T result) { CommonResult commonResult = new CommonResult<>(); commonResult.setCode(200); commonResult.setMessage("操作成功"); commonResult.setResult(result); return commonResult; } public static CommonResult failed(String message) { CommonResult commonResult = new CommonResult<>(); commonResult.setCode(500); commonResult.setMessage(message); return commonResult; } public static CommonResult failed() { return failed("操作失败"); } } ``` ##### 创建`controller/UserController.java` ```java import cn.doooog.provider.po.CommonResult; import cn.doooog.provider.po.User; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/user") @RestController public class UserController { @RequestMapping("/id/{id}") public CommonResult getUserById(@PathVariable("id") Integer id) { //模拟返回业务数据 return CommonResult.success(new User(id, "张三", "123")); } } ``` ##### 启动项目 打开`http://localhost:13000`可以看到注册服务增加了  #### 6、服务消费者 ##### 新建子模块 名为`consumer`,子模块文件结构  ##### 修改`pom.xml` 添加依赖 ```xml org.springframework.boot spring-boot-starter-web org.projectlombok lombok annotationProcessor org.springframework.boot spring-boot-devtools runtime true org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` ##### 创建`ConsumerApplication.java` ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class ConsumerApplication { /** * 使用spring提供的RestTemplate发送http请求调用微服务 * 这里创建RestTemplate对象交给容器管理 */ @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } } ``` **注意**: 从SpringCloud的`Edgware`版本开始,工程中只要添加了`Eureka Client`依赖,就成为`Eureka Client`工程。已经不需要在主启动类上配置`@EnableEurekaClient`或`@EnableDiscoveryClient` 注解了。 ##### 创建配置文件 在资源目录下创建`application.yml` ```yml spring: application: name: consumer server: port: 12000 #eureka配置 eureka: client: serviceUrl: #eureka服务端地址 defaultZone: http://localhost:13000/eureka/ instance: #使用ip地址向Eureka注册(默认为主机名) prefer-ip-address: true #上面的配置已经可以使用ip注册了,但显示的还是主机名,所以这里设置显示的注册名 instance-id: ${spring.cloud.client.ip-address}:${server.port} ``` ##### 创建`po/CommonResult.java` ```java import lombok.Data; import java.io.Serializable; @Data public class CommonResult implements Serializable { private Integer code; private String message; private T result; public static CommonResult success(T result) { CommonResult commonResult = new CommonResult<>(); commonResult.setCode(200); commonResult.setMessage("操作成功"); commonResult.setResult(result); return commonResult; } public static CommonResult failed(String message) { CommonResult commonResult = new CommonResult<>(); commonResult.setCode(500); commonResult.setMessage(message); return commonResult; } public static CommonResult failed() { return failed("操作失败"); } } ``` ##### 创建`controller/TestController.java` ```java import cn.doooog.consumer.po.CommonResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RequestMapping("/test") @RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @RequestMapping("/id/{id}") public CommonResult test(@PathVariable("id") Integer id) { // 通过服务提供者名(provider)获取Eureka Server上的元数据 List instanceList = discoveryClient.getInstances("PROVIDER"); // 获取服务提供者的元数据 ServiceInstance serviceInstance = instanceList.get(0); return restTemplate.getForObject( "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/id/" + id, CommonResult.class); } } ``` ##### 启动项目 打开`http://localhost:13000`可以看到注册服务增加了  访问`http://localhost:12000/test/id/112312`  可以看到消费者调用提供者接口成功。 ## 三、Eureka高可用集群 #### 1、什么是高可用 高可用(High Availability)是分布式系统架构设计中必须要考虑的因素之一。它通常是指:通过设计减少系统不能提供服务的时间。 在高并发的网络环境中,单节点是系统高可用的大敌,应该在系统设计中避免单节点。高可用保证的原则是“集群化”,或者叫“冗余”。有了集群化,就可以保证在某个节点宕机后,集群中的其它节点能够继续提高服务。 高可用衡量指标,业界用N个9来量化可用性: | **描述** | **通俗叫法** | **可用性级别** | **年度停机时间** | | ---------------------------- | ------------ | -------------- | ---------------- | | 基本可用性 | 2个9 | 99% | 87.6小时 | | 较高可用性 | 3个9 | 99.9% | 8.8小时 | | 具有故障自动恢复能力的可用性 | 4个9 | 99.99% | 53分钟 | | 极高可用性 | 5个9 | 99.999% | 5分钟 | `Eureka Server`可以通过运行`多个实例`并`相互注册`的方式实现高可用部署,Eureka Server实例会彼此同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server的默认行为。 #### 2、修改`host`文件 在Eureka集群中,每一个服务都是以自身IP作为区分,但在开发单机环境中,无法实现多IP,所以可以通过修改`host`文件来实现访问不同网络地址(host)映射到同一个地址中`127.0.0.1` > [!NOTE] > > Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开。其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址。如果没有找到,则系统会将网址提交DNS域名解析服务器进行IP地址的解析。 打开本机的 `C:\Windows\System32\drivers\etc\hosts` 文件,添加映射信息 ```yml ## springcloud 配置 127.0.0.1 eurekaServer13000 127.0.0.1 eurekaServer13001 ``` **注意**:windows系统下hosts文件内容不区分大小写;Linux系统下区分大小写;不能写localhost,因为localhost是本机域名,不是一个IP地址。 #### 3、创建集群 在同一集群中需要保证`Eureka`服务器的配置一模一样 首先修改原本`eureka-server`的配置文件 ```yml server: port: 13000 #eureka配置 eureka: instance: hostname: eurekaServer13000 #集群版写法 client: register-with-eureka: false #是否将自己注册到eureka 服务当中(默认true). fetch-registry: false #是否启用从注册中心拉取服务列表的功能(默认true). service-url: #这是集群版写法,13000注册给13001;反之,13001要注册给13000. defaultZone: http://eurekaServer13001:13001/eureka/ server: enable-self-preservation: true #开启自我保护模式(生产环境建议开启) eviction-interval-timer-in-ms: 60000 #清理无效节点的时间间隔(毫秒),默认60秒 ``` 复制成第二个服务模块`eureka-server13001`  修改第二服务的配置 ```yml server: port: 13001 #eureka配置 eureka: instance: hostname: eurekaServer13001 #集群版写法 client: register-with-eureka: false #是否将自己注册到eureka 服务当中(默认true). fetch-registry: false #是否启用从注册中心拉取服务列表的功能(默认true). service-url: #这是集群版写法,13001注册给13000;反之,13000要注册给13001. defaultZone: http://eurekaServer13000:13000/eureka/ server: enable-self-preservation: true #开启自我保护模式(生产环境建议开启) eviction-interval-timer-in-ms: 60000 #清理无效节点的时间间隔(毫秒),默认60秒 ``` #### 4、启动集群 在两个服务详细页面中会有各自的集群记录  #### 5、将微服务注册到集群 将服务提供者/消费者配置中的`defaultZone`添加为多个,用`,`分割 ```yml eureka: client: serviceUrl: defaultZone: http://localhost:13000/eureka/,http://localhost:13001/eureka/ ``` 实际上,由于Eureka Server集群的各个节点相互注册后,服务只需要注册到集群中的某一个节点上,就相当于注册到集群中的所有节点上。 但是,当服务只注册到集群中的某一个节点上时,如果这个节点宕机了,那么该服务将无法注册。所以,实际开发中,都会将服务注册到集群中的所有节点上。 Loading... ## 一、SpringCloud简介 #### 1、概念 Spring Cloud 提供一站式的微服务架构解决方案。也就是说:Spring Cloud 提供了一整套的微服务架构解决方案:服务注册与发现,服务调用,服务保护与熔断,网关,统一消息中间件、配置管理、链路追踪等。 SpringCloud是在SpringBoot的基础上,增加了很多微服务相关的规范。目前,SpringCloud规范已经由Spring官方、SpringCloud Netflix、SpringCloud Alibaba等实现。通过组件化的方式,SpringCloud将这些实现整合起来,构成全家桶式的微服务技术栈。 #### 2、SpringCloud与SpringBoot的关系 Spring Cloud 强烈依赖于 Spring Boot。 * **基础框架**:Spring Cloud 的所有组件(如 Eureka Client, Config Client, Gateway 等)本身都是独立的 Spring Boot 应用程序。它们通过 `@SpringBootApplication` 启动,遵循 Spring Boot 的约定和配置方式。 * **简化配置**:Spring Cloud 利用 Spring Boot 的自动配置机制和 Starter 理念,提供了自己的 `spring-cloud-starter-*` 依赖(如 `spring-cloud-starter-netflix-eureka-client`)。你只需要引入这些 Starter,大部分复杂的配置(如连接注册中心、启用熔断器等)就自动完成了。 * **统一体验**:开发者可以使用相同的开发、测试和部署方式来对待 Spring Cloud 中的每个微服务,因为它们都是基于 Spring Boot 的。这大大降低了学习和开发成本。 #### 3、SpringCloud体系架构图 (图片来自Nuesoft)  1. 基于RestTemplate的微服务调用 2. Eureka服务注册与发现,Eureka高可用集群 3. 基于HTTP的客户端负载均衡器Ribbon,在Ribbon基础上的改进Feign组件 4. Hystrix延迟和容错 5. 基于Spring Cloud Gateway的微服务网关 6. 基于Spring Cloud Config的集中配置管理 7. 基于Spring Cloud Bus的配置刷新 ## 二、微服务调用 在单体应用中,模块间调用是简单的方法调用。但在微服务中,服务是独立的、网络隔离的进程。因此,微服务调用的本质是**网络通信**。 Spring Cloud 的核心贡献在于,它将复杂的网络调用(包括服务发现、负载均衡、容错等)封装起来,让开发者能够以**类似本地方法调用**的体验进行远程服务调用。 #### 1、主要角色与要素 **服务消费者**:需要调用其他服务的应用程序。 **服务提供者**:提供接口供其他服务调用的应用程序。 **服务注册中心**:所有服务实例都会向这里注册自己的元数据(如服务名、IP、端口)。服务消费者从这里查询服务提供者的网络地址。代表组件:**Eureka**, **Nacos**, **Consul**。 **负载均衡器**:当一个服务有多个实例时,负载均衡器决定将请求分发到哪一个健康的实例上。这通常发生在客户端(即服务消费者一侧)。 #### 2、调用方式 ##### 方式一:RestTemplate + @LoadBalanced 这是 Spring Cloud 早期的主流方式。 - **RestTemplate**:是 Spring 提供的用于同步 REST 调用的模板类。 - **@LoadBalanced**:这是一个关键注解。当你使用它来修饰一个 `RestTemplate` 的 Bean 时,它就从一个普通的 REST 客户端转变为了一个具有**客户端负载均衡**能力的客户端。 **工作原理:** * 服务提供者启动,向注册中心(如 Eureka)注册。 * 服务消费者使用被 `@LoadBalanced` 注解的 `RestTemplate`。 * 当消费者使用 `restTemplate.getForObject("http://USER-SERVICE/user/1", User.class)` 进行调用时: - **拦截**:Spring Cloud 会拦截这个请求。 - **服务发现**:从 URL 中的服务名 `USER-SERVICE` 出发,向注册中心查询该服务所有可用的实例列表。 - **负载均衡**:根据内置的负载均衡算法(默认为轮询),从实例列表中选择一个。 - **重构请求**:将原始的 `http://USER-SERVICE/user/1` 重构成真实的实例地址,如 `http://192.168.1.100:8080/user/1`。 - **发起调用**:最后,由 `RestTemplate` 向这个真实地址发起 HTTP 请求。 ##### 方式二:OpenFeign OpenFeign 是一个声明式的、模板化的 HTTP 客户端。它让远程调用更加优雅,就像调用本地接口一样。 - **声明式**:你只需要定义一个接口,并使用注解来描述需要调用的远程服务信息(如服务名、API路径、参数等)。Feign 会在运行时为你自动实现这个接口。 - **集成度更高**:它天然集成了 Ribbon(负载均衡)和 Hystrix(熔断器,虽然新版本中已不推荐,但可集成其他容错组件),开箱即用。 **工作原理:** * 在启动时,Feign 会扫描所有被 `@FeignClient` 注解的接口。 * 为这些接口创建动态代理对象,并注入到 Spring 容器中。 * 当你在代码中调用接口方法时,实际上调用的是动态代理对象。 * 代理对象会: - 根据注解信息(如 `@RequestMapping`)构建出 HTTP 请求的 URL 和参数。 - 结合 Ribbon,通过服务名从注册中心获取实例列表并进行负载均衡。 - 最终发起 HTTP 请求并处理响应。 #### 3、创建多模块项目 ##### Maven **创建父级项目** 主要用来提供统一的依赖版本信息,创建一个普通的Maven项目,不要选择任何骨架 <img src="https://pic.soki.me/file/piclist/image-20251205160259394-1764921779767.png" alt="image-20251205160259394" style="zoom:50%;" style=""> 构建完成后进入项目删除`/src`目录并修改`pom.xml`配置,完成后重新构建项目 <img src="https://pic.soki.me/file/piclist/image-20251205160609434-1764921969859.png" alt="image-20251205160609434" style="zoom:50%;" style=""> ```xml <packaging>pom</packaging> <properties> <java.version>17</java.version> <spring-cloud.version>2025.0.0</spring-cloud.version> <spring-boot.version>3.5.8</spring-boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ``` **注意**:不同的JDK版本需要修改不同的SpringBoot版本,不同的SpringBoot版本对应不同的SpringCloud版本。 后续添加子模块仅需要在左侧项目文件树中右键父模块后,选择新建模块  需要注意的是新建的模块父项必须为父模块 <img src="https://pic.soki.me/file/piclist/image-20251205161121745-1764922281975.png" alt="image-20251205161121745" style="zoom: 67%;" style=""> ##### Gradle #### 4、Eureka服务 ##### 新建子模块 名为`eureka-server`,子模块文件结构  ##### 修改`pom.xml`文件 添加依赖 ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> ``` ##### 创建`EurekaApplication.java` ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } } ``` **注意**:`@EnableEurekaServer`是用来启动Eureka服务,必须添加。 ##### 创建配置文件 在资源目录下创建`application.yml` ```yml server: port: 13000 #eureka配置 eureka: instance: hostname: localhost #Eureka Server的主机名(单机版写法) client: register-with-eureka: false #是否将自己注册到eureka 服务当中(默认true). fetch-registry: false #是否启用从注册中心拉取服务列表的功能(默认true). service-url: #Eureka Server提供给客户端的访问地址(要加上/eureka/,不然会与原本服务信息页面冲突)。 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ server: enable-self-preservation: true #开启自我保护模式(生产环境建议开启) eviction-interval-timer-in-ms: 60000 #清理无效节点的时间间隔(毫秒),默认60秒 ``` **注意**: - 默认情况下,Eureka在90秒内没有接收到某个微服务节点的心跳,就会认为该微服务节点已宕机,并将其从服务列表中剔除。 - 但是,当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为就会非常危险:因为微服务本身是健康的,此时不应该注销这个微服务。 - Eureka通过自我保护机制来解决这个问题。当Eureka Server的某节点发现在短时间内丢失过多客户端时(可能发生了网络分区故障),Eureka会将当前的实例注册信息保护起来,不在注销任何微服务。 - 具体来说:如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制(在单机状态下很容易进入自我保护机制)。此时,Eureka会将当前的实例注册信息保护起来,同时提示下图警告。 - 当网络故障恢复后,该Eureka Server节点会自动退出自我保护机制。  在开发环境可以将其设置为`false` ```yml #eureka server配置 eureka: client: ... server: enable-self-preservation: false #关闭自我保护机制 ``` ##### 启动项目 打开`http://localhost:13000`可以看到下图,表明服务启动成功  #### 5、服务提供者 ##### 新建子模块 名为`provider`,子模块文件结构  ##### 修改`pom.xml` 添加依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>annotationProcessor</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!--使该子模块成为Eureka客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> ``` ##### 创建`ProviderApplication.java` ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } } ``` **注意**: 从SpringCloud的`Edgware`版本开始,工程中只要添加了`Eureka Client`依赖,就成为`Eureka Client`工程。已经不需要在主启动类上配置`@EnableEurekaClient`或`@EnableDiscoveryClient` 注解了。 ##### 创建配置文件 在资源目录下创建`application.yml` ```yml spring: application: name: provider server: port: 11000 #eureka配置 eureka: client: serviceUrl: #eureka服务端地址 defaultZone: http://localhost:13000/eureka/ instance: #使用ip地址向Eureka注册(默认为主机名) prefer-ip-address: true #上面的配置已经可以使用ip注册了,但显示的还是主机名,所以这里设置显示的注册名 instance-id: ${spring.cloud.client.ip-address}:${server.port} ``` ##### 创建`po/User.java` ```java import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer userId; private String userName; private String password; } ``` ##### 创建`po/CommonResult.java` ```java import lombok.Data; import java.io.Serializable; @Data public class CommonResult<T> implements Serializable { private Integer code; private String message; private T result; public static <T> CommonResult<T> success(T result) { CommonResult<T> commonResult = new CommonResult<>(); commonResult.setCode(200); commonResult.setMessage("操作成功"); commonResult.setResult(result); return commonResult; } public static <T> CommonResult<T> failed(String message) { CommonResult<T> commonResult = new CommonResult<>(); commonResult.setCode(500); commonResult.setMessage(message); return commonResult; } public static <T> CommonResult<T> failed() { return failed("操作失败"); } } ``` ##### 创建`controller/UserController.java` ```java import cn.doooog.provider.po.CommonResult; import cn.doooog.provider.po.User; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/user") @RestController public class UserController { @RequestMapping("/id/{id}") public CommonResult<User> getUserById(@PathVariable("id") Integer id) { //模拟返回业务数据 return CommonResult.success(new User(id, "张三", "123")); } } ``` ##### 启动项目 打开`http://localhost:13000`可以看到注册服务增加了  #### 6、服务消费者 ##### 新建子模块 名为`consumer`,子模块文件结构  ##### 修改`pom.xml` 添加依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>annotationProcessor</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!--使该子模块成为Eureka客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> ``` ##### 创建`ConsumerApplication.java` ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class ConsumerApplication { /** * 使用spring提供的RestTemplate发送http请求调用微服务 * 这里创建RestTemplate对象交给容器管理 */ @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } } ``` **注意**: 从SpringCloud的`Edgware`版本开始,工程中只要添加了`Eureka Client`依赖,就成为`Eureka Client`工程。已经不需要在主启动类上配置`@EnableEurekaClient`或`@EnableDiscoveryClient` 注解了。 ##### 创建配置文件 在资源目录下创建`application.yml` ```yml spring: application: name: consumer server: port: 12000 #eureka配置 eureka: client: serviceUrl: #eureka服务端地址 defaultZone: http://localhost:13000/eureka/ instance: #使用ip地址向Eureka注册(默认为主机名) prefer-ip-address: true #上面的配置已经可以使用ip注册了,但显示的还是主机名,所以这里设置显示的注册名 instance-id: ${spring.cloud.client.ip-address}:${server.port} ``` ##### 创建`po/CommonResult.java` ```java import lombok.Data; import java.io.Serializable; @Data public class CommonResult<T> implements Serializable { private Integer code; private String message; private T result; public static <T> CommonResult<T> success(T result) { CommonResult<T> commonResult = new CommonResult<>(); commonResult.setCode(200); commonResult.setMessage("操作成功"); commonResult.setResult(result); return commonResult; } public static <T> CommonResult<T> failed(String message) { CommonResult<T> commonResult = new CommonResult<>(); commonResult.setCode(500); commonResult.setMessage(message); return commonResult; } public static <T> CommonResult<T> failed() { return failed("操作失败"); } } ``` ##### 创建`controller/TestController.java` ```java import cn.doooog.consumer.po.CommonResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RequestMapping("/test") @RestController public class TestController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @RequestMapping("/id/{id}") public CommonResult test(@PathVariable("id") Integer id) { // 通过服务提供者名(provider)获取Eureka Server上的元数据 List<ServiceInstance> instanceList = discoveryClient.getInstances("PROVIDER"); // 获取服务提供者的元数据 ServiceInstance serviceInstance = instanceList.get(0); return restTemplate.getForObject( "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/id/" + id, CommonResult.class); } } ``` ##### 启动项目 打开`http://localhost:13000`可以看到注册服务增加了  访问`http://localhost:12000/test/id/112312`  可以看到消费者调用提供者接口成功。 ## 三、Eureka高可用集群 #### 1、什么是高可用 高可用(High Availability)是分布式系统架构设计中必须要考虑的因素之一。它通常是指:通过设计减少系统不能提供服务的时间。 在高并发的网络环境中,单节点是系统高可用的大敌,应该在系统设计中避免单节点。高可用保证的原则是“集群化”,或者叫“冗余”。有了集群化,就可以保证在某个节点宕机后,集群中的其它节点能够继续提高服务。 高可用衡量指标,业界用N个9来量化可用性: | **描述** | **通俗叫法** | **可用性级别** | **年度停机时间** | | ---------------------------- | ------------ | -------------- | ---------------- | | 基本可用性 | 2个9 | 99% | 87.6小时 | | 较高可用性 | 3个9 | 99.9% | 8.8小时 | | 具有故障自动恢复能力的可用性 | 4个9 | 99.99% | 53分钟 | | 极高可用性 | 5个9 | 99.999% | 5分钟 | `Eureka Server`可以通过运行`多个实例`并`相互注册`的方式实现高可用部署,Eureka Server实例会彼此同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server的默认行为。 #### 2、修改`host`文件 在Eureka集群中,每一个服务都是以自身IP作为区分,但在开发单机环境中,无法实现多IP,所以可以通过修改`host`文件来实现访问不同网络地址(host)映射到同一个地址中`127.0.0.1` > [!NOTE] > > Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开。其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址。如果没有找到,则系统会将网址提交DNS域名解析服务器进行IP地址的解析。 打开本机的 `C:\Windows\System32\drivers\etc\hosts` 文件,添加映射信息 ```yml ## springcloud 配置 127.0.0.1 eurekaServer13000 127.0.0.1 eurekaServer13001 ``` **注意**:windows系统下hosts文件内容不区分大小写;Linux系统下区分大小写;不能写localhost,因为localhost是本机域名,不是一个IP地址。 #### 3、创建集群 在同一集群中需要保证`Eureka`服务器的配置一模一样 首先修改原本`eureka-server`的配置文件 ```yml server: port: 13000 #eureka配置 eureka: instance: hostname: eurekaServer13000 #集群版写法 client: register-with-eureka: false #是否将自己注册到eureka 服务当中(默认true). fetch-registry: false #是否启用从注册中心拉取服务列表的功能(默认true). service-url: #这是集群版写法,13000注册给13001;反之,13001要注册给13000. defaultZone: http://eurekaServer13001:13001/eureka/ server: enable-self-preservation: true #开启自我保护模式(生产环境建议开启) eviction-interval-timer-in-ms: 60000 #清理无效节点的时间间隔(毫秒),默认60秒 ``` 复制成第二个服务模块`eureka-server13001`  修改第二服务的配置 ```yml server: port: 13001 #eureka配置 eureka: instance: hostname: eurekaServer13001 #集群版写法 client: register-with-eureka: false #是否将自己注册到eureka 服务当中(默认true). fetch-registry: false #是否启用从注册中心拉取服务列表的功能(默认true). service-url: #这是集群版写法,13001注册给13000;反之,13000要注册给13001. defaultZone: http://eurekaServer13000:13000/eureka/ server: enable-self-preservation: true #开启自我保护模式(生产环境建议开启) eviction-interval-timer-in-ms: 60000 #清理无效节点的时间间隔(毫秒),默认60秒 ``` #### 4、启动集群 在两个服务详细页面中会有各自的集群记录  #### 5、将微服务注册到集群 将服务提供者/消费者配置中的`defaultZone`添加为多个,用`,`分割 ```yml eureka: client: serviceUrl: defaultZone: http://localhost:13000/eureka/,http://localhost:13001/eureka/ ``` 实际上,由于Eureka Server集群的各个节点相互注册后,服务只需要注册到集群中的某一个节点上,就相当于注册到集群中的所有节点上。 但是,当服务只注册到集群中的某一个节点上时,如果这个节点宕机了,那么该服务将无法注册。所以,实际开发中,都会将服务注册到集群中的所有节点上。 最后修改:2025 年 12 月 06 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。