彼方尚有荣光在|

_xiaolin

园龄:2年9个月粉丝:3关注:5

SpringCloud Alibaba

SpringCloud Alibaba

一、入门简介

1、why会出现SpringCloud alibaba

1.1、Spring Cloud Netflix项目进入维护模式

spring.io/blog/2018/1…

image-20230321123357355

image-20230321123405959

1.2、Spring Cloud Netflix Projects Entering Maintenance Mode

1.2.1、什么是维护模式

image-20230321123441928

将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。 我们将修复 block 级别的 bug 以及安全问题,我们也会考虑并审查社区的小型 pull request。

1.2.2、进入维护模式意味着什么呢?

进入维护模式意味着

Spring Cloud Netflix 将不再开发新的组件 我们都知道Spring Cloud 版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主

新组件功能将以其他替代平代替的方式实现

image-20230321125914412

2、SpringCloud alibaba带来了什么

2.1、是什么

官网:

Spring Cloud Alibaba 参考文档 (spring-cloud-alibaba-group.github.io)

spring-cloud-alibaba/README-zh.md at 2021.x · alibaba/spring-cloud-alibaba · GitHub

诞生: 2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。

image-20230321130044664

2.2、能干嘛

  1. 服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  2. 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  3. 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  4. 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  5. 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  6. 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。

2.3、去哪下载

spring-cloud-alibaba/README-zh.md at 2021.x · alibaba/spring-cloud-alibaba · GitHub

2.4、怎么玩

image-20230321130939615

3、SpringCloud alibaba学习资料获取

3.1、官网

image-20230321131034440

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

SpringCloud Alibaba进入了SpringCloud官方孵化器,而且毕业了

spring.io/projects/sp…

3.2、英文版

github.com/alibaba/spr…

spring-cloud-alibaba-group.github.io/github-page…

3.3、中文版

spring-cloud-alibaba/README-zh.md at 2021.x · alibaba/spring-cloud-alibaba · GitHub

二、SpringCloud Alibaba Nacos服务注册和配置中心

1、Nacos简介

1.1、为什么叫Nacos

前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。

1.2、是什么

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos: Dynamic Naming and Configuration Service

Nacos就是注册中心 + 配置中心的组合

等价于:Nacos = Eureka+Config +Bus

1.3、能干嘛

替代Eureka做服务注册中心

替代Config做服务配置中心

1.4、去哪下

github.com/alibaba/Nac…

官网文档:

home (nacos.io)

spring-cloud-alibaba-group.github.io/github-page…

1.5、各种注册中心比较

image-20230321144420919

据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验

2、安装运行Nacos

本地Java8+Maven环境已经OK

2.1、先从官网下载Nacos:github.com/alibaba/nac…

image-20230321144532889

2.2、解压安装包,直接运行bin目录下的startup.cmd

startup.cmd -m standalone

新版的需要修改配置application.properties

使用以下程序生成秘钥:

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class SecretKeyGenerator {

    public static SecretKey generate() {
        try {
            // 使用AES算法生成256位的SecretKey
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(256);
            return keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        SecretKey secretKey = generate();
        System.out.println(Base64.getEncoder().encodeToString(secretKey.getEncoded()));
    }
} 

搜索并添加到配置文件中:

nacos.core.auth.plugin.nacos.token.secret.key=

image-20230321144935776

2.3、登录

localhost

默认账号密码都是:nacos

2.4、结果页面:

image-20230321145140444

3、Nacos作为服务注册中心演示

3.1、官网文档

Spring Cloud Alibaba Reference Documentation (spring-cloud-alibaba-group.github.io)

3.2、基于Nacos的服务提供者

3.2.1、新建cloudalibaba-provider-payment9001
3.2.2、改pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.lxg.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-provider-payment9001</artifactId>

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project> 
3.2.3、改yml
server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*' 
3.2.4、主启动类
package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @auther xiaolin
 * @creatr 2023/3/21 15:00
 */
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001
{
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9001.class, args);
    }
} 
3.2.5、业务类
package com.lxg.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @auther xiaolin
 * @creatr 2023/3/21 15:00
 */
@RestController
public class PaymentController
{
    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id)
    {
        return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
    }
} 
3.2.6、测试

http://localhost:9001/payment/nacos/1

http://localhost:8848/nacos

image-20230321172943353

nacos服务注册中心+服务提供者9001都OK了

3.2.7、新建9002

新建cloudalibaba-provider-payment9002

与9001完全一致,只需要更改端口号即可

或者取巧不想新建重复体力劳动,直接拷贝虚拟端口映射:

image-20230321173221268

3.3、基于Nacos的服务消费者

3.3.1、新建cloudalibaba-consumer-nacos-order83模块
3.3.2、pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.lxg.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-consumer-nacos-order83</artifactId>

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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

        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.lxg.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project> 

注意:老版的nacos自带ribbon负载均衡,但是新版的已经没有,需要加入loadbalancer依赖

3.3.3、yml文件
server:
  port: 83

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider 
3.3.4、主启动类
package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @auther xiaolin
 * @creatr 2023/3/22 15:36
 */
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83
{
    public static void main(String[] args)
    {
        SpringApplication.run(OrderNacosMain83.class,args);
    }
} 
3.3.5、业务类

ApplicationContextBean:

package com.lxg.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @auther xiaolin
 * @creatr 2023/3/22 15:38
 */

@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
} 

OrderNacosController

package com.lxg.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @auther xiaolin
 * @creatr 2023/3/22 15:42
 */

@RestController
@Slf4j
public class OrderNacosController {



    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @Resource
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/payment/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id){
        return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
    }
} 
3.3.6、测试

Nacos

image-20230322160701636

localhost:83/consumer/payment/nacos/13

image-20230322160716010

image-20230322160724805

实现了负载均衡

3.4、服务注册中心对比

3.4.1、各种注册中心的对比

Nacos全景图所示

image-20230322162700944

Nacos和CAP

image-20230322162800642

Nacos 支持AP和CP模式的切换:

C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。

何时选择使用何种模式?
一般来说,
如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。

如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。

curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP' 

4、Nacos作为服务配置中心演示

4.1、Nacos作为配置中心-基础配置

4.1.1、新建cloudalibaba-config-nacos-client3377
4.1.2、pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.lxg.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-config-nacos-client3377</artifactId>

    <dependencies>
        <!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos-discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--web + actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--一般基础配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

    </dependencies>

</project> 

注意,使用了bootstrap.yml就别忘记引入bootstrap依赖

4.1.3、yml文件

why配置两个:

Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,
拉取配置之后,才能保证项目的正常启动。

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application

优先级高先加载,正常情况应该是被覆盖的,但是bootstrap是不会被application覆盖的 

bootstrap.yml:

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置

  # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
  # nacos-config-client-dev.yaml 

application.yml:

spring:
  profiles:
    active: dev # 表示开发环境 
4.1.4、主启动类
package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @auther xiaolin
 * @creatr 2023/3/22 23:05
 */
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377
{
    public static void main(String[] args) {
        SpringApplication.run(NacosConfigClientMain3377.class, args);
    }
} 
4.1.5、业务类

ConfigClientController:

