SpringCloud

SpringCloud

1.系统架构的演变

单体架构 > 垂直应用架构 > 分布式架构 > SOA > 微服务

1.1 单体架构

互联网早期, 一般的网站应用流量小, 将所有功能代码都写到一个应用中, 通常打成WAR包, 部署到应用服务器中
在这里插入图片描述

  • 优点
    • 开发成本低、不涉及网络通信(性能快)、部署简单
  • 缺点:
    • 随着功能的增多, 应用会越来越大, 部署需要很久
    • 耦合度高: 一旦某个功能出现了异常, 会影响整个应用
    • 单体架构会限制技术选型的灵活性,因为所有的功能模块都需要使用相同的技术栈和框架,可能无法充分利用最新的技术和工具

1.2 垂直应用架构

按业务功能将应用拆分成多个独立的服务, 每个服务负责处理独立的逻辑, 服务之间互不相干, 独立部署

在这里插入图片描述

  • 优点
    • 将对一个服务的请求, 分担给了多个服务, 实现了流量分担, 解决了并发问题,
    • 提高容错率, 一个服务出现了问题, 不影响其他服务的运行
    • 每个模块可以独立开发、测试、部署和维护,具有较强的自治性。
    • 每个模块或服务的代码量相对较小,易于理解和维护
    • 不同的模块可以使用不同的技术栈和编程语言,灵活性更高
    • 每个团队可以专注于自己负责的模块或服务,提高了开发效率。
  • 缺点
    • 服务之间相互独立, 无法进行相互调用, 提高了服务间通信成本
    • 服务之间相互独立, 会有重复的开发任务
    • 分布式事务难以处理

1.3 分布式服务

当垂直应用越来越多,重复的业务代码就会越来越多。这时候,我们就思考可不可以将重复的代码抽取出来,做成统一的业务层作为独立的服务,然后由前端控制层调用不同的业务层服务呢? 这就产生了新的分布式系统架构。它将把工程拆分成表现层和服务层两个部分,服务层中包含业务逻辑。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。

在这里插入图片描述 -

  • 优点
    • 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
  • 缺点
    • 系统间耦合度变高,调用关系错综复杂,难以维护

1.4 面向服务的架构(SOA)

在分布式架构下,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心对集群进行实时管理。此时,用于资源调度和治理中心(SOA Service Oriented Architecture,面向服务的架构)是关键。

在这里插入图片描述

服务治理要做什么?
1.服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
2.服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
3.动态监控服务状态监控报告,人为控制服务状态

  • 缺点
    • 服务间会有依赖关系,一旦某个环节出错会影响较大
    • 服务关系复杂,运维、测试部署困难

1.5 微服务架构

微服务架构, 简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。

在这里插入图片描述

一旦采用微服务系统架构,就势必会遇到这样几个问题:

  • 这么多小服务,如何管理他们?
  • 这么多小服务,他们之间如何通讯?
  • 这么多小服务,客户端怎么访问他们?
  • 这么多小服务,一旦出现问题了,应该如何自处理?
  • 这么多小服务,一旦出现问题了,应该如何排错?

对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。

2.微服务架构的解决方案

2.1 服务治理

服务治理就是进行服务的自动化管理, 其核心是注册和发现

  • 服务注册: 服务实例将自身服务信息注册到注册中心
  • 服务发现: 服务实例通过注册中心, 获取到注册到其中的服务实例的信息, 通过这些信息去请求他们提供服务
  • 服务剔除: 服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求他们提供服务

2.2 服务调用

在微服务架构中, 存在多个服务之间远程调用的需求, 主流的远程调用技术有两种

  • HTTP: HTTP其实是一种网络传输协议, 基于TCP, 规定了数据传输的格式, 现在客户端浏览器与服务端通信基本都是采用HTTP协议
    • 协议特点:
      • 基于文本的协议,易于理解和调试。
      • 支持多种请求方法,如GET、POST、PUT、DELETE等。
      • 通过状态码进行错误处理和状态表示
      • 基于请求-响应模式,使用URL和HTTP头部进行通信
    • 优点:
      • 广泛支持,几乎所有编程语言和平台都提供了HTTP库。
      • 跨语言和跨平台兼容性好。
      • 使用RESTful API设计风格,简单明了。
    • 缺点:
      • 较为庞大的头部信息,可能会增加额外的网络开销
      • 请求和响应之间的格式约束不如RPC严格。
      • 无法直接支持服务调用的面向对象语义
  • RPC: RPC是一种用于不同系统之间进行通信的技术,它使得远程计算机程序可以像本地程序一样被调用,而不需要开发者显式地处理底层通信细节
    • 协议特点:
      • 基于二进制的协议,通常更加高效
      • 通常采用特定的IDL(Interface Definition Language)来定义接口
      • 支持多种传输协议,如TCP、UDP等
      • 提供更加严格的语义和错误处理
    • 优点:
      • 提供了更加严格的接口定义和类型检查,使得服务调用更加可靠
      • 通常比HTTP协议更加高效
      • 支持面向对象的调用语义
    • 缺点:
      • 对于不同语言和平台的兼容性可能较差,需要特定的RPC框架来解决
      • 可能需要额外的序列化和反序列化过程,增加了开销
      • 可能缺乏HTTP的灵活性和可扩展性

