SpringCloud—笔记(三)高级篇

SpringCloud Alibaba 入门简介

1、why会出现SpringCloud alibaba

Spring Cloud Netflix项目进入维护模式

进入维护模式意味着

Spring Cloud Netlix将不再开发新的组件

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

新组件功能将以其他替代平代替的方式实现
SpringCloud—笔记(三)高级篇_第1张图片

2、SpringCloud alibaba带来了什么

是什么:

官网:https://github.com/alibaba/spring-cloud- alibaba/blob/master/README zh.md

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

能干嘛:

  • 服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
  • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

去哪下:

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

怎么玩:

  • Sentinel:阿里巴巴开源产品,把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
  • Nacos:阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  • RocketMQ:Apache RocketMQM基于Java的高性能、高吞吐量的分布式消息和流计算平台。
  • Dubbo:Apache DubboM是-款高性能Java RPC框架。
  • Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
  • Alibaba Cloud OSS:阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务.
    您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • Alibaba Cloud SchedulerX:阿里中间件团队开发的一款分布式任务调度产品,支持周期性的任务与固定时间点触发任务。

3、SpringCloud alibaba学习资料获取

官网:https://spring.io/projects/spring-cloud-alibaba#overview

英文
https://github.com/alibaba/spring-cloud-alibaba

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

中文
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md


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

1、Nacos简介

是什么

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

Nacos: Dynamic Naming and Configuration Service

Nacos就是注册中心+配置中心的组合:等价于Nacos = Eureka+Config + Bus

去哪下

下载:https://github.com/alibaba/Nacos

学习手册:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

各种注册中心比较

SpringCloud—笔记(三)高级篇_第2张图片

2、安装并运行Nacos

本地Java8 + Maven环境已经OK

先从官网下载Nacos

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

如果启动报错:
修改:startup.cmd
修改27行的为单机模式

set MODE="cluster"			//集群模式
set MODE="standalone"		//单机模式

命令运行成功后直接访问:http://localhost:8848/nacos

http://localhost:8848/nacos
账号密码:nacos

结果页面

SpringCloud—笔记(三)高级篇_第3张图片

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

1、官网文档

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_service_registrationdiscovery_nacos_discovery

2、基于Nacos的服务提供者

1、新建Module:cloudalibaba-provider-payment9001

2、POM

父POM


      <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-alibaba-dependenciesartifactId>
        <version>2.1.0.RELEASEversion>
        <type>pomtype>
        <scope>importscope>
      dependency>

本模块POM

    <dependencies>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

3、YML

server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

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

4、主启动

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

5、业务类

@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;
    }
}

6、测试

  • http://localhost:9001/payment/nacos/1
  • nacos控制台
  • nacos服务注册中心+服务提供者9001都OK了

SpringCloud—笔记(三)高级篇_第4张图片

7、为了下一章节演示nacos的负载均衡,参照9001新建9002

  • cloudalibaba-provider-payment9002

3、基于Nacos的服务消费者

1、新建Module:cloudalibaba-consumer-nacos-order83

2、POM:为什么nacos支持负载均衡量
SpringCloud—笔记(三)高级篇_第5张图片

    <dependencies>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        
        <dependency>
            <groupId>com.wlq.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

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

4、主启动

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

5、业务类

@RestController
@Slf4j
public class OrderNacosController {

    @Autowired
    private RestTemplate restTemplate;

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

    @GetMapping(value = "/consumer/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Long id){
        return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
    }
}
@Configuration
public class ApplicationContextConfig {

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

6、测试

  • nacos控制台
    在这里插入图片描述

  • http://localhost:83/consumer/payment/nacos/1:83访问9001/9002,轮询负载OK

4、服务注册中心对比

  • Nacos全景图所示

  • Nacos和CAP
    SpringCloud—笔记(三)高级篇_第6张图片

  • 切换: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作为服务配置中心演示

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

cloudalibaba-config-nacos-client3377

POM

    <dependencies>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
        dependency>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

YML

bootstrap.yml

server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml


# ${prefix}-${spring.profiles.active}.${file-extension}   dataId 的完整格式
# nacos-config-client-dev.yml

application.yml

spring:
  profiles:
    active: dev

主启动

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

业务类

@RestController
@RefreshScope   //动态刷新
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

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

在Nacos中添加配置信息

Nacos中的匹配规则

  • 理论
    https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
    SpringCloud—笔记(三)高级篇_第7张图片

  • 实操:设置DataId

SpringCloud—笔记(三)高级篇_第8张图片

SpringCloud—笔记(三)高级篇_第9张图片
小总结:
SpringCloud—笔记(三)高级篇_第10张图片

测试

  • 启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的ymI配置文件
  • 运行cloud-config-nacos-client3377的主启动类
  • 调用接口查看配置信息
  • http://localhost:3377/config/info

自带动态刷新

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

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

问题 多环境多项目管理

问题1:
实际开发中,通常一个系统会准备