package com.lxg.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @auther xiaolin
 * @creatr 2023/3/22 23:05
 */
@RestController
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
public class ConfigClientController
{
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
} 

@RefreshScope:

image-20230322233012515

4.1.6、在nacos中添加配置信息

Nacos中的匹配规则:

Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则

官网:

nacos.io/zh-cn/docs/…

image-20230322233121009

最后公式:

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} 

配置新增:

image-20230322233229995

nacos-config-dev.yaml

Nacos界面配置对应

image-20230322233303848

设置DataId

公式:spring.application.name−{spring.application.name}-spring.application.name−{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

prefix 默认为 spring.application.name 的值
spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置。
file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置 

小总结说明:

image-20230322233502156

历史配置:

Nacos会记录配置文件的历史版本默认保留30天,此外还有一键回滚功能,回滚操作将会触发配置更新

回滚:

image-20230322233533542

4.1.7、测试

启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件

运行cloud-config-nacos-client3377的主启动类

调用接口查看配置信息:http://localhost:3377/config/info

image-20230322233629171

自带动态刷新

修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新

image-20230322233715502

4.2、Nacos作为配置中心-分类配置

4.2.1、问题

多环境多项目管理

问题1: 实际开发中,通常一个系统会准备 dev开发环境 test测试环境 prod生产环境。 如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

问题2: 一个大型分布式微服务系统会有很多微服务子项目, 每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境...... 那怎么对这些微服务配置进行管理呢?

4.2.2、Nacos的图形化管理界面

配置管理

image-20230326232700647

命名空间

image-20230326232754512

Namespace+Group+Data ID三者关系?为什么这么设计?

  1. 是什么

类似Java里面的package名和类名,最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。

  1. 三者情况

    image-20230326232851285

    默认情况: Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT

    Nacos默认的命名空间是public,Namespace主要用来实现隔离。 比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

    Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

    Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。 比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房, 这时就可以给杭州机房的Service微服务起一个集群名称(HZ), 给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

    最后是Instance,就是微服务的实例。

4.2.3、案例

三种方案加载配置

  1. DataID方案

    指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置

    默认空间+默认分组+新建dev和test两个DataID:

    新建dev配置DataID:

    image-20230327000440545

    新建test配置DataID:

    image-20230327000458138

    通过spring.profile.active属性就能进行多环境下配置文件的读取:

    image-20230327000523744

    测试:

    http://localhost:3377/config/info

    配置是什么就加载什么

  2. Group方案

    通过Group实现环境区分

    新建group

    image-20230327000604885

    在nacos图形界面控制台上面新建配置文件DataID:

    image-20230327000631223

    bootstrap+application:

    image-20230327000717789

    在config下增加一条group的配置即可。 可配置为DEV_GROUP或TEST_GROUP

  3. Namespace方案

    新建dev/test的Namespace:

    image-20230327000800068

    回到服务管理-服务列表查看:

    image-20230327000821826

    按照域名配置填写:

    image-20230327000843775

    YML:

    bootstrap:

    # nacos配置
    server:
      port: 3377
    
    spring:
      application:
        name: nacos-config-client
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #Nacos服务注册中心地址
          config:
            server-addr: localhost:8848 #Nacos作为配置中心地址
            file-extension: yaml #指定yaml格式的配置
            group: DEV_GROUP
            namespace: b4f3cb1a-30bc-4230-92a6-3c81497217f7
      # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
      # nacos-config-client-dev.yaml 
    

    application:

    # Nacos注册配置,application.yml
    spring:
      profiles:
        #active: test
        active: dev
        #active: info 
    

5、Nacos集群和持久化配置(重要)

5.1、官网说明

nacos.io/zh-cn/docs/…

5.1.1、官网架构图

image-20230327090705166

image-20230327091240080

5.1.2、上图官网翻译,真实情况

即VIP就是虚拟ip配置nginx进行访问

image-20230327090731675

5.1.3、说明

默认Nacos使用嵌入式数据库实现数据的存储。(如Nacos关掉后再打开,里面的配置依然存在)但是因为这样,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。 为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。

image-20230327091013057

按照上述,我们需要mysql数据库

官网说明:nacos.io/zh-cn/docs/…

5.2、Nacos持久化配置解释

5.2.1、Nacos默认自带的是嵌入式数据库derby

github.com/alibaba/nac…

观察到pom文件引入了derby

5.2.2、derby到mysql切换配置步骤
  1. nacos安装目录conf目录下找到sql脚本

    新版是mysql-schema.sql,执行脚本(新版需要自建数据库)

  2. nacos安装目录conf目录下找到application.properties

    添加配置

    spring.datasource.platform=mysql
     
    db.num=1
    db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
    db.user=root
    db.password=123456 
    
5.2.3、启动

启动Nacos,可以看到是个全新的空记录界面,以前是记录进derby

5.2.4、新建配置文件

image-20230327093102055

已经保存进mysql数据库

5.3、Linux版 Nacos+MySQL生产环境配置

这个章节一堆坑!!!!!!

若是三台机器搭建需要每一台都按照如下配置

5.3.1、首先安装好mysql

Linux 安装Mysql 详细教程(图文教程)-CSDN博客

5.3.2、安装jdk(1.8+)

Linux 安装JDK详细步骤_linux 安装 jdk_Charge8的博客-CSDN博客

5.3.3、安装Nacos

Release 2.2.1 (Mar 17th, 2023) · alibaba/nacos · GitHub

5.3.4、Linux服务器上mysql数据库配置

SQL脚本在哪里:

image-20230327211633758

复制sql语句执行即可

5.3.5、application.properties 配置

image-20230327211726052

更改数据存放位置:

spring.datasource.platform=mysql
 
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456 

新版的需要修改配置application.properties!!!!!!!!

nacos.core.auth.plugin.nacos.token.secret.key=

报错改一下午!!!!

key可以通过以上的类进行生成:[tip](# 2.2、解压安装包,直接运行bin目录下的startup.cmd)

5.3.6、需要更改机器的hostname

hostname -i 查看主机名(不能是127.0.0.1)

如果是,需要修改:

vi /etc/hostname

直接写名字
如:
centos01 

继续 vi /etc/hosts

主机映射:

192.168.131.101(主机实际ip) centos01 

然后重启才可以生效

5.3.7、Linux服务器上nacos的集群配置cluster.conf

image-20230327212312197

如果是伪分布集群可以:

image-20230327212322393

这个IP不能写127.0.0.1,必须是 Linux命令hostname -i能够识别的IP

image-20230327212346254

真实三台机器:

image-20230327212423127

5.3.8、编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口

新版已经自带这个功能了(不是伪分布式和新版的可以跳过)

/mynacos/nacos/bin 目录下有startup.sh

平时单机版的启动,都是./startup.sh即可。

但是

集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。 命令:./startup.sh -p 3333 表示启动端口号为3333的nacos服务器实例,和上一步的cluster.conf配置的一致。

image-20230327212521434

执行方式:image-20230327212540064

5.3.9、Nginx的配置,由它作为负载均衡器

安装好nginx

修改配置文件:

image-20230327212701850

老版:

upstream cluster{
        server 127.0.0.1:3333;
        server 127.0.0.1:4444;
        server 127.0.0.1:5555;
    }	
    
    
    server {
        listen       1111;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location / {
            #root   html;
            #index  index.html index.htm;
            proxy_pass http://cluster;
        }
.......省略 

image-20230327212717394

新版有grpc,需要多以下配置,否则启动报错:

# nacos服务器grpc相关地址和端口,需要nginx已经有stream模块
stream {
    upstream nacos-server-grpc {
        server 192.168.131.101:9848;
	  server 192.168.131.102:9848;
	  server 192.168.131.103:9848;
    }
    server {
        listen 2111;
        proxy_pass nacos-server-grpc;
    }
} 

按照指定启动

image-20230327212843975

5.3.10、截止到此处,1个Nginx+3个nacos注册中心+1个mysql

测试通过nginx访问nacos

http://192.168.131.101:1111/nacos/#/login

新建一个配置测试:

image-20230327212938342

linux服务器的mysql插入一条记录

image-20230327212950822

如果想要三台机器的数据统一,则必须配置同一个数据库

5.3.11、微服务cloudalibaba-provider-payment9002启动注册进nacos集群

image-20230327213125202

image-20230327213149623

5.3.12、高可用小总结

image-20230327213211137

三、SpringCloud Alibab Sentinel实现熔断与限流

1、Sentinel

1.1、官网

github.com/alibaba/Sen…

中文:introduction | Sentinel (sentinelguard.io)

1.2、是什么

image-20230328004848184

一句话解释,之前我们学过的Hystrix

1.3、去哪下

github.com/alibaba/Sen…

下载jar包

1.4、能干嘛

image-20230328005645003

1.5、怎么玩

spring-cloud-alibaba-group.github.io/github-page…

服务使用中的各种问题:

  1. 服务雪崩
  2. 服务降级
  3. 服务熔断
  4. 服务限流

2、安装Sentinel控制台

2.1、sentinel组件由2部分构成

image-20230328005823852

  1. 后台
  2. 前台8080

2.2、安装步骤

2.2.1、下载

github.com/alibaba/Sen…

2.2.2、运行命令

前提:

  1. java8环境ok
  2. 8080端口不能被占用

命令:java -jar sentinel-dashboard-1.8.6.jar

2.2.3、访问sentinel管理界面

http://localhost:8080

image-20230328010112730

登录账号密码均为sentinel

3、初始化演示工程

3.1、启动Nacos8848成功

http://localhost:8848/nacos/#/login

3.2、Module

3.2.1、新建cloudalibaba-sentinel-service8401
3.2.2、改pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.lxg.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-sentinel-service8401</artifactId>

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件+actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>
</project> 
3.2.3、改yml文件
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*' 
3.2.4、主启动类
package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401
{
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }
} 
3.2.5、controller
package com.lxg.springcloud.controller;

import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @auther zzyy
 * @create 2020-01-09 16:34
 */
@RestController
public class FlowLimitController
{

    @GetMapping("/testA")
    public String testA()
    {
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB()
    {
        return "------testB";
    }
} 
3.2.6、启动Sentinel8080

java -jar sentinel-dashboard-1.8.6.jar

3.2.7、启动微服务8401
3.2.8、查看sentinel控制台

发现现在空空如也

Sentinel采用的懒加载说明:需要执行访问

执行一次访问即可:

  1. http://localhost:8401/testA
  2. http://localhost:8401/testB

效果:

image-20230328093829300

结论:

sentinel8080正在监控微服务8401

4、流控规则

4.1、基础介绍

image-20230328095517358

进一步解释说明:

image-20230328095617597

4.2、流控模式

4.2.1、直接(默认)

直接->快速失败:系统默认

配置及说明:

表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误

image-20230328100144163

测试:

快速点击访问http://localhost:8401/testA

结果:Blocked by Sentinel (flow limiting)

思考:

直接调用默认报错信息,技术方面OK but,是否应该有我们自己的后续处理?

类似有个fallback的兜底方法?

转到其他页面?

4.2.2、关联模式

是什么:

  1. 当关联的资源达到阈值时,就限流自己
  2. 当与A关联的资源B达到阀值后,就限流A自己
  3. B惹事,A挂了

配置A:

设置效果 当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名

image-20230328102841740

postman模拟并发密集访问testB

image-20230328102905014

访问testB成功

image-20230328102926061

postman里新建多线程集合组

image-20230328102940137

将访问地址添加进新新线程组

image-20230328103005354

Run

image-20230328103025903

大批量线程高并发访问B,导致A失效了

运行后发现testA挂了

点击访问http://localhost:8401/testA

Blocked by Sentinel (flow limiting)

4.2.3、链路模式

多个请求调用了同一个微服务

针对上一级接口进行限流

可以对test1或test2进行限流

[Sentinel-限流规则(流控模式:直接、关联、链路)_sentinel 关联 链路_其然乐衣的博客-CSDN博客](blog.csdn.net/Ezerbel/art…)

image-20230328112219498

image-20230328112232577

image-20230328112250653

image-20230328112313671

被调用的getorder服务达到阈值时,限制/order/test2,而/order/test1没事

5、流控效果

5.1、直接->快速失败

直接失败,抛出异常:Blocked by Sentinel (flow limiting)

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

5.2、预热

5.2.1、说明:

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

5.2.2、官网:

github.com/alibaba/Sen…

image-20230328112626269

默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

限流 冷启动:限流 冷启动 · alibaba/Sentinel Wiki · GitHub

5.2.3、源码

com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

image-20230328120547659

5.2.4、warmup配置

默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。 案例,阀值为10+预热时长设置5秒。 系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10

image-20230328120621754

多次点击http://localhost:8401/testB

刚开始不行,后续慢慢OK

5.2.5、应用场景

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

5.3、排队等待

匀速排队,阈值必须设置为QPS

5.3.1、官网

image-20230328120744754

流量控制 匀速排队模式 · alibaba/Sentinel Wiki · GitHub

5.3.2、源码

com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

5.3.3、测试

image-20230328121947437

6、降级规则

6.1、官网、

熔断降级 · alibaba/Sentinel Wiki · GitHub

6.2、基本介绍

旧版:

image-20230328123705954

RT(平均响应时间,秒级) 平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级 窗口期过后关闭断路器 RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

异常比列(秒级) QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

异常数(分钟级) 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

新版

image-20230328123118224

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
6.2.1、进一步说明

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制, 让请求快速失败,避免影响到其它的资源而导致级联错误。

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

6.2.2、旧版Sentinel的断路器是没有半开状态的:

半开的状态系统自动去检测是否请求有异常, 没有异常就关闭断路器恢复使用, 有异常则继续打开断路器不可用。(这样再过窗口期设置时间过才能转到半开状态)具体可以参考Hystrix

复习Hystrix:

image-20230328124027931

6.3、降级策略实战

6.3.1、RT(老版)

是什么:

image-20230328131824742

测试:

代码:

 @GetMapping("/testD")
public String testD()
{
    //暂停几秒钟线程
    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
    log.info("testD 测试RT");
    return "------testD";
} 

配置:

image-20230328131906861

jmeter压测:

image-20230328132007515

结论:

按照上述配置,

永远一秒钟打进来10个请求(大于5个了)调用testD,我们希望200毫秒处理完本次任务, 如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了

后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK

6.3.2、慢调用比例(新版)

1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。 2.RT:响应时间,指系统对请求作出响应的时间。 3.慢调用:当调用的时间(响应的实际时间)>设置的RT的时,这个调用叫做慢调用。 4.慢调用比例:在所以调用中,慢调用占有实际的比例,= 慢调用次数 / 调用次数 5.比例阈值:自己设定的 , 慢调用次数 / 调用次数=比例阈值

统计时长:时间的判断依据 最小请求数:设置的调用最小请求数

进入熔断状态判断依据:当统计时常内,实际请求数目大于最小请求数目,慢调用比例> 比例阈值 ,进入熔断状态

熔断状态:在接下来的熔断时长内请求会自动被熔断

探测恢复状态:熔断时长结束后进入探测恢复状态

结束熔断:在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断 否则继续熔断。

image-20230328130349999

sentinel降级策略:慢调用比例_海滩超人的博客-CSDN博客

Sentinel熔断策略-慢调用比例_紫荆之后-的博客-CSDN博客

6.3.3、异常比例

是什么:

image-20230328133245986

异常数是按照分钟统计的

测试:

代码:

@GetMapping("/testException")
    public String testException()
    {
        log.info("testD 测试RT");
        int age = 10/0;
        return "------testException";
    } 

image-20230328133404437

浏览器狂发请求,发现会进入熔断状态

由于每一次都是异常,半开尝试后仍然是异常,就继续熔断

6.3.3、异常数

是什么:

异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

测试:

代码:

 @GetMapping("/testExceptionCount")
    public String testExceptionCount()
    {
        log.info("testD 测试异常数");
        int age = 10/0;
        return "------testExceptionCount";
    } 

配置:

image-20230328193528494

10秒内请求数大于5,且这些请求中有5个是异常即会进入熔断状态,等3秒后进入半开状态,如果下一个请求还异常就继续熔断3秒,如果正常就取消熔断,重新判断

7、热点key限流

7.1、基本介绍

是什么:

何为热点 热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

image-20230328201612065

7.2、官网:

热点参数限流 · alibaba/Sentinel Wiki (github.com)

7.3、承上启下复习start

兜底方法 分为系统默认和客户自定义,两种

之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论 从HystrixCommand 到@SentinelResource

7.4、代码

 @GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1, 
                         @RequestParam(value = "p2",required = false) String p2){
    return "------testHotKey";
}
public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
{
    return "-----dealHandler_testHotKey";
}
 
 
 //sentinel系统默认的提示:Blocked by Sentinel (flow limiting) 

com.alibaba.csp.sentinel.slots.block.BlockException

7.5、配置

image-20230328201814803

限流模式只支持QPS模式,固定写死了。(这才叫热点) @SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推 单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。 上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。

image-20230328201831613

  1. @SentinelResource(value = "testHotKey")

    异常打到了前台用户界面看到,不友好

  2. @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")

    方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理

    用了我们自己定义的

7.6、测试

error:http://localhost:8401/testHotKey?p1=abc

error:http://localhost:8401/testHotKey?p1=abc&p2=33

right:http://localhost:8401/testHotKey?p2=abc

7.7、参数例外项

7.7.1、配置

image-20230328202045663

添加按钮不能忘

7.7.2、测试

right:http://localhost:8401/testHotKey?p1=5

error:http://localhost:8401/testHotKey?p1=3

当p1等于5的时候,阈值变为200

当p1不等于5的时候,阈值就是平常的1

前提条件:热点参数的注意点,参数必须是基本类型或者String

7.8、其他

手贱添加异常看看....../(ㄒoㄒ)/~~

@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
 
 
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
 
 
总结
 @SentinelResource主管配置出错,运行出错该走异常走异常 

8、系统规则

8.1、是什么

系统自适应限流 · alibaba/Sentinel Wiki (github.com)

8.2、各项配置参数说明

image-20230328213731683

配置全局QPS

9、@SentinelResource

9.1、按资源名称限流+后续处理

启动Nacos成功:http://localhost:8848/nacos/#/login

启动Sentinel成功:java -jar sentinel-dashboard-1.8.6.jar

9.1.1、修改cloudalibaba-sentinel-service8401

pom:(新增以下内容)

<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.lxg.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency> 

yml不变

controller:

package com.lxg.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RateLimitController {
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource() {
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, "serial001"));
    }

    public CommonResult handleException(BlockException exception) {
        return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
    }

    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl() {
        return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
    }
} 
9.1.2、配置规则