HTTP更适合在互联网上进行通信,尤其是在基于RESTful API的微服务架构中,而RPC则更适合于需要严格接口定义和类型检查的内部服务间通信。选择合适的协议取决于具体的应用需求和技术栈。

2.3 服务网关

随着微服务的不断增多,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信可能出现:

  • 客户端需要调用不同的URL地址, 增加难度
  • 在一定的场景下, 存在跨域请求的问题
  • 每个微服务都需要进行单独的身份认证和权限控制

为了解决这些问题, API网关顺势而生, API网关直面意思是将所有API调用统一接入到API网关层,由网关层统一接入和输出。一个网关的基本功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后,各个API服务提供团队可以专注于自己的的业务逻辑处理,而API网关更专注于安全、流量、路由等问题。

在这里插入图片描述

2.4 服务容错

在微服务当中,一个请求经常会涉及到调用几个服务,如果其中某个服务不可用,没有做服务容错的话,极有可能会造成一连串的服务不可用,这就是雪崩效应。

我们没法预防雪崩效应的发生,只能尽可能去做好容错。服务容错的三个核心思想是:

  • 不被外界环境影响
  • 不被上游请求压垮
  • 不被下游响应拖垮

在这里插入图片描述

3.服务通信

3.1 SpringCloud实现服务间通信的方式

  • RESTful API调用
    通过在微服务之间暴露RESTful API,可以使用HTTP请求进行通信, 通过使用Spring Cloud中的 RestTemplate可以方便地进行服务间的HTTP调用
  • Spring Cloud Feign
    声明式的REST客户端, 它简化了服务间调用的过程
  • 消息队列
    服务可以通过发送消息到消息队列来与其他服务进行通信,接收方则可以异步地处理这些消息

1.RESTful API调用和Spring Cloud Feign的区别

  • 底层实现
    • 无论是RESTful API调用还是Spring Cloud Feign, 在底层都是通过HTTP协议进行通信的, 无论是发送请求还是接收响应,都是基于HTTP的请求和响应模型来进行的
  • 实现方式
    • 在RESTful API调用中, 通常是通过手动构建HTTP请求, 开发者需要自行处理HTTP请求的构建、发送和响应解析等细节
    • Spring Cloud Feign是一个声明式的REST客户端, 是一种更加高级和便捷的方式,通过在接口上添加 @FeignClient 注解, 它隐藏了底层的HTTP请求细节,使得调用远程服务变得更加简单和方便

2.微服务中什么情况下使用HTTP通信, 什么情况下使用消息队列通信

  • 使用HTTP通信的场景
    • 即时响应: 例如获取数据、执行操作等
  • 使用消息队列通信的场景
    • 异步处理: 发送方发送消息后不需要等待接收方立即处理并返回结果时,通常可以选择使用消息队列通信
    • 流量削峰: 当服务需要处理突发的高并发请求时,可以选择使用消息队列通信来实现流量削峰
    • 可靠性和容错性: 消息队列提供了消息持久化和副本机制, 能够保证消息的可靠性和容错性,适用于对消息传输的可靠性要求较高的场景。

3.2 HTTP客户端工具

HTTP客户端工具的作用是在应用程序中充当客户端角色, 负责向服务器发送HTTP请求, 并处理服务返回的HTTP响应. 具体来说, HTTP客户端实现通常具有以下作用:

  • 发送请求:HTTP 客户端实现能够发送各种类型的 HTTP 请求,包括 GET、POST、PUT、DELETE 等,以及发送请求参数、请求头等。
  • 接收响应:HTTP 客户端实现能够接收服务器返回的 HTTP 响应,包括响应状态码、响应头、响应体等,并能够处理响应数据。
  • 处理响应:HTTP 客户端实现能够对服务器返回的 HTTP 响应进行处理,包括解析响应数据、提取需要的信息、错误处理等。
  • 连接管理:HTTP 客户端实现能够管理与服务器之间的连接,包括连接的建立、复用、保持等,以提高性能和效率。
  • 重定向处理:HTTP 客户端实现能够处理服务器返回的重定向响应,按照重定向规则进行相应的跟随或处理。
  • 身份验证:HTTP 客户端实现能够支持各种类型的身份验证机制,包括基本身份验证、摘要身份验证、OAuth 等。
  • 身份验证:HTTP 客户端实现能够支持各种类型的身份验证机制,包括基本身份验证、摘要身份验证、OAuth 等。

常见的HTTP客户端工具:

  • HttpClient: 是Java标准库的一部分, 由Apache维护, 相对而言, 更新速度可能不如OkHttp那么快
  • OKHttp: 由Square公司开发和维护, 性能上优于HttpClient。OkHttp使用了连接池和异步执行请求等技术来提高性能

总的来说,如果你需要一个性能良好、易用且功能丰富的网络请求库,OkHttp 是一个很好的选择。但如果你需要一个更标准的、符合 Java 标准库的解决方案,或者在已有的项目中使用 HttpClient,那么它也是一个不错的选择。

3.3 RestTemplate

RestTemplate是Spring提供的一个用于进行RESTful风格的HTTP请求的类, 用于发送HTTP请求并处理响应,包括GET、POST、PUT、DELETE等方法。使用RestTemplate, 可以方便地与RESTful API进行交互,从而实现与远程服务的通信。

RestTemplate使用的是Java原生的HttpURLConnection作为默认的HTTP客户端. 同时它对HttpClient、OKHttp都有支持

  • 步骤一: 注入RestTemplate实例