  • dev开发环境
  • test测试环境
  • prod生产环境。

如何保证指定环境启动时服务能正确读取到Nacos.上相应环境的配置文件呢?

问题2:

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

Nacos的图形化管理界面

  • 配置管理

  • 命名空间

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

  • 是什么:

类似Java里面的package名和类名
最外层的namespace是可以用于区分部署环境的,Group和DatalD逻辑上区分两个目标对象。
SpringCloud—笔记(三)高级篇_第11张图片

默认情况: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,就是微服务的实例。

Case :三种方案 加载配置

  • DatalD方案

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

2、默认空间+默认分组+新建dev和test两个DatalD

在这里插入图片描述

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

spring:
  profiles:
    active: test  # 表示测试环境
#    active: dev # 表示开发环境
# 配置什么就加载什么

4、测试

http://localhost:3377/config/info

  • Group方案

1、通过Group实现环境区分:新建Group

2、在nacos图形界面控制台.上面新建配置文件DatalD
在这里插入图片描述

3、bootstrap+ application

server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml
        group: TEST_GROUP
spring:
  profiles:
    active: info
  • Namespace方案:一句话:访问路径为:namespace-->Group-->DataId

1、新建dev/test的Namespace
SpringCloud—笔记(三)高级篇_第12张图片

2、回到服务管理-服务列表查看
SpringCloud—笔记(三)高级篇_第13张图片

3、按照域名配置填写
SpringCloud—笔记(三)高级篇_第14张图片

4、YML

server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml
        group: DEV_GROUP
        namespace: adb57273-43c6-4d46-9e7c-7adcfa6e0d34

一句话:访问路径为:namespace-->Group-->DataId


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

1、官网说明

https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

集群部署架构图

因此开源的时候推荐用户把所有服务列表放到一个vip下面,然后挂到一个域名下面

http://ip1:port/openAPI 直连ip模式,机器挂则需要修改ip才可以使用。

http://SLB:port/openAPI 挂载SLB模式(内网SLB,不可暴露到公网,以免带来安全风险),直连SLB即可,下面挂server真实ip,可读性不好。

http://nacos.com:port/openAPI 域名 + SLB模式(内网SLB,不可暴露到公网,以免带来安全风险),可读性好,而且换ip方便,推荐模式

SpringCloud—笔记(三)高级篇_第15张图片
官网翻译,真实情况:
SpringCloud—笔记(三)高级篇_第16张图片
默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。

Nacos支持三种部署模式

  • 单机模式-用于测试和单机试用。
  • 集群模式-用于生产环境,确保高可用。
  • 多集群模式-用于多数据中心场景。

Windows

cmd startup.cmd或者双击startup.cmd文件

单机模式支持mysql

在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力,具体的操作步骤:

  1. 安装数据库,版本要求:5.6.5+
  2. 初始化mysq数据库,数据库初始化文件: nacos-mysql.sql
  3. 修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
# 指定数据源为Mysql
spring.datasource.platform=mysql

# 数据库实例数量
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456

再以单机模式启动nacos,nacos所有写嵌入式数据库的数据都写到了mysql。

2、Nacos持久化配置解释

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

derby到mysq|切换配置步骤

nacos-server-1.1.4\nacos\conf目录下找到sq|脚本:nacos-mysql.sql
sql语句源文件

执行脚本

SpringCloud—笔记(三)高级篇_第17张图片

nacos-server-1.1.4\nacos\conf目录下找到application.properties源文件

spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
db.user=root
db.password=123456

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

SpringCloud—笔记(三)高级篇_第18张图片

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

预计需要,1个Nginx+ 3个nacos注册中心+ 1个mysql Nacos

下载Linux版 安装

下载安装:https://github.com/alibaba/nacos/releases/download/1.3.0/nacos-server-1.3.0.tar.gz

	# 解压
	[root@iz2zeje3d4ytszrtpext8fz opt]# tar -zxvf nacos-server-1.3.0.tar.gz
	[root@iz2zeje3d4ytszrtpext8fz opt]# cd nacos/bin
	# 以单机模式启动   -m单机模式
	[root@iz2zeje3d4ytszrtpext8fz bin]# sh startup.sh -m standalone

安全组:开放8848端口

访问:ip:8848/nacos/

集群配置步骤(重点)

官网:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

1、Linux服务器上mysq|数据库配置

用Navicat远程连接服务器
SpringCloud—笔记(三)高级篇_第19张图片
SpringCloud—笔记(三)高级篇_第20张图片

  • 跟上面一样执行一遍SQL脚本:源文件

2、application.properties配置

spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456

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

在nacos的解压目录nacos/的conf目录下,有配置文件cluster.conf,请每行配置成ip:port。(请配置3个或3个以上节点)