image-20230328215253066

图形配置和代码关系

image-20230328215316820

表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流

9.1.3、测试

1秒钟点击1下,OK

超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生

image-20230328215438328

9.1.4、额外问题

此时关闭问服务8401看看

Sentinel控制台,流控规则消失了?????

临时/持久?

9.2、按照Url地址限流+后续处理

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

访问一次http://localhost:8401/rateLimit/byUrl

sentinel配置:

image-20230328215602823

测试:

疯狂点击http://localhost:8401/rateLimit/byUrl

image-20230328215636937

9.3、上面兜底方案面临的问题

  1. 系统默认的,没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 每个业务方法都添加一个兜底的,那代码膨胀加剧
  4. 全局统一的处理方法没有体现。

9.4、客户自定义限流处理逻辑

9.4.1、创建CustomerBlockHandler类用于自定义限流处理逻辑
package com.lxg.springcloud.handler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;

/**
 * @auther xiaolin
 * @creatr 2023/3/28 22:10
 */
public class CustomerBlockHandler {


    public static CommonResult handleException(BlockException exception) {
        return new CommonResult(4444, "按客户自定义,global handlerException----1");
    }

    public static CommonResult handleException2(BlockException exception) {
        return new CommonResult(4444, "按客户自定义,global handlerException----2");
    }
} 
9.4.2、RateLimitController
 /**
     * 自定义通用的限流处理逻辑,
     blockHandlerClass = CustomerBlockHandler.class
     blockHandler = handleException2
     上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
     */
    /**
     * 自定义通用的限流处理逻辑
     */
    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
            blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
    public CommonResult customerBlockHandler()
    {
        return new CommonResult(200,"按客户自定义限流处理逻辑",new Payment(2020L,"serial003"));
    } 