@Bean
public RestTemplate restTemplate() {
	return new RestTemplate();
}
  • 步骤二: 通过@Autowired注入, 使用getForObject发起请求
@Autowired
private RestTemplate restTemplate;

@Test
public void httpGet() {
       // 调用springboot案例中的rest接口
	User user = this.restTemplate.getForObject("http://localhost/user/1", User.class);
	System.out.println(user);
}

通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。

在这里插入图片描述

3.3.1 设置请求头、请求体

// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer YOUR_ACCESS_TOKEN");
headers.set("Content-Type", "application/json");

// 设置请求体
String requestBody = "{\"key\":\"value\"}";

// 创建 HttpEntity 对象,包含请求头和请求体
HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);

// 发送 POST 请求,包含请求头和请求体
ResponseEntity<String> response = restTemplate.exchange(
    "http://example.com/api/resource",
    HttpMethod.POST,
    requestEntity,
    String.class);

4.微服务场景模拟(提供者-消费者)

4.1 提供者

  • 步骤一: 创建user-service项目, 并引入相关依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.5</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  • 步骤二: 提供Restful API
@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("{id}")
    public User queryById(@PathVariable("id") Long id) {
        return this.userService.queryById(id);
    }
}

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id) {
        return this.userMapper.selectById(id);
    }
}

@Mapper
public interface UserMapper extends BaseMapper<User> {
}
  • 步骤三: 配置文件
server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_cloud #你学习mybatis时,使用的数据库地址
    username: root
    password: root

4.2 消费者

  • 步骤一: 创建order-service项目, 并引入相关依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.5</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  • 步骤二: 编写Controller
@RestController
@RequestMapping("order")
public class UserController {
    @Autowired
    private RestTemplate restTemplate;
    
	@Autowired
    private OrderService orderService;

    @GetMapping("getUser/{id}")
    public User getUserByOrderId(@PathVariable("id") Long id){
    	Order order = orderService.querById(id);
    	Long uid = order.getUid(); 
        User user = this.restTemplate.getForObject("http://localhost:8081/user/" + uid, User.class);
        return user;
    }
}

5.Eureka注册中心

5.1 作用

上述案例存在的问题:

  1. order-service需要记住user-service的地址, 如果地址变更, 通信就会失败
  2. order-service不清楚user-service的当前状态, 即使user-service宕机了, 它也不清楚
  3. 当user-service形成集群后, order-service要自己实现负载均衡

在这里插入图片描述

Eureka的作用, 总结就是三个:

  • 服务注册和发现
    • user-service实例启动后, 将自己的信息注册到eureka-server(Eureka服务端) ,这个叫注册服务
    • eureka-server保存服务名称服务实例地址列表的映射关系
      • 集群时, 一个服务名称可能对应多个服务实例地址
    • provider根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取
  • 心跳监测
    user-service服务会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳, 当超过一定时间没有发送心跳时, eureka-server会认为微服务实例故障,将该实例从服务列表中剔除, order-service拉取服务列表时,就能将故障实例排除了
  • 负载均衡
    order-service从实例列表中利用负载均衡算法选中一个实例地址, 向该实例地址发起远程调用

5.2 基础架构

  • 服务注册中心
    Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server。
  • 服务提供者
    提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。
  • 服务消费者
    消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。

5.3 搭建注册中心

使用Eureka, 首先要搭建注册中心

  • 步骤一: 确保Spring Cloud组件使用相同版本号
    spring-cloud-dependencies是Spring Cloud提供的一个BOM, 用于集中管理Spring Cloud组件的版本号, 使用BOM可以确保项目中使用的所有Spring Cloud组件都采用相同的版本, 从而避免版本冲突和其他相关问题
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR10</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>

注意点:SpringCloud和Springboot的版本是存在对应关系
Springcloud的版本依赖问题(最全,包含springCloud所有的版本)

  • 步骤二: 引入eureka服务端的依赖包
    注意: 服务端的依赖结尾是eureka-server
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

出现java.lang.TypeNotPresentException: Type javax.xml.bind.JAXBContext not present异常通常是由于缺少Java的JAXB库所致。在Java 9及更高版本中,JAXB已经从标准库中移除,因此需要手动添加依赖来解决此问题。

  • 步骤三: 编写启动类, 添加@EnableEurekaServer注解,开启eureka的注册中心功能
    启动类一定要添加@EnableEurekaServer注解,开启eureka的注册中心功能
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}
  • 步骤四: 配置文件application.yml
server:
	port: 10086 # 端口
spring:
	application:
		name: eureka-server # 应用名称,会在Eureka中显示
eureka:
	client:
		service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。	
			defaultZone: http://127.0.0.1:${server.port}/eureka
  • 步骤五: 启动服务, 浏览器访问:http://127.0.0.1:10086

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

5.4 服务注册(provider端实现)

  • 步骤一: 引入springCloud的核心依赖
    因为SpringBoot使用2.6.5版本, SpringCloud对应的是2021.0.3版本
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>
  • 步骤二: 引入eureka客户端的依赖
    注意:此处引入的是eureka-client,而非eureka-server
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 步骤三: 编写application.yml, 配置应用名称、eureka-server服务地址
spring:
  application:
    name: user-service # 应用名称,注册到eureka后的服务名称 
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka
  • 步骤四: 启动多个user-service实例
    为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service。将第二个实例的端口改为8082
    在这里插入图片描述

