java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > spring cloud启动部署

spring cloud内容汇总(各个功能模块、启动、部署)的详细过程

作者:小程xy

Spring Cloud 是一套基于 Spring Boot 的框架集合,用于构建分布式微服务架构,文章介绍了Spring Cloud框架及其相关工具的使用,还详细介绍了如何配置和使用这些功能,包括配置文件、依赖管理以及实际应用示例,感兴趣的朋友一起看看吧

Spring Cloud 是一套基于 Spring Boot 的框架集合,用于构建分布式微服务架构。它提供了一系列工具和库,帮助开发者更轻松地管理分布式系统中的关键问题,比如服务注册与发现、负载均衡、分布式配置管理、熔断与降级、链路追踪等。

下图展示了微服务架构中每个主要功能模块的常用解决方案。

一、相关功能的介绍

1. 服务注册与发现

服务注册:服务注册与发现用于让各个服务在启动时自动注册到一个中央注册中心(如 Nacos、Eureka),并且能让其他服务通过注册中心找到并调用它们的地址。
发现:每个服务启动后会将自身的地址和端口信息注册到注册中心;其他服务要调用它时,通过注册中心获取服务实例的地址,而不需要固定的地址

2. 服务调用和负载均衡

服务调用:服务之间的通信方式,可以通过 HTTP(如 RESTful API)或 RPC(远程过程调用)进行服务之间的请求。
负载均衡:在微服务架构中,通常会有多个相同的服务实例分布在不同的服务器上。负载均衡用于在多个实例间分配请求,常见的策略有轮询、随机、最小连接数等,从而提升系统的处理能力和容错性。

3. 分布式事务

分布式事务用于保证多个服务在处理同一个业务操作时的一致性。例如,用户下单时,需要支付服务和库存服务同时完成,如果某一方失败,整个操作需要回滚。

4. 服务熔断和降级

服务熔断:用于防止一个服务的故障传导到其他服务。如果某个服务在短时间内出现大量的错误或响应缓慢,熔断机制会自动切断对该服务的调用,避免对系统造成更大影响。
服务降级:在服务出现问题时,提供降级策略,比如返回默认值或简化响应内容,使系统能够在部分服务不可用的情况下继续运行。

5. 服务链路追踪

服务链路追踪用于跟踪分布式系统中一次请求的完整路径,分析其跨多个服务的执行情况,方便发现延迟或错误。

6. 服务网关

服务网关作为服务的统一入口,处理所有外部请求,提供认证授权、负载均衡、路由分发、监控等功能。它还能对请求进行限流、熔断、降级等保护。

7. 分布式配置管理

分布式配置管理用于集中管理各服务的配置文件,支持动态更新,不需要重启服务。 可以在配置更新后自动推送至各服务节点,使它们能实时更新配置信息,提升了系统的灵活性和一致性。

二、前置内容和准备工作

1、 不同服务之间的调用

下面两个了解其中一个就行

RestTemplate

RestTemplate 是 Spring 提供的一个同步 HTTP 客户端,用于与 RESTful Web 服务进行交互。它支持多种 HTTP 方法,包括 GET、POST、PUT、DELETE 等,简化了调用 REST API 的过程。

在微服务架构中,服务之间需要进行通信,RestTemplate 通过简单的 HTTP 调用实现这一点。通过服务注册和发现机制,可以动态获取服务的地址和端口。

配置RestTemplate

确保 pom.xml 文件中包含了 Spring Web 相关的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

创建 RestTemplate Bean
在 Spring Boot 应用程序中,通常在配置类中创建一个 RestTemplate 的 Bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

RestTemplate常用方法

GET 请求

restTemplate.getForObject(url, String.class);
// getForObject(String url, Class<T> responseType):发起 GET 请求,并返回响应体。

POST 请求

restTemplate.postForObject(url, request, MyResponseObject.class);
// postForObject(String url, Object request, Class<T> responseType):发起 POST 请求,将请求体发送给指定 URL,并返回响应体。

PUT 请求

restTemplate.put(url, request);
// put(String url, Object request):发起 PUT 请求,将请求体发送到指定 URL。

DELETE 请求

restTemplate.delete(url);
// delete(String url):发起 DELETE 请求。

OpenFeign

Feign 是一个声明式的 Web 服务客户端,它简化了与 HTTP 服务交互的方式。使得开发人员能够通过简单的注解方式调用 RESTful Web 服务,而不需要手动编写繁琐的 HTTP 请求代码。(RestTemplate和这个了解一种即可)

引入依赖

如果在使用 Spring Cloud,可以在你的 pom.xml 中加入 Feign 相关的依赖:

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