  • 3333
  • 4444
  • 5555

SpringCloud—笔记(三)高级篇_第21张图片

注意,这个IP不能写127.0.0.1,必须是Linux命令查看能够识别的ip

hostname -i

在这里插入图片描述
编写cluster.conf

# ip:port
192.168.111.144:3333
192.168.111.144:4444
192.168.111.144:5555

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

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

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

SpringCloud—笔记(三)高级篇_第22张图片

在这里插入图片描述

执行方式

startup.sh - p 端口号

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

修改nginx配置文件:
SpringCloud—笔记(三)高级篇_第23张图片
修改内容:
SpringCloud—笔记(三)高级篇_第24张图片
启动nginx
SpringCloud—笔记(三)高级篇_第25张图片

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

测试

  • 启动3个nacos注册中心

    • startup.sh - p 3333
    • startup.sh - p 4444
    • startup.sh - p 5555
    • 查看nacos进程启动数ps -ef | grep nacos | grep -v grep | wc -l
  • 启动nginx

    • ./nginx -c /usr/local/nginx/conf/nginx.conf
    • 查看nginx进程ps - ef| grep nginx
  • 测试通过nginx,访问nacos - http://192.168.111.144:1111/nacos/#/login

  • 新建一个配置测试
    SpringCloud—笔记(三)高级篇_第26张图片

  • 新建后,可在linux服务器的mysql新插入一条记录

    select * from config;
    SpringCloud—笔记(三)高级篇_第27张图片

  • 让微服务cloudalibaba-provider-payment9002启动注册进nacos集群 - 修改配置文件

server:
  port: 9002

spring:
  application:
    name: nacos-payment-provider
  c1oud:
    nacos:
      discovery:
        #配置Nacos地址
        #server-addr: Localhost:8848
        #换成nginx的1111端口,做集群
        server-addr: 192.168.111.144:1111

management:
  endpoints:
    web:
      exposure:
        inc1ude: '*'

  • 启动微服务cloudalibaba-provider-payment9002
  • 访问nacos,查看注册结果
    SpringCloud—笔记(三)高级篇_第28张图片

高可用小总结

SpringCloud—笔记(三)高级篇_第29张图片


SpringCloud Alibaba Sentinel实现熔断与限流

1、Sentinel

git官网:https://github.com/alibaba/Sentinel
官网:https://sentinelguard.io/zh-cn/docs/introduction.html

是什么

Sentinel: 分布式系统的流量防卫兵

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 的主要特性:
SpringCloud—笔记(三)高级篇_第30张图片

去哪下

https://github.com/alibaba/Sentinel/releases
SpringCloud—笔记(三)高级篇_第31张图片

怎么玩

https://spring.io/projects/spring-cloud-alibaba#learn

服务使用中的各种问题.

  • 服务雪崩
  • 服务降级
  • 服务熔断
  • 服务限流

2、安装Sentinel控制台

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

sentinel组件由2部分构成

  • 后台
  • 前台8080

安装步骤

  • 下载:
    https://github.com/alibaba/Sentinel/releases

  • 运行命令
    java -jar sentinel-dashboard-1.8.1.jar

    前提:java8以上,8080端口不能被占用

  • 访问sentinel管理界面

    http://localhost:8080

    账号,密码:sentinel

SpringCloud—笔记(三)高级篇_第32张图片

3、初始化演示工程

1、启动Nacos8848

2、Module

cloudalibaba-sentinel-service8401

POM

    <dependencies>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cspgroupId>
            <artifactId>sentinel-datasource-nacosartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

YML

server:
  port: 8401

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

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

主启动

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

业务类FlowLimitController

@RestController
public class FlowLimitController {

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

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

3、启动Sentinel8080

4、启动微服务8401

5、启动8401微服务后查看sentienl控制台

  • 空空如也,啥都没有

  • Sentinel采用的懒加载说明

    执行一次访问即可:

    http://localhost:8401/testA
    http://localhost:8401/testBSpringCloud—笔记(三)高级篇_第33张图片

  • 结论
    sentinel8080正在监控微服务8401

4、流控规则

基本介绍

SpringCloud—笔记(三)高级篇_第34张图片

进一步解释说明:

  • 资源名:唯一名称,默认请求路径。
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)。
  • 阈值类型/单机阈值:
    • QPS(每秒钟的请求数量)︰当调用该API的QPS达到阈值的时候,进行限流。
    • 线程数:当调用该API的线程数达到阈值的时候,进行限流。
  • 是否集群:不需要集群。
  • 流控模式:
    • 直接:API达到限流条件时,直接限流。
    • 关联:当关联的资源达到阈值时,就限流自己。
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】。
  • 流控效果:
    • 快速失败:直接失败,抛异常。
    • Warm up:根据Code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。

流控模式

1、直接(默认)

直接–>快速失败(系统默认)

配置:

表示1秒钟内查询1次就是OK,若超过次数1,就直接–快速失败,报默认错误
SpringCloud—笔记(三)高级篇_第35张图片

测试:

快速多次访问:http://localhost:8401/testA

结果:

在这里插入图片描述

思考:

类似有个fallback的兜底方法?


2、关联

是什么:

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

配置A:

SpringCloud—笔记(三)高级篇_第36张图片

postman模拟并发密集访问testB

SpringCloud—笔记(三)高级篇_第37张图片

运行后发现testA挂了

访问:localhost:8401/testA

3、链路

流控效果

1、直接->快速失败(默认的流控处理)

直接失败,爆出异常信息:Blocked by Sentinel (flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

2、预热

说明

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

官网.

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

限流冷启动

源码

在这里插入图片描述

WarmUp配置

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

案例:
阀值为10+预热时长设置5秒。

系统初始化的阀值为10 3约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
SpringCloud—笔记(三)高级篇_第38张图片

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

http://localhost:8401/testB

发现刚开始会出现Blocked by Sentinel (flow limiting);后续慢慢ok

应用场景口

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

3、排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。

设置含义: /testB每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
SpringCloud—笔记(三)高级篇_第39张图片

官网:

匀速排队( RuleConstant . CONTROL BEHAVIOR_ RATE _LIMITER )方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考流量控制-匀速器模式,具体的例子可以参见PaceFlowDemo。
该访式的作用如下图所示:
SpringCloud—笔记(三)高级篇_第40张图片
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景, 在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求.而不是在第–秒真接拒绝多余的请求。

5、降级规则

SpringCloud—笔记(三)高级篇_第41张图片

官网说明:

熔断策略

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。

熔断降级规则说明

熔断降级规则(DegradeRule)包含下面几个重要的属性:
SpringCloud—笔记(三)高级篇_第42张图片

降级策略实战

1、慢调用比例模式

慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

1、代码

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

2、配置

满足两个条件会触发熔断:

  • 单位统计时长请求数大于设置的最小值。下面是每秒钟5个。

  • 慢请求达到设置的比例。

SpringCloud—笔记(三)高级篇_第43张图片

3、jmeter压测
SpringCloud—笔记(三)高级篇_第44张图片

4、结论

上面jmeter每秒钟10次请求大于最小值5;每个请求处理时长超过200ms,满足第二个条件,因此触发降级。

2、异常比例

异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

SpringCloud—笔记(三)高级篇_第45张图片
1、代码

	@GetMapping("/testE")
    public String testE() {
        int i = 1 / 0;
        return "testE";
    }

2、配置

如下:这个也是需要两个条件。

  • 每秒钟请求超过五个

  • 异常比例超过50%触发熔断

SpringCloud—笔记(三)高级篇_第46张图片

3、jmeter每秒钟十个访问

4、停掉jmeter再次curl,直接爆程序错误。也就是没触发sentinel熔断

3、异常数

当资源近 1 分钟的异常数目超过阈值之后会进行熔断

注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

SpringCloud—笔记(三)高级篇_第47张图片

6、热点key限流

基本介绍

SpringCloud—笔记(三)高级篇_第48张图片

官网

https://sentinelguard.io/zh-cn/docs/parameter-flow-control.html

热点参数限流

Overview

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel Parameter Flow Control

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。

承上启下复习start - @SentinelResource

兜底方法

分为系统默认和客户自定义,两种

  • 之前的case,限流出问题后,都是用sentine|系统默认的提示: Blocked by Sentinel (flow limiting)
  • 我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论

  • 从HystrixCommand到@SentinelResource

代码

com.alibaba.csp.sentinel.slots.block.BlockException
	@GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2){

        return "------testHotKey";
    }

    public String deal_testHotKey(String p1, String p2, BlockException exception){
        return "-------deal_testHotKey,o(╥﹏╥)o";    //sentinel系统默认的提示: Blocked by Sentinel (flow limiting) 
    }

配置

SpringCloud—笔记(三)高级篇_第49张图片
@SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”)

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

用了我们自己定义的
SpringCloud—笔记(三)高级篇_第50张图片

测试

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

参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

特例情况 :当p1的值为5的时候,不限流(VIP)

  • 普通:超过1秒钟一个后,达到阈值1后马上被限流
  • 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
  • 特例:假如当p1的值等于5时,它的阈值可以达到200

配置

SpringCloud—笔记(三)高级篇_第51张图片

测试

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

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

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

前提条件

  • 热点参数的注意点,参数必须是基本类型或者String

其它

手贱添加异常看看

int age = 10/0

报错500 (后面再讲)

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

总结

  • @SentinelResource主管配置出错,运行出错该走异常走异常

7、系统规则

系统自适应保护

Sentinel 系统自适应保护从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、入口 QPS 和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