在很多案例中, 都会在启动类中添加@EnableDiscoveryClient@EnableEurekaClient用来启用服务发现客户端功能的两个注解

  • @EnableDiscoveryClient: 这个注解是Spring Cloud推荐的通用性注解, 适用于多种服务发现组件, 不仅限于Eureka. 如果您的应用可能在将来切换到其他服务发现组件(例如Consul、ZooKeeper等), 那么使用@EnableDiscoveryClient是更好的选择
  • @EnableEurekaClient: 这个注解是专门用来启用Eureka服务发现客户端功能的。虽然它依赖于Eureka,但它在使用Eureka作为服务发现组件时更加特定和精确

事实上, 即使您没有显式地添加@EnableDiscoveryClient@EnableEurekaClient注解,只要您的Spring Boot应用添加了相应的依赖,并配置了服务发现的相关信息,Spring Cloud也会自动检测到这些配置,并自动启用服务发现客户端功能。

  • 步骤五: 在eureka-server管理页面中user-service应用名称对应两个服务实例, 分别是8081端口和8082端口

在这里插入图片描述

5.5 服务注册(consumer端实现)

  • 步骤一: 引入springCloud的核心依赖
    因为SpringBoot使用2.6.5版本, SpringCloud对应的是2021.0.3版本
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>
  • 步骤二: 引入eureka客户端的依赖
    注意:此处引入的是eureka-client,而非eureka-server
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 步骤三: 编写application.yml, 配置应用名称、eureka-server服务地址
spring:
  application:
    name: order-service # 应用名称,注册到eureka后的服务名称 
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka

我们发现消费者端与生产者端的操作一致, 不论是消费者还是生产者, 注册环节是一致的

  • 步骤四: 启动项目后,查看eureka-server管理页面:

在这里插入图片描述

5.6 服务发现和负载均衡

在消费者端, 服务发现负载均衡都不需要我们自己去实现, 只要添加一个@LoadBalanced注解, 之后就可以使用RestTemplate从eureka注册中心中拉取user-service服务的实例列表, 并自动实现负载均衡

  • 步骤一: 为RestTemplate添加@LoadBalanced注解

在这里插入图片描述

  • 步骤二: 修改远程接口路径
    以服务名称替代ip和端口

在这里插入图片描述

RestTemplate是怎么样发起请求:
RestTemplate并不参与服务发现的过程, 服务发现是通过Eureka客户端实现的

当使用@LoadBalanced注解标记RestTemplate时,Spring Cloud会在其内部创建一个拦截器(LoadBalancerInterceptor)。这个拦截器的作用是拦截RestTemplate的请求,将请求的URL中的服务名称解析出来,并使用负载均衡器(通常是Ribbon)从服务发现组件(通常是Eureka)获取可用的服务实例列表。

  1. 解析服务名称: 当RestTemplate发起请求时, LoadBalancerInterceptor会拦截请求, 解析请求的URL, 提取出其中的服务名称部分。
  2. 获取服务实例列表: 拦截器通过服务发现组件(例如Eureka客户端)获取对应服务名称的所有可用实例列表。
  3. 选择目标实例: 根据负载均衡策略(如轮询、随机、加权轮询等) ,拦截器会选择一个实例作为目标。
  4. 替换请求URL: 拦截器会将原始请求中的服务名称替换为所选择的目标实例的具体网络地址。
  5. 发送请求: 最后,拦截器会将修改后的请求发送到目标实例。

5.7 高可用的Eureka Server

在刚才的案例中, 我们只有一个EurekaServer, 事实上EurekaServer也可以是一个集群, 形成高可用的Eureka中心

5.7.1 服务同步

多个Eureka Server之间也会互相注册为服务, 当服务提供者注册到Eureka Server集群中的某个节点时, 该节点会把服务的信息同步给集群中的每个节点, 从而实现数据同步. 因此, 无论客户端访问到Eureka Server集群中的任意一个节点, 都可以获取到完整的服务列表信息.

5.7.2 搭建高可用的Eureka Server

我们假设要运行两个eureka-server的集群,端口分别为:10086和10087。只需要把eureka-server启动两次即可。

  • 步骤一: 启动第一个eureka-Server

需要在defaultZone中配置其他Eureka服务的地址,例如10087

server:
  port: 10086 # 端口
spring:
  application:
    name: eureka-server # 应用名称,会在Eureka中显示
eureka:
  client:
    service-url: # 配置其他Eureka服务的地址,而不是自己,比如10087
      defaultZone: http://127.0.0.1:10087/eureka

启动服务后,会报异常,这个异常是正常情况,等把10087也启动后,异常就会消失

在这里插入图片描述

  • 步骤二: 启动第二个eureka-Server

在Idea中一个应用不能重复启动,所以我们需要重新配置一个启动器

在这里插入图片描述

这些信息复制上一个eureka-Server

在这里插入图片描述

修改application.yml,然后启动EurekaApplication(10087)

server:
  port: 10087
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  • 步骤三: 访问集群

访问http://127.0.0.1:10086/
在这里插入图片描述

访问http://127.0.0.1:10087/
在这里插入图片描述


5.7.3 注册服务到集群

服务只需要注册到10086节点和10087节点中任何一个即可,因为集群有服务同步,但是既然已经使用到了集群,为了防止其中一个节点宕机,注册服务时要填上集群中所有节点