启用 Feign:
在 Spring Boot 应用的主类或者配置类中添加 @EnableFeignClients 注解,启用 Feign 客户端:

import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableFeignClients  // 启用 Feign 客户端
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

定义 Feign 接口:
Feign 使用接口定义 HTTP 请求。通过注解指定请求的类型和路径:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "account-service")  // 指定服务名称, 这里是指注册到naocs的服务名
public interface AccountClient {
    @GetMapping("/account/balance")
    String getBalance(@RequestParam("userId") String userId);
}

在上面的例子中:

调用 Feign 客户端:
在其他服务中调用 Feign 客户端接口,就像调用本地方法一样:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
    @Autowired
    private AccountClient accountClient;
    @RequestMapping("/order/test")
    public String createOrder(String userId) {
        // 调用 Feign 客户端方法
        String balance = accountClient.getBalance(userId);
        return "Account balance: " + balance;
    }
}

2、 准备工作(引入spring cloud依赖)

1. dependencyManagement

dependencyManagement 是 Maven 构建工具中的一个元素,用于定义项目中依赖的管理方式。它允许我们在父 POM 文件或依赖管理部分中集中声明依赖的版本号和作用范围,所有子模块(子项目)可以自动继承这些声明,而不需要在每个子模块的 pom.xml 中重复定义。

统一依赖版本管理:
dependencyManagement 能够帮助你统一管理多个模块中某个依赖的版本。例如,你可以在一个中央位置(通常是父 POM 文件)声明 Spring Boot 的版本号,这样所有子项目都会使用这个版本,而不需要每个项目中都定义。

简化子项目中的依赖声明:
子项目无需在每个 pom.xml 文件中声明依赖的版本号,只需要定义依赖的 groupIdartifactId,版本号将从 dependencyManagement 中继承。

根项目 pom 文件

<packaging>pom</packaging>
<dependencyManagement>
       <dependencies>
           <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
           <dependency>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-dependencies</artifactId>
               <version>2023.0.3</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
           <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies -->
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-alibaba-dependencies</artifactId>
               <version>2023.0.1.3</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
       </dependencies>
   </dependencyManagement>

子项目 pom 文件

<parent>
    <groupId>com.cloud</groupId>
    <artifactId>learnCloudAlibaba</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
</parent>
<!-- 下面这个是我根项目的pom文件的内容, 要确保上面子项目中 parent 中的 groupid artifactId version 跟下面 根项目的一致-->
<!--    <groupId>com.cloud</groupId>-->
<!--    <artifactId>learnCloudAlibaba</artifactId>-->
<!--    <version>0.0.1-SNAPSHOT</version>-->

2. 引入依赖 spring Cloud

maven官网

Spring Cloud Dependencies

2. 引入依赖 spring Cloud alibaba

spring-cloud-alibaba-dependencies

3. 版本兼容性问题

参考文章 地址

这里我引入的是 springcloud 2023 , springcloudalibaba 2023, springboot 3.2, jdk 17

下面讲解一下每个主要功能模块的部署和配置

三、服务注册与发现 nacos

1. 准备工作

下载 nacos 本地服务 (地址)

添加依赖到子模块

在需要被nacos注册的模块中加入下面配置,启动项目即可在 localhost:8848/nacos 中查看到已经被注册到中央注册中心

<!-- Nacos 服务注册和发现 -->
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>

简单配置

spring.cloud.nacos.discovery.server-addr=localhost:8848

在启动类上加上@EnableDiscoveryClient注解
@EnableDiscoveryClient 是一个注解,用于启用服务注册与发现功能,通常在使用 Spring Cloud 和 Nacos 的服务中添加。它告诉 Spring Boot 应用要注册到服务注册中心,以便其他服务能够发现它。

2. 以单例启动

解压进入nacos的 bin目录,以单例模式启动

.\startup.cmd -m standalone
# localhost:8848/nacos 进行访问, 默认账号密码都是 nacos

3. 常见配置

spring.cloud.nacos.discovery.namespace=命名空间id # 指定命令空间, 不同空间下的实例不可互相访问
spring.cloud.nacos.discovery.group=DEFAULT_GROUP # 默认是DEFAULT_GROUP,指定group,不同group下的实例不可互相访问
spring.cloud.nacos.discovery.cluster-name=BeiJing # 指定当前实例是哪个集群,一般按照地区划分,讲请求发送距离近的实例
spring.cloud.loadbalancer.nacos.enabled=true # 开启 优先向跟当前发送请求的实例 在同一集群的实例发送请求
spring.cloud.nacos.discovery.weight=1 # 当前实例的权值,权值在1-100,默认是1,权值越大越容易接收请求,一般给配置高的服务器权值高一些