官网
https://sentinelguard.io/zh-cn/docs/system-adaptive-protection.html

系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

配置说明
系统规则支持以下的阈值类型:

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

配置全局QPS

SpringCloud—笔记(三)高级篇_第52张图片
访问:http://localhost:8401/testA

testA并未做任何规则控制,快速点击出现 Blocked by Sentinel (flow limiting)

8、@SentinelResource

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

1、启动Nacos成功

2、启动Sentinel成功

3、Module

修改:cloudalibaba-sentinel-service8401

POM


        <dependency>
            <groupId>com.wlq.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>

YML

server:
  port: 8401

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

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

业务类RateLimitController

@RestController
public class RateLimitController {

    @GetMapping("/buResource")
    @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服务不可用");
    }
}

主启动

4、配置流控规则

SpringCloud—笔记(三)高级篇_第53张图片

5、测试

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

6、额外问题

关闭8401看看

sentinel控制台的流控规则消失了

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

1、通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
2、业务类RateLimitController

@GetMapping("/rateLimit/byUrl")
    public CommonResult byUrl(){
        return new CommonResult(200,"按URL限流测试",new Payment(2020L,"serial002"));
    }

3、访问一次
http://localhost:8401/rateLimit/byUrl
4、Sentinel控制台配置
SpringCloud—笔记(三)高级篇_第54张图片

5、测试

会返回默认的
SpringCloud—笔记(三)高级篇_第55张图片

面兜底方案面临的问题

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

客户自定义限流处理逻辑

1、创建CustomerBlockHandler类用于自定义限流处理逻辑

2、自定义限流处理类 CustomerBlockHandler

public class CustomerBlockHandler {
    public static CommonResult handlerException(BlockException exception){
        return new CommonResult(444,"按客户自定义-----1");
    }
    public static CommonResult handlerException2(BlockException exception){
        return new CommonResult(444,"按客户自定义-----2");
    }
}

3、Ratel imitController

@GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
            blockHandlerClass = CustomerBlockHandler.class,
            blockHandler = "handlerException2")
    public CommonResult customerBlockHandler(){
        return new CommonResult(200,"按客户自定义",new Payment(2020L,"serial003"));
    }

4、启动微服务后先调用一次
http://localhost:8401/rateLimit/customerBlockHandler

5、Sentinel控制台配置
SpringCloud—笔记(三)高级篇_第56张图片

6、测试后我们自定义的出来了
在这里插入图片描述

7、进一步说明
SpringCloud—笔记(三)高级篇_第57张图片

更多注解属性说明

9、服务熔断功能

sentinel整合ribbon+ openFeign+ fallback

Ribbon系列

1、启动nacos和sentinel

2、提供者9003/9004

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

POM

    <dependencies>
        
        <dependency>
            <groupId>com.wlq.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

YML 记得修改不同的端口号

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

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

主启动

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

业务类

@RestController
public class PaymentController {

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

    @GetMapping("/testAA")
    public String testAA(){
        return "*********test"+serverPort;
    }
}

测试地址 http://localhost:9003//testAA

3、消费者84

新建cloudalibaba-consumer-nacos-order84

POM

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
        dependency>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        
        <dependency>
            <groupId>com.wlq.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

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: 8719

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

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

主启动

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

业务类

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
@RestController
@Slf4j
public class CircleBreakerController {

    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")//没有配置
    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 ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

}

目的

  • fallback管运行异常
  • blockHandler管配置违规

4、测试地址
http://localhost:84/consumer/fallback/1

在这里插入图片描述

5、没有任何配置,访问http://localhost:84/consumer/fallback/4,页面不友好报错
SpringCloud—笔记(三)高级篇_第58张图片

6、只配置fallback

@RestController
@Slf4j
public class CircleBreakerController {

    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback")//没有配置
    @SentinelResource(value = "fallback", fallback = "handlerFallback") //fallback只负责业务异常
    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 ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

    //本例是fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }

}

访问:http://localhost:84/consumer/fallback/4
在这里插入图片描述

7、只配置blockHandler

@RestController
@Slf4j
public class CircleBreakerController {

    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback")//没有配置
//    @SentinelResource(value = "fallback", fallback = "handlerFallback") //fallback只负责业务异常
    @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
    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 ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

    //本例是fallback
//    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
//        Payment payment = new Payment(id,"null");
//        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
//    }

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

访问:http://localhost:84/consumer/fallback/4

点一下:
SpringCloud—笔记(三)高级篇_第59张图片
配置sentinel:
SpringCloud—笔记(三)高级篇_第60张图片
再多点几下发现:
在这里插入图片描述

8、fallback和blockHandler都配置

@RestController
@Slf4j
public class CircleBreakerController {

    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Autowired
    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")
    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 ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