eureka:
  client:
    service-url: # EurekaServer地址,多个地址以','隔开
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

测试服务同步

  • 步骤一: user服务在10086节点注册后, 会将服务信息同步到10087节点

此时consumer在10086这个节点注册服务,该节点也会将服务信息同步到10087,我们可以测试一下

在这里插入图片描述

10087节点的显示

在这里插入图片描述

  • 步骤二: order服务请求10087节点的user服务

在这里插入图片描述

我们调用order服务的接口,debug发现能调通user服务的接口,虽然这两个服务配置的eureka-server节点不同

总结:
即使两个服务(user服务、order服务)配置的eureka-server的节点不同,但只要这两个eureka-server在一个集群中, user服务和order服务之间也能互相远程调用

5.8 服务提供者

1.服务注册
服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-eureka=true参数是否正确,事实上默认就是true。

如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。参考: Map<String, Map<String, Object>>

  • 第一层Map的Key就是服务名称,即yml配置中的spring.application.name属性
  • 第二层Map的key是服务的实例标识。一般host + port,例如:127.0.0.1:8080
  • 值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。

2.服务续约
服务注册成功后, 服务实例会定期向注册中心发送心跳信息, 以更新自己的注册信息并表示自己仍然存活, 在Eureka中, 服务续约是通过服务实例定时发送心跳请求给Eureka服务器来实现的。

每次发送心跳时, 服务实例会更新自己的租约信息, 包括续约到期时间、最后一次更新时间等. 这些信息用来告诉Eureka服务器该服务实例的状态和可用性. 如果服务实例由于某种原因无法发送心跳请求或在续约到期之前发生了故障, 它的租约将会过期, 注册中心会将其标记为失效并从注册表中移除.

3.修改服务续约的行为

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90
    lease-renewal-interval-in-seconds: 30

lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

也就是说, 默认情况下每个30秒服务会向注册中心发送一次心跳, 证明自己还活着. 如果超过90秒没有发送心跳, EurekaServer就会认为该服务宕机, 会从服务列表中移除, 这两个值在生产环境不要修改, 默认即可。

但是在开发时, 这个值有点太长了, 经常我们关掉一个服务, 会发现Eureka依然认为服务在活着. 所以我们在开发阶段可以适当调小

eureka:
  instance:
    lease-expiration-duration-in-seconds: 10 # 10秒即过期
    lease-renewal-interval-in-seconds: 5 # 5秒一次心跳

5.9 服务消费者

当服务消费者启动时, 会检测eureka.client.fetch-registry=true参数的值, 如果为true(默认为true), 则会拉取Eureka Server服务的列表只读备份, 然后缓存在本地. 并且每隔30秒会重新获取并更新数据

当服务注册成功后, 就会从Eureka Server获取服务列表并缓存在本地,而不是在每次RestTemplate发起请求时才去拉取服务列表。

可以通过修改参数来更改间隔时间, 生产环境中, 我们不需要修改这个值. 但是为了开发环境下, 能够快速得到服务的最新状态, 我们可以将其设置小一点.

eureka:
  client:
    registry-fetch-interval-seconds: 5

5.10 失效剔除和自我保护

1.服务下线
当一个服务实例需要停止或者被下线时, 它会发送一个取消注册的请求给Eureka服务器. Eureka服务器收到这个请求后会将该服务实例从服务注册表中移除

2.失效剔除
当一个服务实例无法按时向Eureka服务器发送心跳请求时, Eureka服务器会将该服务实例标记为失效。默认情况下, Eureka服务器将等待90秒来判断一个服务实例是否失效. 如果在这段时间内没有收到来自服务实例的心跳请求, Eureka服务器就会将该实例标记为失效, 并将其从服务注册表中移除

可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生产环境不要修改。

这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如:10秒
在这里插入图片描述

3.自我保护
我们关停一个服务,就会在Eureka面板看到一条警告:
在这里插入图片描述
如果在一段时间内,丢失的心跳数占总心跳数的比例超过了预定的阈值(默认为15%), Eureka会启动自我保护机制, 这可以防止因为短暂的网络问题或负载过重而导致大量服务实例被误判为失效

Eureka的自我保护机制是一种机制,用于防止网络分区故障时,Eureka注册中心将健康的服务实例误标记为失效的情况。当Eureka Server节点在一个长时间段内没有收到来自某个服务实例的心跳时,Eureka Server会将该服务实例暂时从服务注册表中剔除,但是它并不会立即删除这个服务实例,而是将其保留在一个叫做“只读副本”的内存数据结构中。

开发阶段我们都会关闭自我保护模式: (eureka-service)

eureka:
  server:
    enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
    eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)

6.Ribbon负载均衡

在实际开发环境中, 往往会开启很多provider的集群, 此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?

现在我们不需要自己编写负载均衡算法, Eureka中已经帮我们集成了负载均衡组件:Ribbon
在这里插入图片描述

负载均衡原理:
在这里插入图片描述

6.1 开启负载均衡

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

修改调用方式,不再手动设置ip和端口号,而是直接通过服务名称调用:
在这里插入图片描述

6.2 源码跟踪

为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。