4. Nacos 集群架构

对于 Nacos 集群,主要的作用是 实现高可用和数据一致性,保证服务注册和配置管理的可靠性。

集群架构
Nacos 集群通常包含多个节点,部署在不同的机器或虚拟机上,以提供服务注册、配置管理的冗余和高可用性。当一个节点发生故障时,其他节点可以继续提供服务,从而保证系统的稳定运行。

数据一致性(RAFT 协议)
Nacos 集群内部使用 RAFT 协议 来实现服务数据的强一致性。这种一致性保证了同一服务的多个实例在集群中都能被正确地注册和发现。(在集群中的任意以nacos中注册,即可被整个集群的nacos访问)

高可用性和故障恢复

可以通过nginx反向代理,实现只暴漏一个nacos服务地址,nginx内容实现负载均衡
也可以通过loadbalancer或是在 application 中添加集群的所有地址实现简单的负载均衡

# 会选其中一个地址注册服务
spring.cloud.nacos.discovery.server-addr: 172.20.10.2:8870,172.20.10.2:8860,172.20.10.2:8848

5. 集群模式部署

配置数据库

Nacos 集群需要一个共享的数据库来存储配置信息。可以使用 MySQL 作为存储引擎。

在 MySQL 中创建一个数据库:

CREATE DATABASE nacos_config;

进入mysql,执行 Nacos 提供的 SQL 脚本:

mysql> use nacos_config;
Database changed
mysql> source D:\kafka\nacos\conf\mysql-schema.sql

配置 Nacos 集群

打开每个节点的 conf/application.properties 文件,进行以下配置:

server.port=8848 
spring.datasource.platform=mysql
spring.sql.init.platform=mysql
### Count of DB:
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_cofig?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=password

打开每个节点的 conf/cluster.conf 文件,进行以下配置:

172.20.10.2:8848 # 前面是ip地址,内网的或是公网的
172.20.10.2:8860
172.20.10.2:8870

一些坑
这里我要在本地启动三个nacos,那就需要复制 nacos 文件夹,然后分别修改里面的 application.propertiescluster.conf 的配置文件,其中 server.port=8848 端口之间不要离太近。(离太近nacos 2.0 版本会出问题)这里弄成了 8848, 8860, 8870 端口

启动 Nacos 实例

在每台服务器上启动 Nacos 服务。执行以下命令:

.\startup.cmd -m cluster

注意:每个节点启动时,cluster.conf 文件中需要列出所有节点的 IP 和端口。

四、服务调用和负载均衡 LoadBalancer

Spring Cloud LoadBalancer 是 Spring Cloud 中的一个负载均衡模块,用于在服务调用时实现客户端负载均衡。

1. 基本概念

Spring Cloud LoadBalancer 通过客户端负载均衡,在服务调用者和多个实例之间分配流量。它通过服务发现(比如使用 Nacos)获取可用服务实例的列表,并根据不同的负载均衡策略(如轮询、随机等)选择一个实例进行请求分发。

2. 配置环境

在项目中使用 Spring Cloud LoadBalancer,在每个需要使用客户端负载均衡功能的子模块中添加:

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

配置默认策略(轮询)

spring.cloud.loadbalancer.configurations=default

3. 负载均衡的使用方式

Spring Cloud LoadBalancer 支持使用 RestTemplateWebClientOpenFeign 进行负载均衡。

使用 RestTemplate

定义 RestTemplate Bean 并标注 @LoadBalanced 注解:

@Configuration
public class AppConfig {
    @Bean
    @LoadBalanced  // 启用 RestTemplate 的负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

发起请求
使用 @LoadBalancedRestTemplate 时,可以直接通过服务名称调用服务,而不需要手动指定服务的 IP 地址和端口,避免了ip和端口写死, 只向一个实例发送请求的情况。
(我们同一个服务名称一般会有多个实例(分布式), 通过带有 @LoadBalanced注解的 RestTemplate 可以实现负载均衡, 让请求根据我们的配置分别发送到不同的实例)

@Autowired
private RestTemplate restTemplate;
public String callService() {
    // 使用服务名代替实际地址
    return restTemplate.getForObject("http://module2/api/v1/data", String.class);
}

4. 测试 负载均衡

假设我们有一个根模块, 根模块下面有三个模块 module1,module2,module3。module2 和 module3 我们在本地的不同端口各启动两个(模拟分布式)。当我们通过module1调用 module2 和 module3 的方法,观察请求的分布

将多个实例注册到nacos

这里演示的nacos用的单例模式,最终效果如下图

下面是如何启动让module2和module3分别启动两个实例

查看请求分布

三个module中的demo代码
module1 中,通过get方法访问 /test 时,会向 module2 和 module3 的实例发送请求。(spring cloud loadbalancer 会根据配置实现负载均衡)

@Autowired
    RestTemplate restTemplate;
    @GetMapping("/test")
    public String test() {
        String module2 = restTemplate.getForObject("http://module2/api/test", String.class);
        String module3 = restTemplate.getForObject("http://module3/api/test", String.class);
        return module2 + "\n" + module3;
    }

module2 中

@GetMapping("/api/test")
    public String test() {
        System.out.println("module2 test");
        return "module2 test";
    }

module3 中

@GetMapping("/api/test")
    public String test() {
        System.out.println("module3 test");
        return "module3 test";
    }

当我们在浏览器访问 16 次 /test 的时候, restTemplate.getForObject 会向对应的module发送16次请求。观察module2的两个实例和module3的两个实例可以看到 分别接收到了 8 次请求(默认是轮询)。如下图

5. 使用不同的负载均衡器

上面的配置是所有服务都是用默认的负载均衡器,即轮询的负载均衡器。下面讲一下怎么让不同服务使用不同的负载均衡器

创建两个配置文件,把轮询负载均衡器和随机负载均衡器注册为bean

@Configuration
public class DefaultLoadBalancerConfig {
    @Bean
    ReactorLoadBalancer<ServiceInstance> roundRobinLoadBalancer(Environment environment,
                                                                LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); // 获取负载均衡器名称
        return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}
@Configuration
public class RandomLoadBalancerConfig {
    @Bean
        // 定义一个Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, // 注入环境变量
                                                            LoadBalancerClientFactory loadBalancerClientFactory) { // 注入负载均衡器客户端工厂
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); // 获取负载均衡器的名称
        // 创建并返回一个随机负载均衡器实例
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

创建restTempalte

// module2 使用默认,module3 使用随机
@Configuration
@LoadBalancerClients({
        @LoadBalancerClient(value = "module2", configuration = RandomLoadBalancerConfig.class),
        @LoadBalancerClient(value = "module3", configuration = DefaultLoadBalancerConfig.class)
})
public class AppConfig {
    @Bean
    @LoadBalanced  // 启用 RestTemplate 的负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

五、分布式事务 seata

Seata 是一款开源的分布式事务解决方案,旨在解决微服务架构中跨服务的事务一致性问题。它提供了易于使用、性能高效的分布式事务管理功能,帮助开发者在分布式系统中保持数据一致性。

1. Seata 的概要

Seata 由阿里巴巴发起,最初的目的是为了解决微服务场景下的数据一致性问题,尤其是在分布式数据库事务中。Seata 提供了全局事务、分支事务以及资源管理等功能。

TM请求TC开启一个全局事务,TC会生成一个XID作为该全局事务的编号,XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起;RM请求TC将本地事务注册为全局事务的分支事务,通过全局事务的XID进行关联;TM请求TC告诉XID对应的全局事务是进行提交还是回滚;TC驱动RM将XID对应的自己的本地事务进行提交还是回滚;

下面以官网的案例来演示整个使用过程

2. 准备工作

下载seata服务(tc)
下载seata-service压缩包,解压进入/bin (下载地址), 执行 ./seata-server.bat

配置 Seata
conf 目录下,你会找到一个配置文件 application.yml。编辑该文件以配置 Seata Server 的各种参数,比如数据库连接、事务日志等。