    //本例是fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }

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

}

正常访问:http://localhost:84/consumer/fallback/1
在这里插入图片描述
访问错误:http://localhost:84/consumer/fallback/4
在这里插入图片描述
配置sentinel:
SpringCloud—笔记(三)高级篇_第61张图片
再访问:http://localhost:84/consumer/fallback/4,多点几下
在这里插入图片描述

结论

  • 若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑。

9、忽略属性

exceptionsToIgnore,忽略指定异常,即这些异常不用兜底方法处理。
在这里插入图片描述
访问:http://localhost:84/consumer/fallback/4
SpringCloud—笔记(三)高级篇_第62张图片

Feign系列

1、修改84模块

  • 消费者调用提供者9003
  • Feign组件一般是消费侧

2、POM


<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

3、YML

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

4、业务类

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

controller

//==================OpenFeign

    @Resource
    private PaymentService paymentService;

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

5、主启动

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

6、测试
http://localhost:84/consumer/paymentSQL/1
在这里插入图片描述

7、测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级
在这里插入图片描述

熔断框架比较

-------- Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔商/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式匀速器模式、预热排队模式 不支持 简单的Rate Limiter模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控,机器发观等 简单的监控查看 不提供控制台,可对接其它监控系统

10、规则持久化

是什么

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

怎么玩

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

步骤

修改cloudalibaba-sentinel-service8401

POM

		
        <dependency>
            <groupId>com.alibaba.cspgroupId>
            <artifactId>sentinel-datasource-nacosartifactId>
        dependency>

YML , 添加nacos数据源配置

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080 # 配置sentinel dashboard地址
        port: 8719                # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
      # 流控规则持久化到nacos
      datasource:
        dsl:
          nacos:
            server-addr: localhost:8848
            data-id: cloudalibaba-sentinel-service
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

添加Naco业务规则配置, 内容解析

[{
    "resource": "/rateLimit/byUrl",
    "IimitApp": "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:是否集群。

启动8401后刷新sentinel发现业务规则有了

SpringCloud—笔记(三)高级篇_第63张图片

快速访问测试接口

SpringCloud—笔记(三)高级篇_第64张图片

停止8401再看sentinel ,没有了

在这里插入图片描述

重新启动8401再看sentinel,又出现了

访问:http://localhost:8401/rateLimit/byUrl
SpringCloud—笔记(三)高级篇_第65张图片


SpringCloud Alibaba Seata处理分布式事务

1、分布式事务问题

分布式前

  • 单机单库没这个问题
  • 从1:1 -> 1:N -> N:N

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

SpringCloud—笔记(三)高级篇_第66张图片
一句话:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

2、Seata简介

是什么

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

官网:http://seata.io/zh-cn/index.html

能干嘛

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

分布式事务处理过程的一ID+三组件模型:

  • Transaction ID XID 全局唯一的事务ID
  • 三组件概念
    • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
    • TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
    • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程:

  1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
  2. XID在微服务调用链路的上下文中传播;
  3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;
  4. TM向TC发起针对XID的全局提交或回滚决议;
  5. TC调度XID下管辖的全部分支事务完成提交或回滚请求。

SpringCloud—笔记(三)高级篇_第67张图片

去哪下

http://seata.io/zh-cn/blog/download.html

怎么玩

本地@Transactional

全局@GlobalTransactional

SEATA的分布式交易解决方案
SpringCloud—笔记(三)高级篇_第68张图片

3、Seata-Server安装

1、下载:http://seata.io/zh-cn/blog/download.html,下载版本 - 0.9.0

2、seata.zip解压到指定目录并修改conf目录下的file.conf配置文件
先备份原始file.conf文件

主要修改:自定义事务组名称+事务日志存储模式为db +数据库连接信息

file.conf

service模块

service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "fsp_tx_group"
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}

store模块

## 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
  }
}

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

4、在seata库里建表

建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面

-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `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_gmt_modified_status` (`gmt_modified`, `status`),
  key `idx_transaction_id` (`transaction_id`)
);

-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
  `branch_id` bigint not null,
  `xid` varchar(128) not null,
  `transaction_id` bigint ,
  `resource_group_id` varchar(32),
  `resource_id` varchar(256) ,
  `lock_key` varchar(128) ,
  `branch_type` varchar(8) ,
  `status` tinyint,
  `client_id` varchar(64),
  `application_data` varchar(2000),
  `gmt_create` datetime,
  `gmt_modified` datetime,
  primary key (`branch_id`),
  key `idx_xid` (`xid`)
);

-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
  `row_key` varchar(128) not null,
  `xid` varchar(96),
  `transaction_id` long ,
  `branch_id` long,
  `resource_id` varchar(256) ,
  `table_name` varchar(32) ,
  `pk` varchar(36) ,
  `gmt_create` datetime ,
  `gmt_modified` datetime,
  primary key(`row_key`)
);

5、修改seata\conf目录下的registry.conf配置文件

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }
  ...
}