9.4.3、启动微服务后先调用一次
9.4.4、配置

image-20230328221819765

9.4.5、狂刷

image-20230328221844059

9.4.6、进一步说明

image-20230328221904285

9.5、更多注解属性说明

image-20230328222645377

image-20230328222735662

所有的代码都要用try-catch-finally方式进行处理,o(╥﹏╥)o

Sentinel主要有三个核心Api:

  1. SphU定义资源
  2. Tracer定义统计
  3. ContextUtil定义了上下文

10、服务熔断功能

10.1、sentinel整合ribbon+openFeign+fallback

10.2、Ribbon系列

10.2.1、启动nacos和sentinel
10.2.2、新建9003/9004模块

新建cloudalibaba-provider-payment9003/9004两个一样的做法

1、pom文件

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

    <artifactId>cloudalibaba-provider-payment9003</artifactId>

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.lxg.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project> 

2、application.yml

 server:
  port: 9003/9004

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*' 

3、主启动类

package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9004/9003
{
    public static void main(String[] args) {
            SpringApplication.run(PaymentMain9004/9003.class, args);
    }
} 

4、业务类

package com.lxg.springcloud.controller;

import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();

    static {
        hashMap.put(1L, new Payment(1L, "28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L, new Payment(2L, "bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L, new Payment(3L, "6ua8c1e3bc2742d8848569891xt92183"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult(200, "from mysql,serverPort:  " + serverPort, payment);
        return result;
    }
} 

5、测试地址

http://localhost:9003/paymentSQL/1

image-20230404111425185

10.2.3、新建消费者84模块

新建cloudalibaba-consumer-nacos-order84

1、pom文件

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

    <artifactId>cloudalibaba-consumer-nacos-order84</artifactId>

    <dependencies>

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

        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.lxg.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project> 

2、application.yml

server:
  port: 84

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口

      filter:
        enabled: false
#      web-context-unify: false #关闭将Controller方法做context整合

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider 

3、主启动类

package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

4、业务类

package com.lxg.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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 javax.annotation.Resource;
import java.sql.SQLException;
import java.util.concurrent.Executors;

/**
 * @auther zzyy
 * @create 2020-02-13 20:24
 */
@RestController
@Slf4j
public class CircleBreakerController
{
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;
    @RequestMapping("/consumer/fallback/{id}")
    //    @SentinelResource(value = "fallback")
//    @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
//    @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler"
            ,exceptionsToIgnore = {IllegalArgumentException.class})
    public CommonResult<Payment> fallback(@PathVariable Long id)
    {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
        if (id == 4) {
            throw new IllegalArgumentException ("非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录");
        }
        return result;
    }
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"fallback,无此流水,exception  "+e.getMessage(),payment);
    }
    public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }
} 