 server:
   port: 7091
 spring:
   application:
     name: seata-server
 logging:
   config: classpath:logback-spring.xml
   file:
     path: ${log.home:${user.home}/logs/seata}
   extend:
     logstash-appender:
       destination: 127.0.0.1:4560
     kafka-appender:
       bootstrap-servers: 127.0.0.1:9092
       topic: logback_to_logstash
 console:
   user:
     username: seata
     password: seata
 seata:
   config:
     # support: nacos, consul, apollo, zk, etcd3
     type: nacos
     nacos:
       server-addr: 127.0.0.1:8848
       namespace:
       group: SEATA_GROUP
       username: nacos
       password: nacos
   registry:
     # support: nacos, eureka, redis, zk, consul, etcd3, sofa
     type: nacos
     nacos:
       application: seata-server
       server-addr: 127.0.0.1:8848
       group: SEATA_GROUP
       namespace:
       cluster: default
       username: nacos
       password: nacos
   store:
     # support: file 、 db 、 redis 、 raft
     mode: db
   db:
       datasource: druid
       db-type: mysql
       driver-class-name: com.mysql.jdbc.Driver
       url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
       user: mysql
       password: mysql
       min-conn: 10
       max-conn: 100
       global-table: global_table
       branch-table: branch_table
       lock-table: lock_table
       distributed-lock-table: distributed_lock
       query-limit: 1000
       max-wait: 5000
   #  server:
   #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
   security:
     secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
     tokenValidityInMilliseconds: 1800000
     ignore:
       urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**

数据库配置
创建 seata 数据库,并访问 该地址,在seata数据库中执行里面所有的sql脚本
创建 seata_account, seata_order, seata_storage数据库,访问 该地址, 在上面三个数据库中都创建 UNDO_LOG 表,在对应的数据库中创建对应的 业务表。(地址中有sql脚本)

启动 Seata Server
使用以下命令启动 Seata Server:

sh bin/seata-server.sh

最终效果如下

创建三个模块(account, order, storage),加入相关依赖(数据库驱动,mybatis…),然后按照下面加入 nacosseata 依赖和配置

3. Seata 与 Spring Boot 集成

3.1 添加 Seata 依赖

<dependency>
   <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3.2 配置 Seata

在 Spring Boot 项目的 application.yml 中配置 Seata。

seata:
  registry:
    type: nacos
    nacos:
      server-addr: localhost:8848
      namespace: ""
      group: SEATA_GROUP # 组名  跟我们之前配置的seata的配置文件的是对应的
      application: seata-server # 服务名 跟我们之前配置的seata的配置文件的是对应的
  tx-service-group: default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default
  data-source-proxy-mode: AT

3.3 开启事务管理

@GlobalTransactional 是 Seata 提供的注解,用于实现分布式事务的管理。它是 Seata 的全局事务控制器,通过这个注解,你可以在一个跨多个微服务的操作中,确保数据的一致性和事务的回滚。

作用:

基本语法:

@GlobalTransactional(name = "your-global-tx-name", rollbackFor = Exception.class)
public void yourMethod() {
    // Your business logic
}

参数:

4. 演示流程

下面是通过访问 order 订单服务,然后执行 创建新的订单-> 扣除用户支付的钱 -> 减去用户购买商品的数量更新商品库存

@RestController
public class CreateOrderController {
    @Autowired
    OrderMapper orderMapper;
    @Autowired
    RestTemplate restTemplate;
    @RequestMapping("/order/test")
    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public String createOrder(@RequestParam String userId,
                              @RequestParam String commodityCode,
                              @RequestParam Integer count,
                              @RequestParam Integer money
    ) {
        // 创建订单
        Order order = new Order(null, userId, commodityCode, count, money);
        orderMapper.insert(order);
        // 扣除账户余额
        Map<String, String> mp1 = new HashMap<>();
        mp1.put("userId", userId);
        mp1.put("money", money.toString());
        String resp1 = restTemplate.postForObject("http://localhost:8001/account/test", mp1, String.class);
        // 减去用户购买商品数量,更新库存
        Map<String, String> mp2 = new HashMap<>();
        mp2.put("commodityCode", commodityCode);
        mp2.put("count", count.toString());
        String resp2 = restTemplate.postForObject("http://localhost:8003/storage/test", mp2, String.class);
        if ("ok".equals(resp1) && "ok".equals(resp2)) {
            return "ok";
        }
        return "error";
    }
}

如果在调用其他服务时(扣除账户余额,更新库存时),如果抛出异常的话,整个事务就会回滚。比如减去用户购买商品数量时发现库存不足,抛出异常,整个事务回滚,之前的创建订单扣除账户余额都会回滚

六、服务熔断和降级 sentinel

Sentinel 是阿里巴巴开源的分布式系统流量控制组件,主要用于保护微服务在高并发、突发流量等场景下的稳定性和可靠性。Sentinel 提供了 流量控制、熔断降级、系统自适应保护等机制

1. Sentinel 的核心功能

Sentinel 提供了以下核心功能:

2. Sentinel 的基本概念

在 Sentinel 中,核心是“资源”,可以是服务、接口或方法。每一个资源都会绑定一套限流规则或熔断规则。以下是 Sentinel 的几个基本概念:

3. Sentinel 的监控和控制台

Sentinel 提供了 Dashboard 管理控制台,可以用来监控各个资源的访问情况,并动态配置流量控制和熔断规则。(下载地址, 下载jar包)

启动 jar 包。

访问 http://localhost:8888/ (这里我设置的端口是 8888) 进入控制台。
账号密码都是 sentinel

引入 sentinel