6、先启动Nacos端口号8848

7、再启动seata-server
在这里插入图片描述

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

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

2、分布式事务业务说明

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

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

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

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

3、创建业务数据库

  • seata, order:存储订单的数据库;
  • seata_ storage: 存储库存的数据库;
  • seata_ account: 存储账户信息的数据库。

建库SQL

CREATE DATABASE seata_order;

CREATE DATABASE seata_storage;

CREATE DATABASE seata_account;

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

  • seata order库 下建t_order表
  • seata_ storage库下建t_storage表
  • seata account库下建t_account表

sql

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 CHARSET=utf8;


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=7 CHARSET=utf8;
INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);


CREATE TABLE t_account(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    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=7 CHARSET=utf8;
INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);

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

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

\seata-server-0.9.0\seata\conf目录下的db_ undo_ log.sql

建表SQL

-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

6、最终效果

SpringCloud—笔记(三)高级篇_第69张图片

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

业条需求

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

新建订单Order-Module

1、seata-order-service2001

2、POM

    <dependencies>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-allartifactId>
                    <groupId>io.seatagroupId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>io.seatagroupId>
            <artifactId>seata-allartifactId>
            <version>0.9.0version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
    dependencies>

3、YML

server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中的对应
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

4、file.conf

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->rgroup
  vgroup_mapping.fsp_tx_group = "default"	#修改自定义事务组名称<---------
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}

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、registry.conf

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"
  }
}

6、domain

CommonResult

@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);
    }
}

Order

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    private Integer status; //订单状态:0:创建中;1:已完结

}

7、Dao接口及实现

OrderDao

@Mapper
public interface OrderDao {
    //1 新建订单
    void create(Order order);

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

resource下建mapper,OrderMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.wlq.springcloud.dao.OrderDao">

    <resultMap id="BaseResultMap" type="com.wlq.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>

8、Service接口及实现

  • AccountService
  • OrderService
    • OrderServiceImpl
  • StorageService
public interface OrderService {
    void create(Order order);
}

@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("----->开始新建订单");
        //1 新建订单
        orderDao.create(order);

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

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

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

        log.info("----->下订单结束了,O(∩_∩)O哈哈~");

    }

}
@FeignClient(value = "seata-account-service")
public interface AccountService {
    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);

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

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

9、Controller

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

import javax.annotation.Resource;

@RestController
public class OrderController {

    @Resource
    private OrderService orderService;


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

}

10、Config配置

  • MyBatisConfig
  • DataSourceProxyConfig
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;

/**
 * 使用Seata对数据源进行代理
 */
@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapperLocations}")
    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();
    }

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

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

11、主启动

@EnableDiscoveryClient
@EnableFeignClients
//取消数据源的自动创建,而是使用自己定义的
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SeataOrderMainApp2001 {

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

新建库存Storage-Module

1、seata-storage-service2002

2、POM,与2001大致一样

3、YML

server:
  port: 2002

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

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

4、file.conf,与2001大致一样

5、registry.conf,与2001大致一样

6、domain

@Data
public class Storage {

    private Long id;

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

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

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

    /**
     * 剩余库存
     */
    private Integer residue;
}

CommonResult和2001一样

7、Dao接口及实现

@Mapper
public interface StorageDao {

    //扣减库存
    void decrease(@Param("productId") Long productId, @Param("count") Integer count);
}


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.wlq.springcloud.dao.StorageDao">

    <resultMap id="BaseResultMap" type="com.wlq.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>

8、Service接口及实现

  • StorageService
    • StorageServiceImpl
public interface StorageService {
    /**
     * 扣减库存
     */
    void decrease(Long productId, Integer count);
}
import com.wlq.springcloud.dao.StorageDao;
import com.wlq.springcloud.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
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) {
        LOGGER.info("------->storage-service中扣减库存开始");
        storageDao.decrease(productId,count);
        LOGGER.info("------->storage-service中扣减库存结束");
    }
}

9、Controller

import com.wlq.springcloud.domain.CommonResult;
import com.wlq.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,"扣减库存成功!");
    }

}

10、Config配置,与2001一样

11、主启动

@EnableDiscoveryClient
@EnableFeignClients
//取消数据源的自动创建,而是使用自己定义的
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SeataStorageMainApp2002 {
    public static void main(String[] args) {
        SpringApplication.run(SeataStorageMainApp2002.class,args);
    }
}

新建账户Account-Module

1、seata-account-service2003

2、POM,与2001一样

3、YML

server:
  port: 2003

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

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

4、file.conf ,与2001一样

5、registry.conf ,与2001一样

6、domain

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Long id;

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

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

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

    /**
     * 剩余额度
     */
    private BigDecimal residue;
}

CommonResult与上一样

7、Dao接口及实现

@Mapper
public interface AccountDao {

