Spring Cloud Alibaba学习总结

落花浮王杯 提交于 2021-01-02 11:07:58

Spring Cloud Alibaba

1.入门简介

1.1 为什么会出现SpringCloud alibaba

spring cloud Netflix进入维护模式

1.2 是什么

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

1.3 能干嘛

  1. 服务限流降级

    默认支持Servlet、Feign、RestTemplate、Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics监控

  2. 服务注册与发现

    适配Spring Cloud服务注册与发现标准,默认集成了Ribbon的支持

  3. 分布式配置管理

    支持分布式系统中的外部化配置,配置更改时自动刷新

  4. 消息驱动能力

    基于Spring Cloud Stream为微服务应用构建消息驱动能力

  5. 阿里云存储对象

    阿里云提供的海量、安全、低成本、高可靠的云存储服务.支持在任何应用、任何时间、任何地点存储和访问任意类型的数据

  6. 分布式任务调度

    提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务.同时提供分布式的任务执行模型,如网格任务.网格任务支持海量子任务均匀分配到所有Worker(schedulerx-client)上执行

1.4 怎么用

  1. Sentinel

    阿里巴巴开源产品,把流量作为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性.

  2. Nacos

    阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台.

  3. RocketMQ

    Apache RocketMQ基于Java的高性能,高吞吐量的分布式消息和流计算平台.

  4. Dubbo

    Apache Dubbo是一款高性能的Java RPC框架.

  5. Seata

    阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案.

  6. Alibaba Cloud OSS

    阿里云对象存储服务器(Object Storage Service,简称OSS),是阿里云提供的海量,安全,低成本,高可靠的云存储服务.

  7. Alibaba Cloud Schedulerx

    阿里中间件团队开发的一款分布式调度产品,支持周期性的任务与固定时间点触发任务.

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

2.1 Nacos(Naming Configuration Service)简介

2.1.1 是什么
  1. 一个更易于构建云远程应用的动态服务发现,配置管理和服务管理平台
  2. Nacos就是注册中心 + 配置中心的组合 等价于 Nacos = Eureka + Config + Bus
2.1.2 能干嘛
  1. 替代Eureka做服务注册中心
  2. 替代Config做服务配置中心
2.1.3 注册中心比较
服务注册与发现框架 CAP模型 控制台管理 社区活跃度
Eureka AP 支持 低(2.x版本闭源)
Zookeeper CP 不支持
Consul CP 支持
Nacos AP/CP 支持
  1. nacos和CAP

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

2.2 Nacos安装

  1. 本地Java8+Maven环境已经OK
  2. 先从官网下载Nacos
  3. 解压安装包,直接运行bin目录下的startup.cmd
  4. 运行成功后访问http://localhost:8848/nacos
  5. 默认账号nacos nacos

2.3 Nacos作为服务注册中心

2.3.1 pom
<dependencies>
        <!--nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    <!--自己的公用jar包-->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
        </dependency>
    </dependencies>

2.3.2 yml文件
server:
  port: 9001
spring:
  application:
    name: nacos-payment
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
management:
  endpoints:
    web:
      exposure:
        include: '*'
2.3.3 主启动类
package com.atguigu.springcloud;

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

@SpringBootApplication
@EnableDiscoveryClient
public class NacosPaymentMain9001 {
   
   
    public static void main(String[] args) {
   
   
        SpringApplication.run(NacosPaymentMain9001.class,args);
    }
}
2.3.4 Nacos支持AP和CP两种模式的切换
  1. C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应.

  2. 何时选择何种模式

    一般来说,

    如果不需要存储服务级别的信息且服务实例是通过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’

2.4 Nacos作为服务配置中心

2.4.1 配置中心dataID设置

文件后缀名不能写yml,要写yaml

20201223114853032

2.4.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.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-alibaba-nacos-config-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>
        <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

2.4.3 yaml文件
# 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
        group: DEV_GROUP  #指定配置文件分组GROUP
        namespace: 357392ad-2083-4c4a-afb1-945dc549080f  #通过命名空间ID指定命名空间


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


# application.yml

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

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

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

2.4.5 业务类
package com.atguigu.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* 通过spring cloud的原生注解@RefreshScope 实现配置的自动刷新功能
* */
@RestController
@Slf4j
@RefreshScope   //支持nacos的动态刷新功能
@RequestMapping("/config")
public class ConfignClientController {
   
   

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

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

2.4.6 分类配置
2.4.6.1 namespace+Group+DataID三者关系
  1. 是什么