 <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring.cloud.sentinel.transport.dashboard=localhost:8888

访问module任意controller后就可以看到 module1 加入到控制台

4. @SentinelResource注解

@SentinelResourceSentinel 中用于标记 方法 的注解,它允许你在方法调用时应用 Sentinel 的规则和控制策略。通过使用这个注解,你可以将 Sentinel 的流量控制、熔断降级、热点参数限流等功能集成到业务逻辑中。

1. value 参数:资源名称

2. blockHandler

示例:

@SentinelResource(value = "myResource", blockHandler = "handleBlock")
public String someMethod() {
    // 你的业务逻辑
    return "Hello, Sentinel!";
}
// 流控或熔断时的处理方法
public static String handleBlock(BlockException ex) {
    return "Service is currently unavailable due to high traffic. Please try again later.";
}

在这个例子中,当 myResource 资源被流控或熔断时,handleBlock 方法将被调用,返回一条友好的错误信息。

3. fallback

参数:降级处理方法

示例:

@SentinelResource(value = "myResource", fallback = "fallbackMethod")
public String someMethod() {
    // 可能会抛出异常的业务逻辑
    throw new RuntimeException("Something went wrong");
}
// 降级方法
public static String fallbackMethod(Throwable ex) {
    return "Service is temporarily unavailable due to internal error. Please try again later.";
}

在这个例子中,当 someMethod 方法抛出异常时,fallbackMethod 会被调用,返回一个默认的降级响应。

4. exceptionsToIgnore 参数:忽略的异常类型

5. blockHandlerClass 参数:自定义流控处理类

示例:

@SentinelResource(value = "myResource", blockHandler = "handleBlock", blockHandlerClass = BlockHandler.class)
public String someMethod() {
    // 业务逻辑
    return "Hello, Sentinel!";
}
// 自定义流控处理类
public class BlockHandler {
    public static String handleBlock(BlockException ex) {
        return "Service is temporarily unavailable due to traffic control.";
    }
}

6. fallbackClass 参数:自定义降级处理类

示例:

@SentinelResource(value = "myResource", fallback = "fallbackMethod", fallbackClass = FallbackHandler.class)
public String someMethod() {
    // 业务逻辑
    throw new RuntimeException("Something went wrong");
}
// 自定义降级处理类
public class FallbackHandler {
    public static String fallbackMethod(Throwable ex) {
        return "Service is temporarily unavailable due to an error.";
    }
}

5. 流量控制

5.1 流控模式

链路流控模式指的是,当从指定接口过来的资源请求达到限流条件时,开启限流。

# 关闭Context收敛,这样被监控方法可以进行不同链路的单独控制
spring.cloud.sentinel.web-context-unify: false
# 比如我 /test1 和 /test2 接口下都调用了 test 方法,那么我可以在sentinel 控制台中分别对 /test1 和 /test2 的 test 进行设置

5.2 流控效果

快速拒绝: 既然不再接受新的请求,那么我们可以直接返回一个拒绝信息,告诉用户访问频率过高。
预热: 依然基于方案一,但是由于某些情况下高并发请求是在某一时刻突然到来,我们可以缓慢地将阈值提高到指定阈值,形成一个缓冲保护
排队等待: 不接受新的请求,但是也不直接拒绝,而是进队列先等一下,如果规定时间内能够执行,那么就执行,要是超时就算了。

5.3 实现对方法的限流控制

我们可以使用到@SentinelResource对某一个方法进行限流控制,无论是谁在何处调用了它,,一旦方法被标注,那么就会进行监控。

@RestController
public class TestController {
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    MyService myService;
    @GetMapping("/test1")
    public String test1() {
        return myService.test();
    }
    @GetMapping("/test2")
    public String test2() {
        return myService.test();
    }
}
// ------------------------
@Service
public class MyService {
    @SentinelResource("mytest")	// 标记方法
    public String test() {
        return "test";
    }
}

添加 spring.cloud.sentinel.web-context-unify=false, 可以对 /test1 和 /test2 的 mytest单独控制

不添加的, 无法单独控制

一些坑
如果在方法上添加了 @SentinelResource 注解,但是不在控制台中显示的话(不显示mytest), 可能是因为添加的方法没有加入到spring容器中进行管理。比如我当时下面这样写就出现了不在控制台显示的情况。

@RestController
public class TestController {
    @GetMapping("/test1")
    public String test1() {
        return test();
    }
    @SentinelResource("mytest")
    public String test() {
        return "test";
    }
}

5.4 处理限流情况

当访问某个 接口 出现限流时,会抛出限流异常,重定向到我们添加的路径

@RequestMapping("/blockPage")
    public String blockPage() {
        return "blockPage";
    }
# 用于设置当 流控 或 熔断 触发时,返回给用户的 阻塞页面
spring.cloud.sentinel.block-page=/blockPage

当访问的某个 方法 出现了限流,为 blockHander 指定方法,当限流时会访问 test2, 并且可以接受 test 的参数

@Service
public class MyService {
    @SentinelResource(value = "mytest", blockHandler = "test2")
    public String test(int id) {
        return "test";
    }
    public String test2(int id, BlockException ex) {
        return "test 限流了 " + id;
    }
}

5.5 处理异常的处理

当调用 test 时抛出了异常的话,那么就会执行 fallback 指定的方法, exceptionsToIgnore 指定的是忽略掉的异常。(限流的话会抛出限流的异常,也会被捕获,执行 fallback 指定的方法)
如果 fallback 和 blockHandler 都指定了方法,出现限流异常会优先执行 blockHandler 的方法

@Service
public class MyService {
    @SentinelResource(value = "mytest", fallback = "except", exceptionsToIgnore = IOException.class)
    public String test(int id) {
        throw new RuntimeException("hello world");
    }
// 注意参数, 必须是跟 test 的参数列表相同, 然后可以多加一个获取异常的参数
    public String except(int id, Throwable ex) { 
        System.out.println("hello = " + ex.getMessage());
        return ex.getMessage();
    }
}

5.6 热点参数限流

对接口或方法的某个参数进行限流

@GetMapping("/test1")
@SentinelResource(value = "test1")
public String test1(@RequestParam(required = false) String name, @RequestParam(required = false) String age) {
    return name + " " + age;
}

6. 熔断降级

1. 服务熔断

当某个服务的错误率或失败次数(如异常或超时)超过预设的阈值时,熔断器就会“断开”,该服务的调用将不会继续执行。熔断器“断开”后,所有请求都会被 拒绝,直到熔断器进入 恢复阶段,然后根据预设规则恢复正常工作。

2. 熔断规则

2.1 熔断规则

熔断规则定义了什么情况下触发熔断。常见的触发条件有:

这些规则是可以配置的,通常会在应用初始化时进行设置。

2.2 熔断恢复机制
熔断后,Sentinel 会进入 自恢复机制,通过设定的时间窗口,逐渐恢复正常的服务调用。这一恢复过程通常包括以下两个阶段:

3. 服务降级 (Service Degradation)

服务降级是指在某些情况下,通过返回一个默认值、简单处理或快速失败来减少服务的压力,避免因资源超载或异常请求导致服务崩溃。Sentinel 通过配置不同的降级策略,使得系统能够在流量激增或服务不稳定时自动切换到降级模式。

最后简单介绍一下限流,熔断,降级之间的联系。 根据降级的概念,当出现限流熔断的时候都会触发降级的方法,只不过熔断会根据自己的配置,来跟熔断的服务断开联系,不再接受请求。而限流的话不会断开服务,而是继续接受请求,如果请求不满足 限流规则的话,还是会进入到降级的方法

七、服务链路追踪

在zipkin官网下载 zipkin.jar 包。下载地址

Micrometer Tracing 是 Spring 官方在现代可观测性(Observability)体系中的新工具,用于实现分布式链路追踪。

1. 核心概念

Span

Trace

Context Propagation

2. 工作原理

生成和传播追踪信息

采样策略

导出追踪数据

3. 简单配置

在 Spring Boot 3+ 项目中,Micrometer Tracing 的依赖配置如下:

引入依赖
pom.xml 中添加以下依赖:

<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-observation</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<!-- 使用feign的话加入下面这个依赖 -->
<!--        <dependency>-->
<!--            <groupId>io.github.openfeign</groupId>-->
<!--            <artifactId>feign-micrometer</artifactId>-->
<!--        </dependency>-->

配置

management:
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans
  tracing:
    sampling:
      probability: 1.0 #采样率默认0.1(10次只能有一次被记录),值越大手机越及时

之后就可以访问 http://127.0.0.1:9411

八、服务网关 gateway

Gateway(网关)是微服务架构中的一个重要组件,它通常用作客户端和多个微服务之间的中介,负责请求的路由、负载均衡、认证、限流、安全控制等功能。它通常部署在前端,起到了“入口”作用,是微服务的前端统一访问点。

1. 网关的核心功能

网关的核心职责是将外部请求路由到相应的微服务,同时提供一些重要的功能:

2. 准备工作

引入依赖

<!-- gateway 依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 需要基于注册中心转发请求的话,加上 nacos 依赖 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 如果用到 lb: 的话需要在引入getaway的pom中引入loadbalancer -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

配置路由规则:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      # 配置路由,这里是一个列表,每一项都包含很多信息
      routes:
        - id: module1 # 路由名称(nacos中的服务名)
          uri: lb://module1 # 路由地址,lb表示使用负载均衡到微服务,也可以使用http正常转发
          predicates: # 路由规则,决定哪些请求会被路由
            - Path=/test1/** # 请求的路径匹配规则
          filters: # 向请求头中添加 test=hello world
            - AddRequestHeader=test, hello world
server:
  port: 8002

在 Spring Cloud Gateway 中,predicatesfilters 是配置路由规则和处理请求

常见的 predicates(路由匹配条件):

上面的predicatesfilters只写了一部分,具体可以参考spring官网 地址

Spring Cloud Gateway 与其他网关对比

3. 自定义全局和局部过滤器

过滤器是网关的一个重要特性,可以在请求和响应的生命周期中做一些额外的处理。

3.1 自定义全局过滤器

在 Spring Cloud Gateway 中,全局过滤器(Global Filters)用于在请求和响应过程中对所有路由进行处理。

过滤器的作用:

1. 创建一个全局过滤器

首先,需要创建一个实现 GlobalFilter 接口的类。在这个类中,你可以定义过滤器的逻辑。

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AddHeaderGlobalFilter implements GlobalFilter, Ordered {
   @Override
   public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       return chain.filter(exchange);
   }
   @Override
   public int getOrder() {
       return 1;
   }
}

2. 全局过滤器的工作原理

3. 示例:添加请求头

假设我们需要为每个请求添加一个特定的请求头。

package com.cloud.gateway.config;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AddHeaderGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求的 headers
        HttpHeaders headers = exchange.getRequest().getHeaders();
        // 打印原始请求头
        System.out.println("Request Headers: " + headers);
        // 为请求添加一个新的头部
        exchange.getRequest().mutate()
                .header("test", "hello world") // 添加请求头
                .build();
        // 继续传递到下一个过滤器
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        return 1;
    }
}

3.1 自定义局部过滤器 自定义局部过滤器(GatewayFilter):

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@Component
public class AuthFilter  extends AbstractGatewayFilterFactory<AuthFilter.Config> {
    public AuthFilter () {
        super(Config.class);
    }
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            System.out.println("AuthFilter"); 
            // 下面实现自己的逻辑
            String token = exchange.getRequest().getHeaders().getFirst("token");
            if (token == null || !token.equals(config.getToken())) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);  // 继续处理链中的其他过滤器
        };
    }
    public static class Config {
        private String token;
        public String getToken() {
            return token;
        }
        public void setToken(String token) {
            this.token = token;
        }
    }
}

使用局部过滤器

局部过滤器通常在路由配置中使用,你可以将它应用于特定的路由,例如:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://USER-SERVICE
          predicates:
            - Path=/users/**
          filters:
            - AuthFilter  # 这里引用自定义的局部过滤器

九、分布式配置管理 nacos

分布式配置管理功能的主要作用是在不同的服务之间集中管理和统一分发配置。这使得系统在配置变更时无需重启服务,可以实时更新配置,从而达到快速响应的效果。

基本概念

Nacos 配置管理的使用步骤

引入 Nacos 配置管理

引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

配置 Nacos 服务器地址
bootstrap.yml 文件中,设置 Nacos 配置中心地址和必要的配置信息:

spring:
  application:
 	 name: module1 
  profiles:
     active: dev 
  cloud:
    nacos:
      config:
        server-addr: localhost:8848 # 服务地址
        file-extension: yaml

data_id 一般命名采用 application.name-profiles.active.filex-extension,根据上面的配置,我的dataid就是 module1-dev.yaml

获取配置
可以使用 Spring Boot 的 @Value 注解来获取 Nacos 中的配置项。例如:

@RestController
@RefreshScope
public class TestNacosConfigController {
    @Value("${test}") 		// 获取到 test 的值
    private String test;
    @GetMapping("/nacos/config")
    public String nacosConfig() {
        return test;
    }
}

动态刷新配置
使用 @RefreshScope 注解,自动刷新配置:(当我们配置中心修改时,不需要重启项目,test 就会自动更新)

@RestController
@RefreshScope
public class TestController {
    @Value("${test}")
    private String test;
    @GetMapping("/nacos/config")
    public String getConfig() {
        return test;
    }
}

我踩的一些坑:

这里我学习的时候就遇到了,通过第一个配置(2023.0.1.3)死活获取不到 test,但是用第二个配置(降低版本的),其他的都没改,就可以获取到。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2023.0.1.2</version>
</dependency>

到此这篇关于spring cloud内容汇总(各个功能模块、启动、部署)的详细过程的文章就介绍到这了,更多相关spring cloud启动部署内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文