5、ApplicationContextConfig

package com.lxg.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
} 

6、修改后重启微服务

热部署对java代码级生效及时

对@SentinelResource注解内属性,有时效果不好

7、目的

fallback管运行异常

blockHandler管配置违规

8、测试地址

http://localhost:84/consumer/fallback/1

image-20230404111920502

9、无任何配置情况

给客户error页面,不友好

image-20230404112026973

10、只配置fallback

image-20230404112046963

本例sentinel无配置

image-20230404112109762

11、只配置blockHandler

image-20230404112139358

本例sentinel需配置:

image-20230404112200210

image-20230404112210091

12、fallback和blockHandler都配置

image-20230404112535125

本例sentinel需配置

image-20230404112550178

image-20230404112607909

13、忽略属性.......

image-20230404112628883

本例sentinel无配置

image-20230404112645632

程序异常打到前台了,对用户不友好

10.3、Feign系列

10.3.1、修改84模块

84消费者调用提供者9003

Feign组件一般是消费侧

1、pom文件新增

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

2、yml修改

server:
  port: 84

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8179
      filter:
        enabled: false
#      web-context-unify: false #关闭将Controller方法做context整合

management:
  endpoints:
    web:
      exposure:
        include: '*'

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
#service-url:
#  nacos-user-service: http://nacos-payment-provider 

激活Sentinel对Feign的支持

3、主启动添加注解

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

添加@EnableFeignClients启动Feign的功能

4、业务类

PaymentService

package com.lxg.springcloud.service;

import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 11:33
 */

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);

} 

PaymentFallbackService

package com.lxg.springcloud.service;

import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import org.springframework.stereotype.Component;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 12:25
 */

@Component
public class PaymentFallbackService implements PaymentService{
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
} 

Controller

package com.lxg.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lxg.springcloud.entities.CommonResult;
import com.lxg.springcloud.entities.Payment;
import com.lxg.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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 javax.annotation.Resource;
import java.sql.SQLException;
import java.util.concurrent.Executors;

/**
 * @auther zzyy
 * @create 2020-02-13 20:24
 */
@RestController
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")
//    @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
//    @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
//    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler"
//            ,exceptionsToIgnore = {IllegalArgumentException.class})
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数异常....");
        } else if (result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录");
        }
        return result;
    }

    public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444, "fallback,无此流水,exception  " + e.getMessage(), payment);
    }

    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(), payment);
    }


    //==================OpenFeign
    @Resource
    private PaymentService paymentService;

    @GetMapping(value = "/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        return paymentService.paymentSQL(id);
    }


} 

5、访问

http://localhost:84/consumer/paymentSQL/1

测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死

image-20230404123702858

10.3.2、熔断框架比较

image-20230404123750978

11、规则持久化

11.1、是什么

一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化

11.2、怎么玩

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台 的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效

11.3、步骤

11.3.1、修改cloudalibaba-sentinel-service8401

1、pom文件

<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency> 

2、yml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
#      web-context-unify: false #关闭将Controller方法做context整合
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*' 
11.3.2、添加Nacos业务规则配置