    1. 类似于Java里面的package名和类名
    2. 最外层的namespace是可以用于鱼粉部署环境的,Group和DataID逻辑上区分两个目标对象
  2. 三者情况

images\image-20201223140229213.png)]

  1. 默认情况

    Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT

  1. Nacos默认的命名空间是public,Namespace主要是用来实现隔离.
  2. 比方说我们说我们现在有三个开发环境:开发,测试,生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的.
  3. Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
  4. Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是指对指定微服务的一个虚拟划分.
  5. 比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
  6. 这时就可以给杭州机房的Service微服务起一个集群名称(HZ),
  7. 给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能.
  8. 最后是Instance,就是微服务的实例.
2.4.6.1 三种法案加载配置
  1. DataID方案

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

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

      • 新建dev配置DataID----------nacos-config-client-dev.yaml
      • 新建test配置DataID----------nacos-config-client-test.yaml
    3. 通过spring.profile.active属性就能进行多环境下文件的读取

      spring:
        profiles:
          active: dev # 表示开发环境
          #active: test   #表示测试环境
      
  2. Group方案

    1. 通过Group实现环境区分

      • 新建Group,在新建配置文件时,将DEFAULT_GROUP改为你要建的分组即可(例如DEV_GROUP)
    2. 在nacos图形界面控制台上面新建配置文件DataID

      • 新建dev配置DataID----------nacos-config-client-dev.yaml
    3. bootstrap+application

      • bootstrap文件在config下指定group

        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  #指定配置文件分组GROUP
        
      • yml文件

        spring:
          profiles:
            active: dev # 表示开发环境
        
  3. Namespace方案

    1. 新建dev/test的Namespace

    2. 回到服务管理-服务列表查看

    3. 按照域名配置填写

    4. yml

      • bootstrap.yml

        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  #指定配置文件分组GROUP
                namespace: 357392ad-2083-4c4a-afb1-945dc549080f  #通过命名空间ID指定命名空间
        
      • application.yml

        spring:
          profiles:
            active: dev # 表示开发环境
        

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

2.5.1 说明
  1. 默认Nacos使用嵌入式数据库实现数据存储.所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的.为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储.

  2. Nacos支持三种部署模式

    1. 单机模式 - 用于测试和单机使用.

      • 在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况.

      • 0.7版本之后增加了mysql数据源能力,具体的操作步骤:

        1. 安装数据库,版本要求: 5.6.5+

        2. 初始化mysql数据库,数据库初始化文件:nacos-mysql.sql

        3. 修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url,用户名和密码.

          images\image-.png)]

     4. 再以单机模式启动nacos,nacos所有写嵌入式数据库的数据都写到了mysql.
    
    1. 集群模式 - 用于生产环境,确保高可用.

    2. 多集群模式 - 用于多数据中心场景.

2.5.2 Nacos持久化配置解释
  1. Nacos默认自带的是嵌入式数据库derby

  2. derby到mysql切换配置步骤

    1. nacos-server-1.1.4\nacos\conf目录下找到SQL脚本----nacos-mysql.sql-----并执行脚本

    2. nacos-server-1.1.4\nacos\conf目录下找到application.properties,添加这段配置

      spring.datasource.platform=mysql
      
      db.num=1
      db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
      db.user=root
      db.password=123456
      
      
  3. 启动Nacos,可以看到是个全新的空记录界面,以前是记录进derby

  4. MYSQL密码:u3w)D9Q&wb%w

3. SpringCloud Alibaba Sentinel

Sentinel Hystrix
1.单独一个组件,可以独立出来 1.需要我们程序员自己手工搭建监控平台
2.直接界面化的细粒度统一配置 2.没有一套web界面可以给我们进行更加细粒度化的配置流控,速率控制,服务熔断,服务降级…
  1. 约定 > 配置 > 编码
  2. 都可以写在代码里面,但是我们本次还是大规模学习使用配置和注解的方式,尽量少写代码

3.1 安装Sentinel控制台

3.1.1 组成

Sentinel分为两个部分:

  1. 核心库(Java客户端)不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo/SpringCloud等框架也有较好的支持.
  2. 控制台(Dashboard)基于SpringBoot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器.