    /**
     * 扣减账户余额
     */
    void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
}


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.wlq.springcloud.dao.AccountDao">

    <resultMap id="BaseResultMap" type="com.wlq.springcloud.domain.Account">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="total" property="total" jdbcType="DECIMAL"/>
        <result column="used" property="used" jdbcType="DECIMAL"/>
        <result column="residue" property="residue" jdbcType="DECIMAL"/>
    resultMap>

    <update id="decrease">
        UPDATE t_account
        SET
          residue = residue - #{money},used = used + #{money}
        WHERE
          user_id = #{userId};
    update>

mapper>

8、Service接口及实现

public interface AccountService {

    /**
     * 扣减账户余额
     * @param userId 用户id
     * @param money 金额
     */
    void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
import com.wlq.springcloud.dao.AccountDao;
import com.wlq.springcloud.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

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

/**
 */
@Service
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) {
        LOGGER.info("------->account-service中扣减账户余额开始");
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service中扣减账户余额结束");
    }
}

9、Controller

import com.wlq.springcloud.domain.CommonResult;
import com.wlq.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,"扣减账户余额成功!");
    }

}

10、config配置,与上面一样

11、主启动

@EnableDiscoveryClient
@EnableFeignClients
//取消数据源的自动创建,而是使用自己定义的
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SeataAccountMainApp2003 {
    public static void main(String[] args) {
        SpringApplication.run(SeataAccountMainApp2003.class,args);
    }
}

6、Test

下订单->减库存->扣余额->改(订单)状态
SpringCloud—笔记(三)高级篇_第70张图片

数据库初始情况

t_account
SpringCloud—笔记(三)高级篇_第71张图片
t_order
SpringCloud—笔记(三)高级篇_第72张图片
t_storage
SpringCloud—笔记(三)高级篇_第73张图片

正常下单

  • http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
  • 数据库情况

超时异常,没加@GlobalTransactional

模拟AccountServiceImpl添加超时

@Service
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) {
        LOGGER.info("------->account-service中扣减账户余额开始");
        //模拟超时异常,全局事务回滚
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service中扣减账户余额结束");
    }
}

另外,OpenFeign的调用默认时间是1s以内,所以最后会抛异常。
SpringCloud—笔记(三)高级篇_第74张图片

数据库情况:
在这里插入图片描述
SpringCloud—笔记(三)高级篇_第75张图片
故障情况

  • 当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1
  • 而且由于feign的重试机制,账户余额还有可能被多次扣减

超时异常,添加@GlobalTransactional

用@GlobalTransactional标注OrderServiceImpl的create()方法。

@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("----->开始新建订单");
        //1 新建订单
        orderDao.create(order);

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

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

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

        log.info("----->下订单结束了,O(∩_∩)O哈哈~");

    }

}

还是模拟AccountServiceImpl添加超时,下单后数据库数据并没有任何改变,记录都添加不进来,达到出异常,数据库回滚的效果。
SpringCloud—笔记(三)高级篇_第76张图片
数据库情况:
SpringCloud—笔记(三)高级篇_第77张图片


7、补充

1、 Seata

  • 2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案
  • Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架
  • 2020起始,参加工作后用1.0以后的版本

2、再看TC/TM/RM三大组件重

SpringCloud—笔记(三)高级篇_第78张图片
分布式事务执行流程:

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

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

是什么:

Seata 是什么?

  • Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

  • AT 模式

前提

  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。

整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:

    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。
一阶段加载

在一阶段, Seata会拦截“业务SQL"

  • 1、解析SQL语义,r找到“业务SQL"要更新的业务数据,在业务数据被更新前,将其保存成"before image" ,
  • 2、执行“业务SQL”更新业务数据,在业务数据更新之后,
  • 3、其保存成"after image" ,最后生成行锁。

以上操作全部在一个数据库事务内完成, 这样保证了一阶段操作的原子性。
SpringCloud—笔记(三)高级篇_第79张图片

二阶段提交
  • 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
  • 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
  • 二阶段如是顺利提交的话
  • 因为“业务SQL"在一阶段已经提交至数据库,所以Seata框架只需将-阶段保存的快照数据和行锁删掉,完成数据清理即可。
    SpringCloud—笔记(三)高级篇_第80张图片
二阶段回滚

二阶段回滚:

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

SpringCloud—笔记(三)高级篇_第81张图片

4、debug

AccountServiceImpl,打上断点
SpringCloud—笔记(三)高级篇_第82张图片

seata数据库
在这里插入图片描述
查看undo_log表:在这里插入图片描述
发现rollback_ingo里面全都记录了beforeimage和afterimage的数据;

二阶段事务回滚就会:根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:

update product set name = 'TXC' where id = 1;

提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

5、补充

SpringCloud—笔记(三)高级篇_第83张图片

你可能感兴趣的:(springcloud,笔记,spring)