显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

  • 关键类一: LoadBalancerIntercepor
    RestTemplate.getForObject --> RestTemplate.execute --> RestTemplate.doExecute --> AbstractClientHttpRequest.execute --> AbstractBufferingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.executeInternal --> InterceptingClientHttpRequest.execute --> LoadBalancerInterceptor.intercept方法

在这里插入图片描述
可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:

  • request.getURI():获取请求uri,本例中就是 http://user-service/user/1
  • originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-service
  • this.loadBalancer.execute():处理服务id,和用户请求。

这里的this.loadBalancer是LoadBalancerClient类型,我们继续跟入。

  • 关键类二: LoadBalancerClient

在这里插入图片描述

  • getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
  • getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务

放行后,再次访问并跟踪,发现获取的是8081:
在这里插入图片描述
测试: 使用LoadBalancerClient获取服务列表

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ItcastServiceConsumerApplication.class)
public class LoadBalanceTest {

    @Autowired
    private RibbonLoadBalancerClient client;

    @Test
    public void testLoadBalance(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("service-provider");
            System.out.println(instance.getHost() + ":" +instance.getPort());
        }
    }
}

结果:
在这里插入图片描述
符合了我们的预期推测,确实是轮询方式。

6.3 负载均衡策略IRule

在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:
在这里插入图片描述

我们继续跟入:
在这里插入图片描述

继续跟踪源码chooseServer方法,发现这么一段代码:
在这里插入图片描述

我们看看这个rule是谁:
在这里插入图片描述

这里的rule默认值是一个RoundRobinRule,看类的介绍:
在这里插入图片描述

6.4 Ribbon流程

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。用一幅图来总结一下:
在这里插入图片描述

  • 拦截我们的RestTemplate请求http://userservice/user/1
  • RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-service
  • DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
  • eureka返回列表: localhost:8081、localhost:8082
  • IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
  • RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

6.5 负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:
在这里插入图片描述

不同规则的含义如下:

在这里插入图片描述

6.5.1 自定义负载均衡策略

通过定义IRule实现可以修改负载均衡规则,有两种方式:

1.代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:

@Bean
public IRule randomRule(){
    return new RandomRule();
}

在这里插入图片描述

2.配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:

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

6.6 饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true
    clients: userservice

7.Hystrix(容错和隔离机制)

在Spring Cloud中, 通常使用的是Hystrix作为服务熔断和容错的解决方案. Hystrix在Spring Cloud中是通过spring-cloud-starter-netflix-hystrix依赖提供的. 在Spring Cloud的早期版本中, Hystrix是一个常用的组件,但是随着Netflix停止对Hystrix的维护, Spring Cloud也逐渐推荐使用其他的解决方案, 比如Resilience4j和Sentinel

7.1 雪崩效应

雪崩效应是指在分布式系统中,当某个微服务或组件出现故障时,由于系统中的其他微服务或组件对其依赖,导致故障进一步扩大,最终导致整个系统的崩溃。这种现象类似于雪崩,一旦开始滚动,就会不断扩大,直至整个系统无法正常运行。

微服务之间相互调用,关系复杂,正常情况如下图所示:

在这里插入图片描述

某个时刻,服务A挂了,服务B和服务C依然在调用服务A

在这里插入图片描述

由于服务A挂了,导致服务C和服务B无法得到服务A的响应,这时候服务C和服务B由于大量线程积压,最终导致服务C和服务B挂掉.

在这里插入图片描述

相同道理,由于服务之间有关联,所以会导致整个调用链上的所有服务都挂掉.

在这里插入图片描述

7.2 Hystix解决雪崩问题的方案

  • 服务熔断: 熔断异常服务, 快速返回报错响应, 防止级联系统故障, 避免雪崩效应的发生
  • 降级处理: 当某个服务出现故障或者延迟时, 可以使用预设的降级逻辑来替代原始的服务调用

7.3 服务熔断

通过服务熔断机制,系统可以在面对不可避免的故障情况下保持稳定,避免由于一个服务的故障而导致整个系统的崩溃。服务熔断器能够快速地停止对故障服务的调用,并提供一个临时的替代方案,保障系统的稳定性和可用性。
在这里插入图片描述

7.3.1 熔断状态

  • 关闭状态: 所有请求都正常访问
  • 打开状态: 当错误率超过阈值, 熔断器打开, 在打开状态下, 会拒绝所有对被保护资源的请求, 不会执行主逻辑, 避免进一步的请求对系统造成负担, 同时也给资源(服务)一些时间来恢复
  • 半开状态: 经过一段时间后(5秒), 熔断器进入半开状态, 在半开状态下, 熔断器会允许部分请求通过,以测试被保护资源(服务)是否已经恢复正常, 如果这些请求成功, 则熔断器会进入关闭状态, 否则会再次回到打开状态, 重复这个过程

7.3.2 启用熔断

  • 步骤一: 引入Hystrix依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  • 步骤二: 在启动类上添加@EnableCircuitBreaker注解,开启熔断

在这里插入图片描述

  • 步骤三: 需要使用熔断功能的服务调用方法上添加@HystrixCommand注解,并指定一个降级处理方法,用于处理熔断时的情况

在这里插入图片描述

服务熔断和降级处理通常是配合使用的, 在服务熔断时可以执行降级处理逻辑,提供给用户一个友好的提示信息, 告知当前服务不可用,而不是让用户长时间等待或看到错误页面

7.3.3 熔断触发条件