3.1.2 安装步骤
  1. 下载 https://github.com/alibaba/Sentinel/releases/download/1.7.0/sentinel-dashboard-1.7.0.jar

  2. 运行命令

    1. 前提
      • java 8 环境OK
      • 8080端口不能被占用
    2. 命令 java -jar sentinel-dashboard-1.7.0.jar
  3. 访问sentinel管理界面 localhost:8080

  4. pom文件依赖

    <!--alibaba sentinel-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
            <!--spring cloud alibaba sentinel-datasource-nacos 后续持久化会用到-->
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-datasource-nacos</artifactId>
            </dependency>
    

3.2 流控规则

3.2.1 基本介绍

解释说明:

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

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

    images\image-20201224170933646.png)]

    结果: Blocked by Sentinel (flow limiting)

    QPS和线程区别:

    1. QPS把敌人抵御在国门之外,超过阈值,就不让进来
    2. 线程是放进来关门打狗,超过阈值,你进来,但是你在那给我等着.
  2. 关联

    设置效果

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

    在这里插入图片描述

  3. 链路

3.2.2 流控效果
  1. 直接–>快速失败(默认的流控处理)

  2. Warm up(预热)

    Warm Up(RuleConstant.CONTROL_BEHAVIOR_WAARM_UP)方式,即预热/冷启动方式.当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮.通过"冷启动",让通过的流量缓慢增加,在一定的时间内逐渐增加到阈值上线,给冷系统一个预热的时间,避免冷系统被压垮.详细文档可以参考流量控制 - Warm Up 文档

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

    • 官网

      1. 默认coldFactor为3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值.
      2. 限流冷启动
    • Warm Up配置

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

      2. 案例,阈值为10 + 预热时长设置为5秒

        系统初始化的阈值为10/3约等于3,即阈值刚开始为3;然后过了5秒后阈值才慢慢升高恢复到10

      在这里插入图片描述

  3. 排队等待

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

    设置含义: /testA每秒请求一次,超过的话就排队等待,等待的超时时间为20000毫秒.

    在这里插入图片描述

3.3 Sentinel降级

3.3.1 基本介绍

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

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

Sentinel的断路器是没有半开状态的

  1. 半开的状态系统自动去检测是否请求有异常,
  2. 没有异常就关闭断路器恢复使用,
  3. 有异常则基础打开断路器不可用.

在这里插入图片描述

  1. RT(平均响应时间,秒级)

    平均响应时间 超出阈值 且 在时间窗口期内通过的请求>=5, 两个条件同时满足后触发降级

    窗口期过后关闭断路器

    RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

  2. 异常比例(秒级)

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

  3. 异常数(分钟级)

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

3.3.2 降级策略实战
  1. RT

    1. 是什么

      平均响应时间(DEGRADE_GRADE_RT): 当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以ms为单位),那么在接下来的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动的熔断(抛出DegradeException).注意Sentinel默认统计的RT上线是4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置.

      在这里插入图片描述

    2. 测试

      1. 测试代码

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

        在这里插入图片描述

        按照上述配置:

        • 永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次的任务,
        • 如果超过200毫秒还没处理完,在未来一秒钟的窗口期内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了
        • 后续访问量没有那么大了,保险丝恢复正常,微服务可以访问
  2. 异常比例

    1. 是什么

      异常比例(DEGRADE_GRADE_EXCEPTION_RATIO): 当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule中的count)之后,资源进入降级状态,即在接下来的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回.异常比率的阈值范围[0.0,1.0],代表0% - 100%.

      20201225140811335

    2. 配置

    20201225143049015

    @GetMapping("/testE")
        public String getE(){
          
          
            log.info("testE   测试异常比例");
            int age = 10/0;
            return "------testE";
        }
    
    
    1. 结论

      按照上述配置,

      单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;

      开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了.

      断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级.

  3. 异常数

    1. 是什么

      异常数(DEGRADE_GRADE_EXCEPTION_COUNT): 当资源近1分钟的异常数目超过阈值之后会进行熔断.注意由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后仍可能再进入熔断状态.

      时间窗口一定要大于等于60秒

      在这里插入图片描述

    2. 代码配置

      在这里插入图片描述

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

3.4 热点key限流

3.4.1 是什么

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

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

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