image-20230404130400992

 [
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

 
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。 
11.3.3、启动8401后刷新sentinel发现业务规则有了

image-20230404130443147

11.3.4、快速访问测试接口

http://localhost:8401/rateLimit/byUrl

image-20230404130512784

11.3.5、停止8401再看sentinel

image-20230404130532222

11.3.6、重新启动8401再看sentinel

乍一看还是没有,稍等一会儿

多次调用:http://localhost:8401/rateLimit/byUrl

配置重新出现了,持久化验证通过

四、SpringCloud Alibaba

1、分布式事务问题\

1.1、分布式前

单机单库没这个问题

从1:1 -> 1:N -> N:N

1.2、分布式之后

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源, 业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

image-20230404181422234

一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

2、Seata简介

2.1、是什么

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

官网地址:seata.io/zh-cn/

2.2、能干嘛

一个典型的分布式事务过程:

2.2.1、分布式事务处理过程的一ID+三组件模型:
  1. Transaction ID XID:全局唯一的事务ID
  2. 3组件概念:
    • Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
    • Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
    • Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
2.2.2、处理过程
1、TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
2、XID 在微服务调用链路的上下文中传播;
3、RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
4、TM 向 TC 发起针对 XID 的全局提交或回滚决议;
5、TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。 

image-20230404182020903

image-20230404182051023

2.3、去哪下

发布说明: github.com/seata/seata…

2.4、怎么玩

本地@Transactional

全局@GlobalTransactional

SEATA 的分布式交易解决方案:

image-20230404182210223

3、Seata-Server安装

3.1、官网地址

seata.io/zh-cn/

3.2、下载版本

github.com/seata/seata…

下载的是seata-server-1.6.0.zip

3.3、seata-server-1.6.0.zip解压到指定目录并修改conf目录下的application.yml配置文件

先备份application.yml

主要修改:事务日志存储模式为db+数据库连接信息

application.yml:

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${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: file
  registry:
    # support: nacos, eureka, redis, zk, consul, etcd3, sofa
    #type: file
    type: nacos
    preferred-networks: 30.240.*
    nacos:
      #application: seata-server
      server-addr: 127.0.0.1:8848
      #group: SEATA_GROUP
      namespace:
      cluster: default
      #username:
      #password:
      #context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
  store:
    # support: file 、 db 、 redis
    #mode: file
    mode: db
    #session:
     # mode: file
    #lock:
     # mode: file
    file:
      dir: sessionStore
      max-branch-session-size: 16384
      max-global-session-size: 512
      file-write-buffer-cache-size: 16384
      session-reload-read-size: 100
      flush-disk-mode: async
    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: root
      password: 123456
      min-conn: 5
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 100
      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,/**/*.ico,/console-fe/public/**,/api/v1/auth/login 

3.4、mysql5.7数据库新建库seata

3.5、在seata库里建表

建表sql在/seata/script/server/db/mysql.sql

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0); 

3.6、启动nacos端口号8848

http://localhost:8848/nacos/

3.7、启动seata-server

双击即可

Seata

3.8、以上配置可参考

Seata部署指南

4、订单/库存/账户业务数据库准备

以下演示都需要先启动Nacos后启动Seata,保证两个都OK

Seata没启动报错:no available server to connect

4.1、分布式事务业务说明

4.1.1、业务说明

这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存, 再通过远程调用账户服务来扣减用户账户里面的余额, 最后在订单服务中修改订单状态为已完成。

该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

下订单--->扣库存--->减账户(余额)

4.2、创建业务数据库

seata_order:存储订单的数据库;

seata_storage:存储库存的数据库;

seata_account:存储账户信息的数据库。

建库SQL:

 CREATE DATABASE seata_order;
 
CREATE DATABASE seata_storage;
 
CREATE DATABASE seata_account; 

4.3、按照上述3库分别建对应业务表

seata_order库下建t_order表:

 CREATE TABLE t_order (
  `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
  `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
  `count` INT(11) DEFAULT NULL COMMENT '数量',
  `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
  `status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' 
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
 
SELECT * FROM t_order; 

seata_storage库下建t_storage 表:

 CREATE TABLE t_storage (
 `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
 `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
 `total` INT(11) DEFAULT NULL COMMENT '总库存',
 `used` INT(11) DEFAULT NULL COMMENT '已用库存',
 `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 
 
INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '100', '0', '100');
 
SELECT * FROM t_storage; 

seata_account库下建t_account 表:

 CREATE TABLE t_account (
  `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
  `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
  `total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
  `used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
  `residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
 
INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)  VALUES ('1', '1', '1000', '0', '1000');
 
SELECT * FROM t_account; 

4.4、按照上述3库分别建对应的回滚日志表

订单-库存-账户3个库下都需要建各自的回滚日志表

建表sql:seata/script/client/at/db at master · seata/seata · GitHub

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table'; 

4.5、最终效果

image-20230404211610531

5、订单/库存/账户业务微服务准备

5.1、业务需求

下订单->减库存->扣余额->改(订单)状态

5.2、新建订单seata-order-service2001

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

    <artifactId>seata-order-service2001</artifactId>

    <dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <!-- 注意一定要引入对版本,要引入spring-cloud版本seata,而不是springboot版本的seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <!-- 排除掉springcloud默认的seata版本,以免版本不一致出现问题-->
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 上面排除掉了springcloud默认色seata版本,此处引入和seata-server版本对应的seata包-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.6.0</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--web-actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--mysql-druid-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.13</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--loadbalancer-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
    </dependencies>
</project> 
5.2.2、yml
server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    alibaba:
      seata:
        tx-service-group: default_tx_group
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false
  client:
    config:
      default:
        # 连接超时时间,默认10s,设置单位为毫秒
        connectTimeout: 2000
        # 请求处理超时时间,默认60s,设置单位为毫秒
        readTimeout: 5000
        loggerLevel: FULL
logging:
  level:
    io:
      seata: info

mybatis:
  mapper-locations: classpath:mapper/*.xml

# seata配置
seata:
  enabled: true
  # Seata 应用编号,默认为 ${spring.application.name}
  #application-id: ${spring.application.name}
  # Seata 事务组编号,用于 TC 集群名
  tx-service-group: default_tx_group
  # 开启自动代理
  enable-auto-data-source-proxy: true
  # 服务配置项
  #service:
    # 虚拟组和分组的映射
   # vgroup-mapping:
    #  gulimall-order-group: default
  #config:
   # type: nacos
    #nacos:
#      serverAddr: 127.0.0.1:8848
#      group: SEATA_GROUP
#      namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
#      namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b 
5.2.3、file.conf(可能是没有配置nacos配置中心)
transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  #thread factory for netty
  thread-factory {
    boss-thread-prefix = "NettyBoss"
    worker-thread-prefix = "NettyServerNIOWorker"
    server-executor-thread-prefix = "NettyServerBizHandler"
    share-boss-worker = false
    client-selector-thread-prefix = "NettyClientSelector"
    client-selector-thread-size = 1
    client-worker-thread-prefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    boss-thread-size = 1
    #auto default pin or 8
    worker-thread-size = 8
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}

service {

  vgroup_mapping.default_tx_group = "default" #修改自定义事务组名称

  default.grouplist = "127.0.0.1:8091"
  enableDegrade = false
  disable = false
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
  disableGlobalTransaction = false
}


client {
  async.commit.buffer.limit = 10000
  lock {
    retry.internal = 10
    retry.times = 30
  }
  report.retry.count = 5
  tm.commit.retry.count = 1
  tm.rollback.retry.count = 1
}

## transaction log store
store {
  ## store mode: file、db
  mode = "db"

  ## file store
  file {
    dir = "sessionStore"

    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }

  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "123456"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}
lock {
  ## the lock store mode: local、remote
  mode = "remote"

  local {
    ## store locks in user's database
  }

  remote {
    ## store locks in the seata's server
  }
}
recovery {
  #schedule committing retry period in milliseconds
  committing-retry-period = 1000
  #schedule asyn committing retry period in milliseconds
  asyn-committing-retry-period = 1000
  #schedule rollbacking retry period in milliseconds
  rollbacking-retry-period = 1000
  #schedule timeout retry period in milliseconds
  timeout-retry-period = 1000
}

transaction {
  undo.data.validation = true
  undo.log.serialization = "jackson"
  undo.log.save.days = 7
  #schedule delete expired undo_log in milliseconds
  undo.log.delete.period = 86400000
  undo.log.table = "undo_log"
}

## metrics settings
metrics {
  enabled = false
  registry-type = "compact"
  # multi exporters use comma divided
  exporter-list = "prometheus"
  exporter-prometheus-port = 9898
}

support {
  ## spring
  spring {
    # auto proxy the DataSource bean
    datasource.autoproxy = false
  }
} 
5.2.4、registry.conf(可在yml直接配置)
# registry {
#   # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
#   type = "nacos"
#
#   nacos {
#     serverAddr = "localhost:8848"
#     namespace = ""
#     cluster = "default"
#   }
#   eureka {
#     serviceUrl = "http://localhost:8761/eureka"
#     application = "default"
#     weight = "1"
#   }
#   redis {
#     serverAddr = "localhost:6379"
#     db = "0"
#   }
#   zk {
#     cluster = "default"
#     serverAddr = "127.0.0.1:2181"
#     session.timeout = 6000
#     connect.timeout = 2000
#   }
#   consul {
#     cluster = "default"
#     serverAddr = "127.0.0.1:8500"
#   }
#   etcd3 {
#     cluster = "default"
#     serverAddr = "http://localhost:2379"
#   }
#   sofa {
#     serverAddr = "127.0.0.1:9603"
#     application = "default"
#     region = "DEFAULT_ZONE"
#     datacenter = "DefaultDataCenter"
#     cluster = "default"
#     group = "SEATA_GROUP"
#     addressWaitTime = "3000"
#   }
#   file {
#     name = "file.conf"
#   }
# }
#
# config {
#   # file、nacos 、apollo、zk、consul、etcd3
#   type = "file"
#
#   nacos {
#     serverAddr = "localhost"
#     namespace = ""
#   }
#   consul {
#     serverAddr = "127.0.0.1:8500"
#   }
#   apollo {
#     app.id = "seata-server"
#     apollo.meta = "http://192.168.1.204:8801"
#   }
#   zk {
#     serverAddr = "127.0.0.1:2181"
#     session.timeout = 6000
#     connect.timeout = 2000
#   }
#   etcd3 {
#     serverAddr = "http://localhost:2379"
#   }
#   file {
#     name = "file.conf"
#   }
# }
# 

yml配置

 registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
#      namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b 
5.2.5、domain

1、CommonResult

package com.lxg.springcloud.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

    public CommonResult(Integer code, String message)
    {
        this(code,message,null);
    }
} 

2、Order

package com.lxg.springcloud.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order
{
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    /**
     * 订单状态:0:创建中;1:已完结
     */
    private Integer status;
} 
5.2.6、Dao接口
package com.lxg.springcloud.dao;

import com.lxg.springcloud.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 22:00
 */

@Mapper
public interface OrderDao {

    //1、新建订单
    void create(Order order);

    //2、修改订单状态,从0改为1
    void update(@Param("userId") Long userId,@Param("status") Integer status);
} 

OrderMapper:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lxg.springcloud.dao.OrderDao">
    
    <resultMap id="BaseResultMap" type="com.lxg.springcloud.domain.Order">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="count" property="count" jdbcType="INTEGER"/>
        <result column="money" property="money" jdbcType="DECIMAL"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
    </resultMap>
    
    
    <insert id="create">
        insert into t_order (id,user_id,product_id,count,money,status)
        values (null,#{userId},#{productId},#{count},#{money},0);
    </insert>

    <update id="update">
        update t_order set status = 1
        where user_id = #{userId} and status = #{status};
    </update>
</mapper> 
5.2.7、Service

AccountService接口:

package com.lxg.springcloud.service;

import com.lxg.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 22:14
 */

@FeignClient(value = "seata-account-service")
public interface AccountService {

    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);

} 

OrderService:

package com.lxg.springcloud.service;

import com.lxg.springcloud.domain.Order;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 22:14
 */

public interface OrderService {

    void create(Order order);

} 

StorageService:

package com.lxg.springcloud.service;

import com.lxg.springcloud.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 22:14
 */

@FeignClient(value = "seata-storage-service")
public interface StorageService {

    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count);

} 