错误率超过阈值: 10秒内请求的失败率超过50%, 就触发熔断机制, 之后每隔5秒重新尝试请求微服务, 如果微服务不能响应, 继续走熔断机制. 如果微服务可达, 则关闭熔断机制, 恢复正常请求(触发条件可以自定义)

7.3.4 测试服务熔断

@GetMapping("{id}")
@HystrixCommand
public String queryUserById(@PathVariable("id") Long id){
    if(id == 1){
        throw new RuntimeException("太忙了");
    }
    String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
    return user;
}

7.3.5 自定义熔断条件

circuitBreaker:
	requestVolumeThreshold: 10
	sleepWindowInMilliseconds: 10000
	errorThresholdPercentage: 50
  • requestVolumeThreshold:触发熔断的最小请求次数,默认20
  • errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%
  • sleepWindowInMilliseconds:休眠时长,默认是5000毫秒

7.4 服务降级

在这里插入图片描述

实现服务降级功能:

  • 步骤一: 引入Hystrix依赖
  • 步骤二: 在启动类上添加@EnableCircuitBreaker注解,开启熔断
  • 步骤三: 编写降级逻辑
  • 步骤四: 添加@HystrixCommand注解, 声明一个降级逻辑的方法
@HystrixCommand(fallbackMethod = "queryUserByIdFallBack")
public String queryUserById(@RequestParam("id") Long id) {
	String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
	return user;
}

public String queryUserByIdFallBack(Long id){
	return "请求繁忙,请稍后再试!";
}

我们可以把降级方法认为是当前方法的替代品, 所以编写降级方法时,需要注意以下两点

  • 相同的参数列表
  • 相同的返回值类型

如果没有满足相同的参数列表和返回值声明, 在方法启动时,就会抛出异常

7.4.1 服务降级触发条件

  • 响应时间超时
  • 错误率超过阈值

注意: 如果一个方法在执行过程中抛出异常并不会直接导致服务降级, 服务降级通常是根据一系列的触发条件来决定的,这些条件可能包括错误率超过阈值、响应时间超时、系统负载过高等

7.4.2 默认的fallBack

把Fallback配置加在类上, 实现默认fallback

在这里插入图片描述

  • @DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法
  • @HystrixCommand:在方法上直接使用该注解,使用默认的剪辑方法。

7.4.3 设置超时时间

当使用 Hystrix 执行隔离策略时,如果命令的执行时间超过了 6 秒,Hystrix 将会抛出超时异常并执行相应的降级逻辑。

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms

在这里插入图片描述

  • @DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法
  • @HystrixCommand:在方法上直接使用该注解,使用默认的剪辑方法。

7.5 服务熔断和服务降级的区别

  • 服务熔断
    • 触发条件: 错误率超过阈值, 熔断器开启
    • 目的: 拒绝请求对故障服务的访问, 直到熔断器半开或者关闭
    • 行为: 停止对故障服务的请求, 执行降级逻辑
    • 影响范围: 一旦熔断器打开,所有对该服务的请求都将被拒绝,直到熔断器尝试关闭或者半开
  • 服务降级
    • 触发条件: 响应时间超时、错误率超过阈值、系统负载过高等
    • 目的: 在服务不可用或者负载过高时, 执行备用响应, 保证服务可用性, 防止因为单个服务的故障而导整个系统崩溃。
    • 行为: 服务降级时, 不是直接返回错误或异常, 而是执行备用响应
    • 影响范围: 服务降级不会影响下一次请求的执行,而是提供了一个替代方案,让系统可以继续运行并尽可能地保持可用性。

服务熔断一般需要指定降级逻辑(服务降级)
服务降级则未必一定是服务熔断引起的, 还有可能是响应超时、系统负载过高等原因

8.Feign

8.1 使用restTemplate存在的问题

先来看我们以前利用RestTemplate发起远程调用的代码:

String url = "http://user-service/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
  • 代码可读性差,编程体验不统一
  • 参数复杂URL难以维护

8.2 使用Feign替代RestTemplate

  • 步骤一: 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 步骤二: 启动类上添加@EnableFeignClients注解, 开启Feign功能

在这里插入图片描述

  • 步骤三: 编写Feign客户端, 它是一个接口
@FeignClient("user-service")
public interface UserClient {
	
	// 基于远程调用服务的信息
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
    
}
  • 步骤四: 使用Feign替代RestTemplate
@Autowired
private UserClient userClient;

public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
    // 根据id查询订单并返回
    Order order = orderService.queryOrderById(orderId);
    // 使用Feign发起HTTp请求
    User user = userClient.findById(order.getUserId());
    order.setUser(user);
    return order;
}

8.3 编写Feign接口的注意点

  • @FeignClient: 指定要调用的远程服务的名称
  • @RequestMapping: 主要用于feign框架拼接传递url,弥补了Ribbon的url需要手动拼接的缺陷
  • @PathVariable(“id”) 当路径为restful风格时路径传参方式
  • @RequestParam(“id”) 当路径为?id=250 时传参方式
  • @RequestBody User user 当路径为对象时采用的传参方式
@FeignClient("feign-provider")
@RequestMapping("/provider")
public interface UserFeign {
 
    @RequestMapping("/getUserById/{id}")
    User getUserById(@PathVariable("id") Integer id);
 
    @RequestMapping("/deleteUserById")
    User deleteUserById(@RequestParam("id") Integer id);//?形式拼接参数,?id=250
 