3.4.2 返回的值(系统默认和自定义)
  1. 兜底方法分为系统默认用户自定义两种
  2. 之前的case,限流出问题之后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)
  3. 现在我们可以自定义一个 从@HystrixCommand到@SentinelResource
3.4.3 代码配置
@GetMapping("/getHotKey")
    @SentinelResource(value = "getHotKey", blockHandler = "deal_getHotKey")
    public String getHotKey(@RequestParam(value = "p1",required = false) String p1,
                            @RequestParam(value = "p2",required = false) String p2){
   
   
        log.info("getHotKey   测试热点key限流");
        return "------getHotKey";
    }

    public String deal_getHotKey(String p1, String p2, BlockException exception){
   
   
        log.info("deal_getHotKey    aaaaaa");
        return "------deal_getHotKey";
    }

在这里插入图片描述

  1. 第一种

    1. @SentinelResource(value = “getHotKey”, blockHandler = “deal_getHotKey”)
    2. 方法getHotKey里面第一个参数只要超过每秒1次,马上降级处理
    3. 用了我们自定义的方法.结果:------deal_getHotKey
  2. 第二种

    1. @SentinelResource(value = “getHotKey”)

    2. 异常打到了前台界面,对用户不友好,结果:
      20201225150730068

  3. 所以,如果我们用热点key限流一定要写兜底的方法

3.4.4 参数例外项

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

  1. 特例情况

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

images\image-.png)]

  1. 结论: http://localhost:8401/sentinel/getHotKey?p1=5 阈值在每秒200下,怎么点都可以

    http://localhost:8401/sentinel/getHotKey?p1=1 阈值还是每秒1下,报错

@SentinelResource

处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

RuntimeException

int age = 10/0,这个是java运行时爆出的运行时异常RuntimeException,@SentinelResource不管

总结

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

3.5 Sentinel系统规则

3.5.1 是什么

系统保护规则是从应用级别的入口流量进行控制,从单台机器的load,cpu使用率,平均RT,入口QPS并发线程数等几个纬度监控应用指标,让系统尽可能泡在最大吞吐量的同时保证系统整体的稳定性.

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

3.5.2 参数配置
  • Load自适应 (仅对Linux/Unix-like机器生效): 系统的load1作为启发指标,进行自适应系统保护.当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段).系统容量由系统的maxQPS * minRt估算得出.设定参考值一般是CPU cores * 2.5.
  • CPU usage (1.5.0+版本) : 当系统CPU使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏.
  • 平均RT : 当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒.
  • 并发线程数 : 当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护.
  • 入口QPS : 当单台机器上所有入口流量的QPS达到阈值即触发系统保护.

3.6 @SentinelResource

3.6.1 按资源名称限流+后续处理
  1. 按照@SentinelResource资源名称配置流控规则
  2. 因为我们写了兜底的方法,所以按照我们的方法执行
  3. 产生的问题:关闭8401服务器,系统流控规则消失
3.6.2 按URL地址限流+后续处理
  1. 按照URL地址进行配置流控规则
  2. 我们没有写兜底的方法,所以就用系统默认自带的
  3. 产生的问题:关闭8401服务器,系统流控规则消失
3.6.3 上面兜底方案面临的问题
  1. 系统默认的,没有体现我们自己的业务要求.
  2. 依照现有调价,我们自定义处理方法和业务代码耦合在一起,不直观.
  3. 每个业务方法都添加一个兜底的方法,代码膨胀加剧.
  4. 全局统一的处理方法没有体现.
3.6.4 客户自定义限流处理逻辑
  1. 创建CustomerBlockHandler类用于自定义限流处理逻辑

  2. 自定义限流处理类CustomerBlockHandler

    package com.atguigu.springcloud.myhandler;
    
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.atguigu.springcloud.utils.CommonResult;
    
    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. RateLimitController

    package com.atguigu.springcloud.controller;
    
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.atguigu.springcloud.myhandler.CustomerBlockHandler;
    import com.atguigu.springcloud.utils.CommonResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/rate")
    public class RateLimitController {
         
         
    
        @GetMapping("/consumerBlockHandler")
        @SentinelResource(value = "consumerBlockHandler",
                blockHandlerClass = CustomerBlockHandler.class,
                blockHandler = "handlerException2")
        public CommonResult consumerBlockHandler(){
         
         
            return new CommonResult(200,"用户自定义----OK","我成功了");
        }
    }
    
    
  4. 启动微服务后先调用一次

  5. Sentinel控制台配置