OrderServiceImpl:

package com.lxg.springcloud.service.impl;

import com.lxg.springcloud.dao.OrderDao;
import com.lxg.springcloud.domain.Order;
import com.lxg.springcloud.service.AccountService;
import com.lxg.springcloud.service.OrderService;
import com.lxg.springcloud.service.StorageService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 22:14
 */
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDao orderDao;

    @Resource
    private StorageService storageService;

    @Resource
    private AccountService accountService;


    /**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     * 简单说:
     * 下订单->减库存->减余额->改状态
     */
    @Override
    @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
    public void create(Order order) {

        log.info("Seata全局事务id=================>{}", RootContext.getXID());

        log.info("--->开始新建订单");
        //1、新建订单
        orderDao.create(order);

        log.info("--->订单微服务开始调用库存,做扣减count");
        //2、扣减库存
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("--->订单微服务开始调用库存,做扣减end");

        //3、扣减账户
        log.info("--->订单微服务开始调用账号,做扣减money");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("--->订单微服务开始调用账号,做扣减end");

        //4、修改订单状态,从0->1,1代表已经完成
        log.info("--->修改订单状态开始");
        orderDao.update(order.getUserId(),0);
        log.info("--->修改订单状态结束");

        log.info("下订单结束了,O(∩_∩)O哈哈~");
    }
} 
5.2.8、controller
package com.lxg.springcloud.controller;

import com.lxg.springcloud.domain.CommonResult;
import com.lxg.springcloud.domain.Order;
import com.lxg.springcloud.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 22:30
 */

@RestController
public class OrderController {

    @Resource
    private OrderService orderService;


    @GetMapping("/order/create")
    public CommonResult create(Order order){
        orderService.create(order);
        return new CommonResult(200,"订单创建成功!");

    }

} 
5.2.9、config

MyBatisConfig

package com.lxg.springcloud.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan({"com.lxg.springcloud.dao"})
public class MyBatisConfig {
} 

DataSourceProxyConfig:新版配置就启动报错

package com.lxg.springcloud.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;

//@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }




    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

} 
5.2.10、主启动类
package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 22:35
 */
@EnableFeignClients
@EnableDiscoveryClient
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication
public class SeataOrderMainApp2001 {
    public static void main(String[] args) {
        SpringApplication.run(SeataOrderMainApp2001.class,args);
    }
} 

如果数据源配置有效就要@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

5.3、seata-order-service2001

5.3.1、pom文件

与以上模块一致

5.3.2、yml文件
server:
  port: 2002

spring:
  application:
    name: seata-storage-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    alibaba:
      seata:
        tx-service-group: default_tx_group
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_storage
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapper-locations: classpath:mapper/*.xml

# seata配置
seata:
  enabled: true
  # Seata 应用编号,默认为 ${spring.application.name}
  #application-id: ${spring.application.name}
  # Seata 事务组编号,用于 TC 集群名
  tx-service-group: default_tx_group
    # 开启自动代理
  enable-auto-data-source-proxy: true
    # 服务配置项
    #service:
    # 虚拟组和分组的映射
    # vgroup-mapping:
    #  gulimall-order-group: default
    #config:
    # type: nacos
  #nacos:
  #      serverAddr: 127.0.0.1:8848
  #      group: SEATA_GROUP
  #      namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      cluster: "default"
#      namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b 
5.3.3、domain
package com.lxg.springcloud.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

    public CommonResult(Integer code, String message)
    {
        this(code,message,null);
    }
} 
package com.lxg.springcloud.domain;

import lombok.Data;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 23:54
 */
@Data
public class Storage {

    private Long id;

    /**
     * 产品id
     */
    private Long productId;

    /**
     * 总库存
     */
    private Integer total;

    /**
     * 已用库存
     */
    private Integer used;

    /**
     * 剩余库存
     */
    private Integer residue;
} 
5.3.4、dao
package com.lxg.springcloud.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @auther xiaolin
 * @creatr 2023/4/4 23:55
 */