    @RequestMapping("/addUser")
    User addUser(@RequestBody User user);//pojo--->json
}

8.4 原理

使用Feign时, 会通过服务发现组件(例如Eureka)从注册表中获取与指定服务名称匹配的所有服务实例。然后,它会根据一定的负载均衡算法(例如轮询、随机等)选择其中一个服务实例作为目标服务。

一旦选择了目标服务实例, Feign将使用该实例的网络地址(IP地址和端口号)来构建HTTP请求, 并将请求发送到目标服务实例. 这样, Feign就能够实现对指定服务的调用, 而不需要显式指定服务的网络地址.

8.5 日志级别

@FeignClient注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。
在这里插入图片描述
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。

如何自定义配置: 例如修改feign的日志级别

feign:  
  client:
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

而日志的级别分为四种:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

8.6 选择HTTP客户端

Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

我们提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。这里我们使用HttpClient来演示

  • 步骤一: 引入HttpClient的依赖
<!--httpClient的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
  • 配置连接池
feign:
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

接下来,在FeignClientFactoryBean(实例化对象)中的loadBalance方法中打断点:

在这里插入图片描述

Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient:
在这里插入图片描述

8.7 抽取接口Api

我们可以观察到Feign的客户端与服务提供者的controller代码非常相似:
在这里插入图片描述

抽取方式
在这里插入图片描述

  • 步骤一: 抽取一个独立的module, 命名为feign-api
    在这里插入图片描述
  • 步骤二: 引入Feign的starter依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 步骤三: 在feign-api中编写UserAPI

在这里插入图片描述

  • 步骤四: 在user-service和order-service中使用feign-api
    在order-service、user-service的pom文件中中引入feign-api的依赖:
<dependency>
    <groupId>cn.itcast.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>

测试
发现服务报错了:
在这里插入图片描述
这是因为UserClient现在在cn.itcast.feign.clients包下,而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient。

解决扫描包问题
方式一:
指定Feign应该扫描的包:

@EnableFeignClients(basePackages = "cn.itcast.feign.clients")

方式二:
指定需要加载的Client接口:

@EnableFeignClients(clients = {UserClient.class})

8.8 负载均衡

Feign中本身已经集成了Ribbon依赖和自动配置:
在这里插入图片描述

因此我们不需要额外引入依赖,也不需要再注册RestTemplate对象。

在这里插入图片描述
Feign底层和RestTemplate底层都使用了URLConnection

8.9 Hystrix支持

一、开启Feign的熔断功能
Feign默认也有对Hystrix的集成:
在这里插入图片描述

只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:(在itcast-service-consumer工程添加配置内容)

feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能

注意:
虽然Feign已经继承了Hystrix, 但是如果你要使用@HystrixCommand注解,那么还是得依赖spring-cloud-starter-netflix-hystrix, 这两个依赖并不冲突, Feign中对Hystrix的集成, 主要是对远程服务降级

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

<!--Feign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

二、编写降级服务类UserClientFallback,实现UserClient
  UserClientFallback实现了UserClient , 需要重写queryById方法, 而重写的这个方法即降级方法(替代方法).
  注意UserClientFallback 要添加@Component注解,让Spring管理

@Component
public class UserClientFallback implements UserClient {

    @Override
    public User queryById(Long id) {
        User user = new User();
        user.setUserName("服务器繁忙,请稍后再试!");
        return user;
    }
}

三、在UserClient中,指定刚才编写的实现类
通过fallback来指定降级处理类

@FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 标注该类是一个feign接口
public interface UserClient {

    @GetMapping("user/{id}")
    User queryUserById(@PathVariable("id") Long id);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Cloud是一个用于构建分布式系统的开发工具集合。它提供了一些常用的组件和框架,包括服务注册和发现、负载均衡、断路器、分布式配置等等。在使用Spring Cloud时,有一些常见的错误和注意事项需要注意。 首先,关于Spring Boot和Spring Cloud版本对应错误。在使用Spring Cloud时,需要确保Spring Boot和Spring Cloud的版本兼容。不同版本之间可能存在依赖冲突或不兼容的情况,因此需要根据官方文档或者相关文档来选择合适的版本。 另外,Spring Cloud Config是一个用于集中管理和动态获取配置的工具。它支持从Git、SVN或本地文件系统中获取配置文件,并提供了服务器和客户端支持。你可以通过官方使用说明文档了解更多关于Spring Cloud Config的详细信息。 此外,关于选择使用Nacos还是Eureka作为服务注册和发现组件的问题。Nacos是一个功能更强大的服务注册和发现组件,它整合了Spring Cloud Eureka、Spring Cloud Config和Spring Cloud Bus的功能。使用Nacos可以实现配置的中心动态刷新,而不需要为配置中心新增集群或使用消息队列。另一方面,Eureka是Spring Cloud原生全家桶的一部分,相对来说更加稳定一些。选择使用哪个组件需要根据具体的需求和项目特点来决定。 综上所述,Spring Cloud是一个用于构建分布式系统的开发工具集合,它提供了一些常用的组件和框架。在使用Spring Cloud时,需要注意Spring Boot和Spring Cloud版本的兼容性,并可以使用Spring Cloud Config来动态获取配置。同时,可以选择使用Nacos或Eureka作为服务注册和发现组件,具体选择需要根据项目需求来决定。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值