20201225163826116

  1. 测试后我们自定义的出来了

3.7 服务熔断功能

Sentinel整合ribbon+OpenFeign+fallback

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

3.8 规则持久化

3.8.1 是什么

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

3.8.2 怎么用

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

3.8.3 步骤
  1. pom文件

    <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    
  2. yml文件

    spring:
      application:
        name: nacos-sentinel-service
      cloud:
        nacos:
          discovery:
            # nacos 服务注册中心地址
            server-addr: 127.0.0.1:8848
        sentinel:
          transport:
            # 配置中心 sentinel dashboard地址
            dashboard: 127.0.0.1:8080
            # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
            port: 8719
          datasource:
            ds1:
              nacos:
                server-addr: localhost:8848
                dataId: nacos-sentinel-service
                groupId: DEFAULT_GROUP
                data-type: json
                rule-type: flow
    
  3. nacos新建配置

    [
        {
         
         
            "resource":"/rate/byResource",
            "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: 是否集群
    

4. Spring Cloud Alibaba Seata处理分布式事务

4.1 分布式事务问题

  1. 分布式前: 单机单库没有任何问题 从1:1 --> 1:N --> N:N

  2. 分布式之后:

    单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,

    业务操作需要三个微服务来完成.此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没有办法保证

    images\image-.png)]

    images\image-.png)]

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

4.2 Seata简介

4.2.1 是什么

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

http://seata.io/zh-cn

4.2.2 能干嘛
  1. 一个典型的分布式事务过程

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

      • Transaction ID XID 全局唯一的事务ID

      • 3组件概念

        • Transaction Coordinator(TC) - 事务协调者

          事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;

        • Transaction Manager™ - 事务管理器

          控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的协议;

        • Resource Manager(RM) - 资源管理器

          控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚.

    2. 处理过程

      1. TM向TC申请开启一个全局事务,全局事务创建成功并且生成一个全局唯一的XID;

      2. XID在微服务调用链路的上下文中传播;

      3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;

      4. TM向TC发起针对XID的全局提交或回滚决议;

      5. TC调度XID下管辖的全部分支事务完成提交或回滚请求.

      images\image-20201228142009465.png)]

4.2.3 怎么用
  1. 本地 @Transactional
  2. 全局 @GlobalTransactional

4.3 Seata-Server安装

  1. 下载 http://seata.io/zh-cn

  2. 解压缩

  3. 修改conf目录下的file.conf文件

    service {
      #vgroup->rgroup
      # vgroup_mapping.my_test_tx_group 修改为自己的名字,默认为default
      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 {
      ## store mode: file、db、redis
      mode = "db"  # file修改为 db
      
      # 下面的数据库配置修改为自己的
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
        datasource = "druid"
        ## mysql/oracle/postgresql/h2/oceanbase etc.
        dbType = "mysql"
        driverClassName = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://127.0.0.1:3306/seata"
        user = "root"
        password = "123456"
        minConn = 5
        maxConn = 100
        globalTable = "global_table"
        branchTable = "branch_table"
        lockTable = "lock_table"
        queryLimit = 100
        maxWait = 5000
      }
    }
    
  4. 创建seata数据库,将conf/db_store.sql粘脚本建表

  5. 修改conf/registry.conf文件

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      # type从file修改为nacos
      type = "nacos"
    
      nacos {
      #nacos地址修改为自己的地址
        serverAddr = "localhost:8848"
        namespace = ""
        cluster = "default"
      }
    

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

4.4.1 业务说明

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

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,

再通过远程调用账户服务来扣减用户账户里面的余额,

最后在订单服务中修改订单状态位已完成.

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