@Mapper
public interface StorageDao {
    /**
     * 扣减库存
     */
    void decrease(@Param("productId") Long productId, @Param("count") Integer count);
} 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lxg.springcloud.dao.StorageDao">

    <resultMap id="BaseResultMap" type="com.lxg.springcloud.domain.Storage">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="total" property="total" jdbcType="INTEGER"/>
        <result column="used" property="used" jdbcType="INTEGER"/>
        <result column="residue" property="residue" jdbcType="INTEGER"/>
    </resultMap>

    <update id="decrease">
        UPDATE t_storage
        SET used    = used + #{count},
            residue = residue - #{count}
        WHERE product_id = #{productId}
    </update>

</mapper> 
5.3.5、service
package com.lxg.springcloud.service;

public interface StorageService {
    /**
     * 扣减库存
     */
    void decrease(Long productId, Integer count);
} 
package com.lxg.springcloud.service.Impl;

import com.lxg.springcloud.dao.StorageDao;
import com.lxg.springcloud.service.StorageService;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
    private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);

    @Resource
    private StorageDao storageDao;

    /**
     * 扣减库存
     */
    @Override
    public void decrease(Long productId, Integer count) {
        log.info("Seata全局事务id=================>{}",RootContext.getXID());

        LOGGER.info("------->storage-service中扣减库存开始");
        storageDao.decrease(productId,count);
        LOGGER.info("------->storage-service中扣减库存结束");
    }
} 
5.3.6、controller
package com.lxg.springcloud.controller;

import com.lxg.springcloud.domain.CommonResult;
import com.lxg.springcloud.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StorageController {

    @Autowired
    private StorageService storageService;

    /**
     * 扣减库存
     */
    @RequestMapping("/storage/decrease")
    public CommonResult decrease(Long productId, Integer count) {
        storageService.decrease(productId, count);
        return new CommonResult(200,"扣减库存成功!");
    }
} 
5.3.7、file.conf

与上模块一致

5.3.8、registry.conf

与上模块一致

5.3.9、config

与上模块一致

5.3.10、主启动类
package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SeataStorageServiceApplication2002 {

    public static void main(String[] args) {
        SpringApplication.run(SeataStorageServiceApplication2002.class, args);
    }

} 

5.4、seata-account-service2003

5.4.1、pom

与以上一致

5.4.2、yml文件
server:
  port: 2003

spring:
  application:
    name: seata-account-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    alibaba:
      seata:
        tx-service-group: default_tx_group
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_account
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapper-locations: classpath:mapper/*.xml

# seata配置
seata:
  enabled: true
  # Seata 应用编号,默认为 ${spring.application.name}
  #application-id: ${spring.application.name}
  # Seata 事务组编号,用于 TC 集群名
  tx-service-group: default_tx_group
  # 开启自动代理
  enable-auto-data-source-proxy: true
  # 服务配置项
  #service:
    # 虚拟组和分组的映射
   # vgroup-mapping:
    #  gulimall-order-group: default
  #config:
   # type: nacos
    #nacos:
#      serverAddr: 127.0.0.1:8848
#      group: SEATA_GROUP
#      namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
#      namespace: 86ab3ca5-81db-4efb-ae22-7b4a2980a96b 
5.4.3、domain
package com.lxg.springcloud.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Long id;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 总额度
     */
    private BigDecimal total;

    /**
     * 已用额度
     */
    private BigDecimal used;

    /**
     * 剩余额度
     */
    private BigDecimal residue;
} 
package com.lxg.springcloud.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

    public CommonResult(Integer code, String message)
    {
        this(code,message,null);
    }
} 
5.4.4、file.conf

与以上一致

5.4.5、registry.conf

与以上一致

5.4.6、dao
package com.lxg.springcloud.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.math.BigDecimal;

@Mapper
public interface AccountDao {

    /**
     * 扣减账户余额
     */
    void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
} 
5.4.7、service
package com.lxg.springcloud.service;

import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

public interface AccountService {

    /**
     * 扣减账户余额
     * @param userId 用户id
     * @param money 金额
     */
    void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
} 
package com.lxg.springcloud.service.impl;

import com.lxg.springcloud.dao.AccountDao;
import com.lxg.springcloud.service.AccountService;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AccountServiceImpl implements AccountService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);


    @Resource
    AccountDao accountDao;

    /**
     * 扣减账户余额
     */
    @Override
    public void decrease(Long userId, BigDecimal money) {
        log.info("Seata全局事务id=================>{}", RootContext.getXID());

        LOGGER.info("------->account-service中扣减账户余额开始");
        //模拟超时异常,全局事务回滚
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service中扣减账户余额结束");
    }
} 
5.4.8、controller
package com.lxg.springcloud.controller;

import com.lxg.springcloud.domain.CommonResult;
import com.lxg.springcloud.service.AccountService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;

@RestController
public class AccountController {

    @Resource
    AccountService accountService;

    /**
     * 扣减账户余额
     */
    @RequestMapping("/account/decrease")
    public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
        accountService.decrease(userId,money);
        return new CommonResult(200,"扣减账户余额成功!");
    }
} 
5.4.9、config

与以上一致

5.4.10、主启动类
package com.lxg.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class SeataAccountMainApp2003
{
    public static void main(String[] args)
    {
        SpringApplication.run(SeataAccountMainApp2003.class, args);
    }
} 

6、Test

下订单->减库存->扣余额->改(订单)状态

数据库初始情况:

image-20230405020958918

6.1、正常下单

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

数据库情况:

image-202304050210389396.2、超时异常,没加@GlobalTransactional

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

AccountServiceImpl添加超时

image-20230405021129209

故障情况:

当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1

而且由于feign的重试机制,账户余额还有可能被多次扣减

6.3、超时异常,添加@GlobalTransactional

AccountServiceImpl添加超时

OrderServiceImpl@GlobalTransactional

@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order)
{
。。。。。。
} 

下单后数据库数据并没有任何改变

记录都添加不进来

7、一部分补充

7.1、Seata

2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案

Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架

2020起始,参加工作后用1.0以后的版本

7.2、再看TC/TM/RM三大组件

image-20230405110527336

分布式事务的执行流程:

  1. TM 开启分布式事务(TM 向 TC 注册全局事务记录);
  2. 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
  3. TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
  4. TC 汇总事务信息,决定分布式事务是提交还是回滚;
  5. TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。

7.3、AT模式如何做到对业务的无侵入

7.3.1、是什么

image-20230405110716822

7.3.2、一阶段加载

在一阶段,Seata 会拦截“业务 SQL”, 1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”, 2 执行“业务 SQL”更新业务数据,在业务数据更新之后, 3 其保存成“after image”,最后生成行锁。 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

image-20230405110732213

7.3.3、二阶段提交

二阶段如是顺利提交的话, 因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

image-20230405110746799

7.3.4、二阶段回滚

二阶段回滚: 二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。 回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”, 如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

image-20230405110800333

7.4、自己debug

会发现seata数据库的表数据和undo.log表的数据有变化

7.5、补充

image-20230405110920874

具体其他内容可以阅读官网文档:

Seata 是什么

本文作者:_xiaolin

本文链接:https://www.cnblogs.com/SilverStar/p/17415639.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   _xiaolin  阅读(0)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起