4.4.2 创建业务数据库
  1. seata_order: 存储订单的数据库;

    -- 建表 t_order
    /*
     Navicat Premium Data Transfer
    
     Source Server         : 本地
     Source Server Type    : MySQL
     Source Server Version : 50706
     Source Host           : localhost:3306
     Source Schema         : seata_order
    
     Target Server Type    : MySQL
     Target Server Version : 50706
     File Encoding         : 65001
    
     Date: 28/12/2020 16:34:14
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for t_order
    -- ----------------------------
    DROP TABLE IF EXISTS `t_order`;
    CREATE TABLE `t_order`  (
      `id` bigint(11) NOT NULL,
      `user_id` bigint(11) NULL DEFAULT NULL COMMENT '用户ID',
      `product_id` bigint(11) NULL DEFAULT NULL COMMENT '产品ID',
      `count` int(11) NULL DEFAULT NULL COMMENT '数量',
      `money` decimal(11, 0) NULL DEFAULT NULL COMMENT '金额',
      `status` int(1) UNSIGNED NULL DEFAULT NULL COMMENT '订单状态: 0创建中,1已完结',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单表' ROW_FORMAT = Compact;
    
    -- ----------------------------
    -- Records of t_order
    -- ----------------------------
    INSERT INTO `t_order` VALUES (51, 1, 1, 10, 100, 1);
    INSERT INTO `t_order` VALUES (52, 1, 1, 10, 100, 1);
    INSERT INTO `t_order` VALUES (53, 1, 1, 10, 100, 1);
    INSERT INTO `t_order` VALUES (54, 1, 1, 10, 100, 0);
    
    SET FOREIGN_KEY_CHECKS = 1;
    
    -- 建回滚日志表conf/db_undo_log.sql
    
    
  2. seata_storage: 存储库存的数据库;

    --建表 t_storage
    /*
     Navicat Premium Data Transfer
    
     Source Server         : 本地
     Source Server Type    : MySQL
     Source Server Version : 50706
     Source Host           : localhost:3306
     Source Schema         : seata_storage
    
     Target Server Type    : MySQL
     Target Server Version : 50706
     File Encoding         : 65001
    
     Date: 28/12/2020 16:34:34
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for t_storage
    -- ----------------------------
    DROP TABLE IF EXISTS `t_storage`;
    CREATE TABLE `t_storage`  (
      `id` bigint(11) NOT NULL,
      `product_id` bigint(11) NULL DEFAULT NULL COMMENT '产品ID',
      `total` int(11) NULL DEFAULT NULL COMMENT '总库存',
      `used` int(11) NULL DEFAULT NULL COMMENT '已用库存',
      `residue` int(11) NULL DEFAULT NULL COMMENT '剩余库存',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '库存' ROW_FORMAT = Compact;
    
    -- ----------------------------
    -- Records of t_storage
    -- ----------------------------
    INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);
    
    SET FOREIGN_KEY_CHECKS = 1;
    
    
    -- 建回滚日志表conf/db_undo_log.sql
    
  3. seata_account: 存储账户信息的数据库.

    --建表 t_account
    /*
     Navicat Premium Data Transfer
    
     Source Server         : 本地
     Source Server Type    : MySQL
     Source Server Version : 50706
     Source Host           : localhost:3306
     Source Schema         : seata_account
    
     Target Server Type    : MySQL
     Target Server Version : 50706
     File Encoding         : 65001
    
     Date: 28/12/2020 16:34:27
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for t_account
    -- ----------------------------
    DROP TABLE IF EXISTS `t_account`;
    CREATE TABLE `t_account`  (
      `id` bigint(11) NOT NULL,
      `user_id` bigint(11) NULL DEFAULT NULL COMMENT '用户ID',
      `total` decimal(10, 0) NULL DEFAULT NULL COMMENT '总额度',
      `used` decimal(10, 0) NULL DEFAULT NULL COMMENT '已用额度',
      `residue` decimal(10, 0) NULL DEFAULT NULL COMMENT '剩余可用额度',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '账户信息' ROW_FORMAT = Compact;
    
    -- ----------------------------
    -- Records of t_account
    -- ----------------------------
    INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);
    
    SET FOREIGN_KEY_CHECKS = 1;
    
    -- 建回滚日志表conf/db_undo_log.sql
    
    

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

4.5.1 业务需求

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

4.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.atguigu.springcloud</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>seata-order-service2001</artifactId>
    
        <dependencies>
            <!--nacos-discovery-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!--alibaba sentinel-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
            <!--spring cloud alibaba sentinel-datasource-nacos 后续持久化会用到-->
            <dependency>
                <groupId>com.alibaba.csp</groupId>
                <artifactId>sentinel-datasource-nacos</artifactId>
            </dependency>
            <!--alibaba seata-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                <exclusions>
                    <exclusion>
                        <artifactId>seata-all</artifactId>
                        <groupId>io.seata</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!--版本需匹配一致-->
            <dependency>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
                <version>0.9.0</version>
            </dependency>
            <!--feign-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <!--Mybatis-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.0.0</version>
            </dependency>
            <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.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.atguigu.springcloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    
    
  2. yam文件

    server:
      port: 2001
    
    spring:
      application:
        name: seata-order-service
      cloud:
        alibaba:
          seata:
            tx-service-group: my_test_tx_group
        nacos:
          discovery:
            server-addr: localhost:8848
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&rewriteBatchedStatements=true&useSSL=true
        username: root
        password: 123456
    feign:
      hystrix:
        enabled: true
    logging:
      level:
        io:
          seata: info
    # mybatis配置
    mybatis:
      mapperLocations: classpath:mapper/*.xml
      type-aliases-package: com.atgui.springcloud.domain  #实体类包
    
    
  3. DataSourceProxyConfig配置类

    package com.atgui.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;
    /**
     * 使用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();
        }
    }
    
    
  4. MybatisConfig配置类

    package com.atgui.springcloud.config;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @MapperScan({
         
         "com.atgui.springcloud.dao"})
    public class MyBatisConfig {
         
         
    }
    
    
  5. service实现类

    package com.atgui.springcloud.service.impl;
    
    import com.atgui.springcloud.dao.OrderDao;
    import com.atgui.springcloud.domain.Order;
    import com.atgui.springcloud.service.AccountService;
    import com.atgui.springcloud.service.OrderService;
    import com.atgui.springcloud.service.StorageService;
    import io.seata.spring.annotation.GlobalTransactional;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
         
         
    
        @Resource
        private OrderDao dao;
    
        @Resource
        private StorageService storageService;
    
        @Resource
        private AccountService accountService;
    
        @Override
        @GlobalTransactional(name = "order-create",rollbackFor = Exception.class)
        public void create(Order order) {
         
         
            //1.创建订单
            log.info("--------开始创建订单---------");
            dao.create(order);
            //2.调用库存服务,扣减数量
            log.info("--------订单微服务调用库存微服务,扣减数量---------");
            storageService.decrease(order.getProductId(),order.getCount());
            //3.调用账户服务,扣减money
            log.info("--------订单微服务调用账户微服务,扣减钱---------");
            accountService.decrease(order.getUserId(),order.getMoney());
            //4.修改订单状态
            log.info("--------修改订单状态开始---------");
            dao.update(order.getUserId(),0);
            log.info("--------修改订单状态结束---------");
    
            log.info("--------下订单结束---------");
        }
    
    }
    
    

4.7 Seata补充

4.7.1 执行流程
  1. TM开启分布式事务(TM向TC注册全局事务记录);
  2. 按业务场景,编排数据库、服务等事务内资源(RM向TC会报资源准备状态);
  3. TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务);
  4. TC汇总事务信息,决定分布式事务是提交还是回滚;
  5. TC通知所有RM提交/回滚资源,事务二阶段结束.
4.7.2 AT模式如何做到对业务的无侵入

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

  1. AT模式

    1. 前提
      • 基于本地ACID事务的关系型数据库
      • Java应用,通过JDBC访问数据库
    2. 整体机制(两阶段提交协议的演变)
      • 一阶段: 业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源.
      • 二阶段:
        • 提交异步化,非常快速地完成.
        • 回滚通过一阶段的回滚日志进行反向补偿.
  2. 一阶段加载

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

    1 . 解析SQL语义,找到"业务SQL"要更新的业务数据,在业务数据被更新前,将其保存成"before image",

    2 . 执行"业务SQL"更新业务数据,在业务数据更新之后,

    3 . 将其保存成"after image",最后生成行锁.

    以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性.

    images\image-20201230153317262.png)]

  3. 二阶段提交

    二阶段如果顺利提交的话,

    因为"业务SQL"在一阶段已经提交至数据库,所以Seata只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可.

    images\image-20201230153605200.png)]

  4. 二阶段回滚

    二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的"业务SQL",还原业务数据.

    回滚方式便是用"before image" 还原业务数据;但在还原前要首先要校验脏写,对比"数据库当前业务数据"和"after image",

    如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理.

    images\image-20201230154227673.png)]

  5. 补充

mages\image-20201230160139619.png)]

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!