• SpringCloud Alibaba学习笔记,记重点!!


    SpringCloud Alibaba入门简介

    Spring Cloud Netflix 项目进入维护模式,Spring Cloud Netflix 将不再开发新的组件。Spring Cloud 版本迭代算是比较快的,因而出现了很多重大 ISSUE 都还来不及 Fix 就又推另一个 Release 了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主,新组件功能将以其他替代平代替的方式实现。基于该背景下,诞生了 SpringCloud Alibaba.

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

    SpringCloud Alibaba 特性

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

    Spring Alibaba核心组件

    官网:Spring Cloud Alibaba

    英文文档:Spring Cloud Alibaba Reference Documentation

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

    Nacos服务注册和配置中心

    Nacos简介

    官网:home

    GitHub:https://github.com/alibaba/Nacos

    开发手册:Spring Cloud Alibaba Reference Documentation

    Nacos,全称 Dynamic Naming and Configuration Service,Nacos = Eureka+Config +Bus,能够替代 Eureka 做服务注册中心和 Config 做服务配置中心。

    各类注册中心比较

    服务注册与发现框架CAP模型控制台管理社区活跃度
    EurakaAP支持低(2.x 闭源)
    ZookeeperCP不支持
    ConsulCP支持
    NacosAP支持

    Nacos安装与运行

    1. 准备 Java8 + Maven 环境
    2. 下载 Nocas:https://github.com/alibaba/nacos/releases
    3. 解压安装包,直接运行 bin 目录下的 startup.cmd
    startup.cmd -m standalone
    
    1. 命令运行成功后直接访问:http://localhost:8848/nacos

    Nacos服务注册中心

    基于Nacos的服务提供者

    1. 新建 module:cloudalibaba-provider-payment9001

    2. 改 POM

      • 修改父 POM,添加以下依赖
      1. <dependency>
      2. <groupId>com.alibaba.cloud</groupId>
      3. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
      4. <version>2.1.0.RELEASE</version>
      5. <type>pom</type>
      6. <scope>import</scope>
      7. </dependency>
      • 本模块 POM
      1. <dependencies>
      2. <!--SpringCloud ailibaba nacos -->
      3. <dependency>
      4. <groupId>com.alibaba.cloud</groupId>
      5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      6. </dependency>
      7. <!-- SpringBoot整合Web组件 -->
      8. <dependency>
      9. <groupId>org.springframework.boot</groupId>
      10. <artifactId>spring-boot-starter-web</artifactId>
      11. </dependency>
      12. <dependency>
      13. <groupId>org.springframework.boot</groupId>
      14. <artifactId>spring-boot-starter-actuator</artifactId>
      15. </dependency>
      16. <!--日常通用jar包配置-->
      17. <dependency>
      18. <groupId>org.springframework.boot</groupId>
      19. <artifactId>spring-boot-devtools</artifactId>
      20. <scope>runtime</scope>
      21. <optional>true</optional>
      22. </dependency>
      23. <dependency>
      24. <groupId>org.projectlombok</groupId>
      25. <artifactId>lombok</artifactId>
      26. <optional>true</optional>
      27. </dependency>
      28. <dependency>
      29. <groupId>org.springframework.boot</groupId>
      30. <artifactId>spring-boot-starter-test</artifactId>
      31. <scope>test</scope>
      32. </dependency>
      33. </dependencies>
    3. 写 YML

    1. server:
    2. port: 9001
    3. spring:
    4. application:
    5. name: nacos-payment-provider
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: localhost:8848 #配置Nacos地址
    10. management:
    11. endpoints:
    12. web:
    13. exposure:
    14. include: '*'
    1. 主启动
    1. @EnableDiscoveryClient
    2. @SpringBootApplication
    3. public class PaymentMain9001
    4. {
    5. public static void main(String[] args) {
    6. SpringApplication.run(PaymentMain9001.class, args);
    7. }
    8. }
    1. 业务类
    1. @RestController
    2. public class PaymentController
    3. {
    4. @Value("${server.port}")
    5. private String serverPort;
    6. @GetMapping(value = "/payment/nacos/{id}")
    7. public String getPayment(@PathVariable("id") Integer id)
    8. {
    9. return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
    10. }
    11. }
    1. 测试

    基于Nacos的服务消费者

    1. 根据 cloudalibaba-provider-payment9001 新建 cloudalibaba-provider-payment9002 演示负载均衡。
    2. 新建 module:cloudalibaba-consumer-nacos-order83
    3. 改 POM
    1. <dependencies>
    2. <!--SpringCloud ailibaba nacos -->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    6. </dependency>
    7. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    8. <dependency>
    9. <groupId>com.xiaobai.springcloud</groupId>
    10. <artifactId>cloud-api-commons</artifactId>
    11. <version>${project.version}</version>
    12. </dependency>
    13. <!-- SpringBoot整合Web组件 -->
    14. <dependency>
    15. <groupId>org.springframework.boot</groupId>
    16. <artifactId>spring-boot-starter-web</artifactId>
    17. </dependency>
    18. <dependency>
    19. <groupId>org.springframework.boot</groupId>
    20. <artifactId>spring-boot-starter-actuator</artifactId>
    21. </dependency>
    22. <!--日常通用jar包配置-->
    23. <dependency>
    24. <groupId>org.springframework.boot</groupId>
    25. <artifactId>spring-boot-devtools</artifactId>
    26. <scope>runtime</scope>
    27. <optional>true</optional>
    28. </dependency>
    29. <dependency>
    30. <groupId>org.projectlombok</groupId>
    31. <artifactId>lombok</artifactId>
    32. <optional>true</optional>
    33. </dependency>
    34. <dependency>
    35. <groupId>org.springframework.boot</groupId>
    36. <artifactId>spring-boot-starter-test</artifactId>
    37. <scope>test</scope>
    38. </dependency>
    39. </dependencies>
    1. 写 YML
    1. server:
    2. port: 83
    3. spring:
    4. application:
    5. name: nacos-order-consumer
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: localhost:8848
    10. #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
    11. service-url:
    12. nacos-user-service: http://nacos-payment-provider
    1. 主启动
    1. @EnableDiscoveryClient
    2. @SpringBootApplication
    3. public class OrderNacosMain83
    4. {
    5. public static void main(String[] args)
    6. {
    7. SpringApplication.run(OrderNacosMain83.class,args);
    8. }
    9. }
    1. 业务类

      • config
      1. @Configuration
      2. public class ApplicationContextBean
      3. {
      4. @Bean
      5. @LoadBalanced
      6. public RestTemplate getRestTemplate()
      7. {
      8. return new RestTemplate();
      9. }
      10. }
      • OrderNacosController
      1. @RestController
      2. public class OrderNacosController
      3. {
      4. @Resource
      5. private RestTemplate restTemplate;
      6. @Value("${service-url.nacos-user-service}")
      7. private String serverURL;
      8. @GetMapping("/consumer/payment/nacos/{id}")
      9. public String paymentInfo(@PathVariable("id") Long id)
      10. {
      11. return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
      12. }
      13. }
    2. 测试

    为什么 Nocas 支持负载轮询? Nocas 中内置了 Ribbon !

    服务注册中心对比

    Nocas 全景图

    Nacos与其他注册中心特性对比

    NacosEurakaConsulCoreDNSZooKeeper
    一致性协议CP + APAPCP/CP
    健康检查TCP/HTTP/MySQL/Client BeatClient BeatTCP/HTTP/GRPC/Cmd/Client Beat
    负载均衡权重/DSL/metadata/CMDBRibbonFabioRR/
    雪崩保护支持支持不支持不支持不支持
    自动注销支持支持不支持不支持不支持
    访问协议HTTP/DNS/UDPHTTPHTTP/DNSDNSTCP
    监听支持支持支持支持不支持支持
    多数据中心支持支持支持不支持不支持
    跨注册中心支持不支持支持不支持不支持
    SpringCloud集成支持不支持不支持不支持支持
    Dubbon集成支持不支持不支持不支持支持
    K8s集成支持不支持支持支持不支持

    Nacos 服务发现实例模型

    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'
    

    Nacos服务配置中心

    基础配置

    1. 新建 module:cloudalibaba-config-nacos-client3377
    2. 改 POM
    1. <dependencies>
    2. <!--nacos-config-->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    6. </dependency>
    7. <!--nacos-discovery-->
    8. <dependency>
    9. <groupId>com.alibaba.cloud</groupId>
    10. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    11. </dependency>
    12. <!--web + actuator-->
    13. <dependency>
    14. <groupId>org.springframework.boot</groupId>
    15. <artifactId>spring-boot-starter-web</artifactId>
    16. </dependency>
    17. <dependency>
    18. <groupId>org.springframework.boot</groupId>
    19. <artifactId>spring-boot-starter-actuator</artifactId>
    20. </dependency>
    21. <!--一般基础配置-->
    22. <dependency>
    23. <groupId>org.springframework.boot</groupId>
    24. <artifactId>spring-boot-devtools</artifactId>
    25. <scope>runtime</scope>
    26. <optional>true</optional>
    27. </dependency>
    28. <dependency>
    29. <groupId>org.projectlombok</groupId>
    30. <artifactId>lombok</artifactId>
    31. <optional>true</optional>
    32. </dependency>
    33. <dependency>
    34. <groupId>org.springframework.boot</groupId>
    35. <artifactId>spring-boot-starter-test</artifactId>
    36. <scope>test</scope>
    37. </dependency>
    38. </dependencies>
    1. 写 YML

      • bootstrap
      1. # nacos配置
      2. server:
      3. port: 3377
      4. spring:
      5. application:
      6. name: nacos-config-client
      7. cloud:
      8. nacos:
      9. discovery:
      10. server-addr: localhost:8848 #Nacos服务注册中心地址
      11. config:
      12. server-addr: localhost:8848 #Nacos作为配置中心地址
      13. file-extension: yaml #指定yaml格式的配置
      14. # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
      • application
      1. spring:
      2. profiles:
      3. active: dev # 表示开发环境
    2. 主启动

    1. @EnableDiscoveryClient
    2. @SpringBootApplication
    3. public class NacosConfigClientMain3377
    4. {
    5. public static void main(String[] args) {
    6. SpringApplication.run(NacosConfigClientMain3377.class, args);
    7. }
    8. }
    1. 业务类

      • controller
      1. @RestController //通过Spring Cloud原生注解@RefreshScope实现配置自动更新
      2. @RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
      3. public class ConfigClientController
      4. {
      5. @Value("${config.info}")
      6. private String configInfo;
      7. @GetMapping("/config/info")
      8. public String getConfigInfo() {
      9. return configInfo;
      10. }
      11. }
    2. 在 Nacos 中添加配置信息

      Nacos中的匹配规则:Nacos Spring Cloud 快速开始

      设置DataId:spring.application.name−spring.application.name−{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

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

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

    1. 测试
      • 启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件;
      • 运行cloud-config-nacos-client3377的主启动类;
      • 调用接口查看配置信息:http://localhost:3377/config/info
      • 修改下 Nacos 中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新

    分类配置

    多环境多项目管理中面临的问题:

    1. 实际开发中,通常一个系统会准备 dev开发环境、test测试环境、prod生产环境。如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
    2. 一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境......
      那怎么对这些微服务配置进行管理呢?

    Nacos 的图形化管理界面

    Namespace+Group+Data ID三者关系

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

    三种方案加载配置

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

      1. 新建 dev 配置 DataID

      1. 同上,新建 test 配置 DataID
      2. 通过spring.profile.active属性就能进行多环境下配置文件的读取

      1. 测试,访问:http://localhost:3377/config/info,返回配置内容
    • Group方案:通过Group实现环境区分

      1. 新建 Group,在 nacos 图形界面控制台上面新建配置文件DataID

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

      1. 测试,访问:http://localhost:3377/config/info,返回配置内容
    • Namespace方案

      1. 新建dev/test的Namespace

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

      1. 按照域名配置填写

      1. 修改 YML

        • bootstrap:config 添加 namespace 配置
        1. config:
        2. server-addr: localhost:8848 #Nacos作为配置中心地址
        3. file-extension: yaml #这里我们获取的yaml格式的配置
        4. namespace: 5da1dccc-ee26-49e0-b8e5-7d9559b95ab0
        5. #group: DEV_GROUP
        6. group: TEST_GROUP
        • application
        1. # Nacos注册配置,application.yml
        2. spring:
        3. profiles:
        4. #active: test
        5. active: dev
        6. #active: info
      2. 测试,访问:http://localhost:3377/config/info,返回配置内容

    Nacos集群和持久化配置

    官网说明

    官方文档:集群部署说明

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

    查看官网文档说明:Nacos支持三种部署模式

    Nacos持久化配置

    Nacos默认自带的是嵌入式数据库derby,说明:https://github.com/alibaba/nacos/blob/develop/config/pom.xml

    切换配置 MySQL 步骤

    1. 新建 nacos 数据库,在 nacos-server-1.1.4\nacos\conf 目录下找到 sql 脚本,执行:
    1. create database nacos;
    2. use nacos;
    3. source D:\Devware\nacos-server-2.1.0-BETA\conf\nacos-mysql.sql
    1. nacos-server-1.1.4\nacos\conf 目录下找到 application.properties,修改数据库配置
    1. #*************** Config Module Related Configurations ***************#
    2. ### If use MySQL as datasource:
    3. spring.datasource.platform=mysql
    4. ### Count of DB:
    5. db.num=1
    6. ### Connect URL of DB:
    7. db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
    8. db.user.0=root
    9. db.password.0=root
    1. 启动Nacos,可以看到是个全新的空记录界面,以前是记录进derby

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

    1. 环境准备

      tar -xzvf /opt/ nacos-server-1.1.4.tar.gz
      
    2. 集群配置

      • 新建 nacos 数据库,将 nacos 安装目录里的 nacos-mysql.sql 导入到 mysql 数据库中
      1. create database nacos;
      2. use nacos;
      3. source /opt/nacos/confnacos-mysql.sql
      • 修改 application.properties 配置
      1. cp application.properties.example application.properties
      2. vim application.properties

      • Linux服务器上nacos的集群配置 cluster.conf
      1. cp cluster.conf.example cluster.conf
      2. vim cluster.conf

      • 编辑Nacos的启动脚本 startup.sh,使它能够接受不同的启动端口
      1. cd /opt/nacos/bin
      2. vim startup.sh

      1. #执行方式
      2. ./startup.sh -p 3333
      3. ./startup.sh -p 4444
      • Nginx的配置,由它作为负载均衡器
      vim /usr/local/nginx/conf
      

      配置内容

      1. upstream cluster{
      2. server 127.0.0.1:3333;
      3. server 127.0.0.1:4444;
      4. server 127.0.0.1:5555;
      5. }
      6. server {
      7. listen 1111;
      8. server_name localhost;
      9. #charset koi8-r;
      10. #access_log logs/host.access.log main;
      11. location / {
      12. #root html;
      13. #index index.html index.htm;
      14. proxy_pass http://cluster;
      15. }
      16. ......

      启动 nginx,执行: ./nginx -C /usr/local/nginx/conf/nginx.conf

    3. 测试

      • 修改 cloudablibaba-provider-payment9002 yml
      server-addr: 192.168.111.144:1111
      
      • 微服务 cloudalibaba-provider-payment9002 启动,注册进 nacos 集群

    Sentinel实现熔断与限流

    Sentinel简介

    GitHub:https://github.com/alibaba/Sentinel

    Sentinel 是一款功能强大的流量控制组件,以 flow 为突破点,覆盖流量控制、并发限制、断路、自适应系统保护等多个领域,保障微服务的可靠性。

    使用手册:Spring Cloud Alibaba Reference Documentation

    Sentinel安装

    Sentinel 分为两个部分:

    • 核心库(Uava客户端):不依赖任何框架/库,能够运行于所有ava运行时环境,同时对 Dubbo/Spring Cloud 等框架也有较好的支特。
    • 控制台(Dashboard):基于Spring Boot开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
    1. 下载:https://github.com/alibaba/Sentinel/releases
    2. 保证 Java8 环境且 8080 端口不被占用,运行 java -jar sentinel-dashboard-1.7.0.jar
    3. 访问sentinel管理界面:http://localhost:8080 ,登录账号密码均为 sentinel

    初始化工程

    1. 新建 module,cloudalibaba-sentinel-service8401
    2. 改 POM
    1. <dependencies>
    2. <!--SpringCloud ailibaba nacos -->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    6. </dependency>
    7. <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
    8. <dependency>
    9. <groupId>com.alibaba.csp</groupId>
    10. <artifactId>sentinel-datasource-nacos</artifactId>
    11. </dependency>
    12. <!--SpringCloud ailibaba sentinel -->
    13. <dependency>
    14. <groupId>com.alibaba.cloud</groupId>
    15. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    16. </dependency>
    17. <!--openfeign-->
    18. <dependency>
    19. <groupId>org.springframework.cloud</groupId>
    20. <artifactId>spring-cloud-starter-openfeign</artifactId>
    21. </dependency>
    22. <!-- SpringBoot整合Web组件+actuator -->
    23. <dependency>
    24. <groupId>org.springframework.boot</groupId>
    25. <artifactId>spring-boot-starter-web</artifactId>
    26. </dependency>
    27. <dependency>
    28. <groupId>org.springframework.boot</groupId>
    29. <artifactId>spring-boot-starter-actuator</artifactId>
    30. </dependency>
    31. <!--日常通用jar包配置-->
    32. <dependency>
    33. <groupId>org.springframework.boot</groupId>
    34. <artifactId>spring-boot-devtools</artifactId>
    35. <scope>runtime</scope>
    36. <optional>true</optional>
    37. </dependency>
    38. <dependency>
    39. <groupId>cn.hutool</groupId>
    40. <artifactId>hutool-all</artifactId>
    41. <version>4.6.3</version>
    42. </dependency>
    43. <dependency>
    44. <groupId>org.projectlombok</groupId>
    45. <artifactId>lombok</artifactId>
    46. <optional>true</optional>
    47. </dependency>
    48. <dependency>
    49. <groupId>org.springframework.boot</groupId>
    50. <artifactId>spring-boot-starter-test</artifactId>
    51. <scope>test</scope>
    52. </dependency>
    53. </dependencies>
    1. 写 YML
    1. server:
    2. port: 8401
    3. spring:
    4. application:
    5. name: cloudalibaba-sentinel-service
    6. cloud:
    7. nacos:
    8. discovery:
    9. #Nacos服务注册中心地址
    10. server-addr: localhost:8848
    11. sentinel:
    12. transport:
    13. #配置Sentinel dashboard地址
    14. dashboard: localhost:8080
    15. #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
    16. port: 8719
    17. management:
    18. endpoints:
    19. web:
    20. exposure:
    21. include: '*'
    1. 主启动
    1. @EnableDiscoveryClient
    2. @SpringBootApplication
    3. public class MainApp8401
    4. {
    5. public static void main(String[] args) {
    6. SpringApplication.run(MainApp8401.class, args);
    7. }
    8. }
    1. controller
    1. @RestController
    2. @Log4j2
    3. public class FlowLimitController
    4. {
    5. @GetMapping("/testA")
    6. public String testA()
    7. {
    8. return "------testA";
    9. }
    10. @GetMapping("/testB")
    11. public String testB()
    12. {
    13. return "------testB";
    14. }
    15. }
    1. 测试

    Sentinel 采用的懒加载方式,只有在微服务被访问之后 Sentienl 才进行监测。

    流控规则

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

    流量模式

    • 直接模式(默认):直接->快速失败

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

    ​ 快速点击访问:http://localhost:8401/testA,结果 Blocked by Sentinel (flow limiting)

    • 关联模式:当关联的资源达到阈值时,就限流自己

    postman 模拟并发密集访问 testB,大批量线程高并发访问B,导致A失效了

    • 链路模式:多个请求调用了同一个微服务

    流控效果

    • 默认的流控处理:直接->快速失败

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

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

    文档:https://github.com/alibaba/Sentinel/wiki/流量控制

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

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

    测试:多次点击:http://localhost:8401/testB,刚开始不行,后续慢慢OK

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

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

    官网:https://github.com/alibaba/Sentinel/wiki/流量控制

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

    测试

    降级规则

    官网:https://github.com/alibaba/Sentinel/wiki/熔断降级

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

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

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

    降级策略实战

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

      • 代码
      1. @GetMapping("/testD")
      2. public String testD()
      3. {
      4. //暂停几秒钟线程
      5. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
      6. log.info("testD 测试RT");
      7. return "------testD";
      8. }
      • 配置

      • jmeter压测

      • 结论

        永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了。后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK。

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

      • 代码
      1. @GetMapping("/testD")
      2. public String testD()
      3. {
      4. log.info("testD 测试RT");
      5. int age = 10/0;
      6. return "------testD";
      7. }
      • 配置

      • jmeter压测

      • 结论

        开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。

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

      • 代码
      1. @GetMapping("/testE")
      2. public String testE()
      3. {
      4. log.info("testE 测试异常比例");
      5. int age = 10/0;
      6. return "------testE 测试异常比例";
      7. }
      • 配置

      http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级。

      • jmeter压测

    热点key限流

    官网:https://github.com/alibaba/Sentinel/wiki/热点参数限流

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

    @SentinelResource

    1. @GetMapping("/testHotKey")
    2. @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
    3. public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
    4. @RequestParam(value = "p2",required = false) String p2){
    5. return "------testHotKey";
    6. }
    7. public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
    8. {
    9. return "-----dealHandler_testHotKey";
    10. }

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

    测试:

    参数例外项

    我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样。假如当p1的值等于5时,它的阈值可以达到200

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

    系统规则

    官网:https://github.com/alibaba/Sentinel/wiki/系统自适应限流

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

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

    系统规则支持以下的模式:

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

    @SentinelResource

    按资源名限流+后续处理

    启动 Nacos,执行:startup.cmd -m standalone,访问:http://localhost:8848/nacos/#/login 测试

    启动 Sentinel,执行:java -jar sentinel-dashboard-1.7.0.jar

    修改 cloudablibaba-sentinel-service8401

    1. 改 POM,添加以依赖
    1. <dependencies>
    2. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    3. <groupId>com.xiaobai.springcloud</groupId>
    4. <artifactId>cloud-api-commons</artifactId>
    5. <version>${project.version}</version>
    6. </dependency>
    7. </dependencies>
    1. controller,新建 RateLimitController
    1. @RestController
    2. public class RateLimitController
    3. {
    4. @GetMapping("/byResource")
    5. @SentinelResource(value = "byResource",blockHandler = "handleException")
    6. public CommonResult byResource()
    7. {
    8. return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
    9. }
    10. public CommonResult handleException(BlockException exception)
    11. {
    12. return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    13. }
    14. }
    1. 流控规则配置

      • 配置步骤

      • 图形配置与代码关系

    2. 测试,启动 8401,访问:http://localhost:8401/byResource,间隔时间大于等于 1s 访问成功,间隔时间小于 1s 返回自定义的限流处理信息。

    遗留问题: 当 8401 服务关闭,Sentinel 流量控制规则消失。

    按Url地址限流+后续处理

    1. 修改 controller,添加如下方法
    1. @GetMapping("/rateLimit/byUrl")
    2. @SentinelResource(value = "byUrl")
    3. public CommonResult byUrl()
    4. {
    5. return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
    6. }
    1. 初步测试,访问:http://localhost:8401/rateLimit/byUrl,访问成功
    2. 配置流控规则

    1. 二次测试,连续访问:http://localhost:8401/rateLimit/byUrl,返回 Sentinel 自带的限流处理结果

    上述兜底方案面临的问题:

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

    客户自定义限流处理逻辑

    1. 创建 CustomerBlockHandler 类用于自定义限流处理逻辑
    1. public class CustomerBlockHandler
    2. {
    3. public static CommonResult handleException(BlockException exception){
    4. return new CommonResult(2020,"自定义的限流处理信息......CustomerBlockHandler-----1");
    5. }
    6. public static CommonResult handleException2(BlockException exception){
    7. return new CommonResult(2020,"自定义的限流处理信息......CustomerBlockHandler------2");
    8. }
    9. }
    1. RateLimitController 添加自定义限流处理逻辑
    1. /**
    2. * 自定义通用的限流处理逻辑,
    3. * blockHandlerClass = CustomerBlockHandler.class
    4. * blockHandler = handleException2
    5. * 上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
    6. */
    7. /**
    8. * 自定义通用的限流处理逻辑
    9. */
    10. @GetMapping("/rateLimit/customerBlockHandler")
    11. @SentinelResource(value = "customerBlockHandler",
    12. blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
    13. public CommonResult customerBlockHandler()
    14. {
    15. return new CommonResult(200,"按客户自定义限流处理逻辑");
    16. }
    1. 启动微服务后调用:http://localhost:8401/rateLimit/customerBlockHandler
    2. Sentinel 控制台添加配置

    1. 再次调用:http://localhost:8401/rateLimit/customerBlockHandler

    服务熔断

    Ribbon系列

    服务提供者9003/9004

    1. 新建cloudalibaba-provider-payment9003/9004
    2. 改 POM
    1. <dependencies>
    2. <!--SpringCloud ailibaba nacos -->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    6. </dependency>
    7. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    8. <groupId>com.xiaobai.springcloud</groupId>
    9. <artifactId>cloud-api-commons</artifactId>
    10. <version>${project.version}</version>
    11. </dependency>
    12. <!-- SpringBoot整合Web组件 -->
    13. <dependency>
    14. <groupId>org.springframework.boot</groupId>
    15. <artifactId>spring-boot-starter-web</artifactId>
    16. </dependency>
    17. <dependency>
    18. <groupId>org.springframework.boot</groupId>
    19. <artifactId>spring-boot-starter-actuator</artifactId>
    20. </dependency>
    21. <!--日常通用jar包配置-->
    22. <dependency>
    23. <groupId>org.springframework.boot</groupId>
    24. <artifactId>spring-boot-devtools</artifactId>
    25. <scope>runtime</scope>
    26. <optional>true</optional>
    27. </dependency>
    28. <dependency>
    29. <groupId>org.projectlombok</groupId>
    30. <artifactId>lombok</artifactId>
    31. <optional>true</optional>
    32. </dependency>
    33. <dependency>
    34. <groupId>org.springframework.boot</groupId>
    35. <artifactId>spring-boot-starter-test</artifactId>
    36. <scope>test</scope>
    37. </dependency>
    38. </dependencies>
    1. 写 YML(记得该端口号)
    1. server:
    2. port: 9003
    3. spring:
    4. application:
    5. name: nacos-payment-provider
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: localhost:8848 #配置Nacos地址
    10. management:
    11. endpoints:
    12. web:
    13. exposure:
    14. include: '*'
    1. 主启动
    1. @SpringBootApplication
    2. @EnableDiscoveryClient
    3. public class PaymentMain9003
    4. {
    5. public static void main(String[] args) {
    6. SpringApplication.run(PaymentMain9003.class, args);
    7. }
    8. }
    1. 业务类
    1. @RestController
    2. public class PaymentController
    3. {
    4. @Value("${server.port}")
    5. private String serverPort;
    6. public static HashMap<Long,Payment> hashMap = new HashMap<>();
    7. static
    8. {
    9. hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
    10. hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
    11. hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
    12. }
    13. @GetMapping(value = "/paymentSQL/{id}")
    14. public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
    15. {
    16. Payment payment = hashMap.get(id);
    17. CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
    18. return result;
    19. }
    20. }
    1. 测试,访问:http://localhost:9003/paymentSQL/1

    服务消费者84

    1. 新建 cloudalibaba-consumer-nacos-order84
    2. 改 POM
    1. <dependencies>
    2. <!--SpringCloud ailibaba nacos -->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    6. </dependency>
    7. <!--SpringCloud ailibaba sentinel -->
    8. <dependency>
    9. <groupId>com.alibaba.cloud</groupId>
    10. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    11. </dependency>
    12. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    13. <dependency>
    14. <groupId>com.xiaobai.springcloud</groupId>
    15. <artifactId>cloud-api-commons</artifactId>
    16. <version>${project.version}</version>
    17. </dependency>
    18. <!-- SpringBoot整合Web组件 -->
    19. <dependency>
    20. <groupId>org.springframework.boot</groupId>
    21. <artifactId>spring-boot-starter-web</artifactId>
    22. </dependency>
    23. <dependency>
    24. <groupId>org.springframework.boot</groupId>
    25. <artifactId>spring-boot-starter-actuator</artifactId>
    26. </dependency>
    27. <!--日常通用jar包配置-->
    28. <dependency>
    29. <groupId>org.springframework.boot</groupId>
    30. <artifactId>spring-boot-devtools</artifactId>
    31. <scope>runtime</scope>
    32. <optional>true</optional>
    33. </dependency>
    34. <dependency>
    35. <groupId>org.projectlombok</groupId>
    36. <artifactId>lombok</artifactId>
    37. <optional>true</optional>
    38. </dependency>
    39. <dependency>
    40. <groupId>org.springframework.boot</groupId>
    41. <artifactId>spring-boot-starter-test</artifactId>
    42. <scope>test</scope>
    43. </dependency>
    44. </dependencies>
    1. 写 YML
    1. server:
    2. port: 84
    3. spring:
    4. application:
    5. name: nacos-order-consumer
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: localhost:8848
    10. sentinel:
    11. transport:
    12. #配置Sentinel dashboard地址
    13. dashboard: localhost:8080
    14. #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
    15. port: 8719
    16. #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
    17. service-url:
    18. nacos-user-service: http://nacos-payment-provider
    1. 主启动
    1. @EnableDiscoveryClient
    2. @SpringBootApplication
    3. public class OrderNacosMain84
    4. {
    5. public static void main(String[] args) {
    6. SpringApplication.run(OrderNacosMain84.class, args);
    7. }
    8. }
    1. 配置类
    1. @Configuration
    2. public class ApplicationContextConfig
    3. {
    4. @Bean
    5. @LoadBalanced
    6. public RestTemplate getRestTemplate()
    7. {
    8. return new RestTemplate();
    9. }
    10. }
    1. 业务类
    1. @RestController
    2. @Slf4j
    3. public class CircleBreakerController
    4. {
    5. public static final String SERVICE_URL = "http://nacos-payment-provider";
    6. @Resource
    7. private RestTemplate restTemplate;
    8. @RequestMapping("/consumer/fallback/{id}")
    9. @SentinelResource(value = "fallback")
    10. public CommonResult fallback(@PathVariable Long id)
    11. {
    12. CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
    13. if (id == 4) {
    14. throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
    15. }else if (result.getData() == null) {
    16. throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
    17. }
    18. return result;
    19. }
    20. }
    1. 测试:http://localhost:84/consumer/fallback/4,直接给客户展示 error 页面,不友好

    添加 fallback 配置

    1. 修改 CircleBreakerController,添加 fallback 配置
    1. @RequestMapping("/consumer/fallback/{id}")
    2. @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
    3. public CommonResult<Payment> fallback(@PathVariable Long id)
    4. {
    5. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
    6. if (id == 4) {
    7. throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
    8. }else if (result.getData() == null) {
    9. throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
    10. }
    11. return result;
    12. }
    13. public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
    14. Payment payment = new Payment(id,"null");
    15. return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
    16. }

    1. 测试

    添加 blockHandler

    1. 修改 CircleBreakerController,添加 fallback 配置
    1. @RequestMapping("/consumer/fallback/{id}")
    2. @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
    3. public CommonResult<Payment> fallback(@PathVariable Long id)
    4. {
    5. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
    6. if (id == 4) {
    7. throw new IllegalArgumentException ("非法参数异常....");
    8. }else if (result.getData() == null) {
    9. throw new NullPointerException ("NullPointerException,该ID没有对应记录");
    10. }
    11. return result;
    12. }
    13. public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
    14. Payment payment = new Payment(id,"null");
    15. return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
    16. }

    1. Sentinel 配置

    1. 测试

    添加 fallback 和 blockHandler 配置

    1. 修改 CircleBreakerController,添加 fallback 和 blockHandler 配置
    1. @RequestMapping("/consumer/fallback/{id}")
    2. @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
    3. public CommonResult<Payment> fallback(@PathVariable Long id)
    4. {
    5. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
    6. if (id == 4) {
    7. throw new IllegalArgumentException ("非法参数异常....");
    8. }else if (result.getData() == null) {
    9. throw new NullPointerException ("NullPointerException,该ID没有对应记录");
    10. }
    11. return result;
    12. }
    13. public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
    14. Payment payment = new Payment(id,"null");
    15. return new CommonResult<>(444,"fallback,无此流水,exception "+e.getMessage(),payment);
    16. }
    17. public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
    18. Payment payment = new Payment(id,"null");
    19. return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
    20. }

    1. Sentinel 配置

    1. 测试

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

    忽略属性...

    1. 修改 controller
    1. @RequestMapping("/consumer/fallback/{id}")
    2. @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler",
    3. exceptionsToIgnore = {IllegalArgumentException.class})
    4. public CommonResult<Payment> fallback(@PathVariable Long id)
    5. {
    6. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
    7. if (id == 4) {
    8. throw new IllegalArgumentException ("非法参数异常....");
    9. }else if (result.getData() == null) {
    10. throw new NullPointerException ("NullPointerException,该ID没有对应记录");
    11. }
    12. return result;
    13. }

    Feign系列

    修改84模块

    1. 修改 POM,添加依赖
    1. <!--SpringCloud openfeign -->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-openfeign</artifactId>
    5. </dependency>
    1. 修改 YML,添加配置
    1. # 激活Sentinel对Feign的支持
    2. feign:
    3. sentinel:
    4. enabled: true
    1. 业务类

      • 带 @FeignClient 注解的业务接口
      1. /** 使用 fallback 方式是无法获取异常信息的,
      2. * 如果想要获取异常信息,可以使用 fallbackFactory参数
      3. */
      4. @FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)//调用中关闭9003服务提供者
      5. public interface PaymentService
      6. {
      7. @GetMapping(value = "/paymentSQL/{id}")
      8. public CommonResult paymentSQL(@PathVariable("id") Long id);
      9. }
      • 添加 fallback 类
      1. @Component
      2. public class PaymentFallbackService implements PaymentService
      3. {
      4. @Override
      5. public CommonResult<Payment> paymentSQL(Long id)
      6. {
      7. return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "errorSerial......"));
      8. }
      9. }
    2. controller

    1. //==================OpenFeign
    2. @Resource
    3. private PaymentService paymentService;
    4. @GetMapping(value = "/consumer/paymentSQL/{id}")
    5. public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
    6. {
    7. if(id == 4)
    8. {
    9. throw new RuntimeException("没有该id");
    10. }
    11. return paymentService.paymentSQL(id);
    12. }
    1. 主启动,添加 @EnableFeignClients 启动 Feign 的功能
    2. 测试,访问:http://localhost:84/consumer/paymentSQL/1,故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死

    熔断框架比较

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

    规则持久化

    1. 修改 cloudalibaba-sentinel-service8401
    2. 修改 POM,添加依赖
    1. <!--SpringCloud ailibaba sentinel-datasource-nacos -->
    2. <dependency>
    3. <groupId>com.alibaba.csp</groupId>
    4. <artifactId>sentinel-datasource-nacos</artifactId>
    5. </dependency>
    1. 修改 YML,添加 Nacos 数据源配置
    1. sentinel:
    2. datasource:
    3. ds1:
    4. nacos:
    5. server-addr: localhost:8848
    6. dataId: cloudalibaba-sentinel-service
    7. groupId: DEFAULT_GROUP
    8. data-type: json
    9. rule-type: flow
    1. 添加 Nacos 业务规则配置

    1. [
    2. {
    3. "resource": "/rateLimit/byUrl",
    4. "limitApp": "default",
    5. "grade": 1,
    6. "count": 1,
    7. "strategy": 0,
    8. "controlBehavior": 0,
    9. "clusterMode": false
    10. }
    11. ]
    • resource:资源名称;
    • limitApp:来源应用;
    • grade:阈值类型,0表示线程数,1表示QPS;
    • count:单机阈值;
    • strategy:流控模式,0表示直接,1表示关联,2表示链路;
    • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
    • clusterMode:是否集群。
    1. 启动8401后刷新 sentinel 发现业务规则有了

    1. 快速访问测试接口:http://localhost:8401/rateLimit/byUrl,返回限流信息
    2. 停止 8401 再看 sentinel

    1. 重新启动 8401 再看 sentinel,调用:http://localhost:8401/rateLimit/byUrl,配置重新出现了,持久化验证通过

    Seata处理分布式事务

    分布式事务问题

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

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

    Seata简介

    官网:Seata 官网

    下载:https://github.com/seata/seata/releases

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

    一个典型的分布式事务过程由一个事务ID + 三个组件组成。三个组件包括:

    1. Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
    2. Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
    3. Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

    处理过程:

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

    Seata 只需两个注解:本地@Transactional 和 全局@GlobalTransactional 即可实现分布式事务控制。

    Seata-Server安装

    1. 下载:https://github.com/seata/seata/releases,课程版本下载的是seata-server-0.9.0.zip

    2. seata-server-0.9.0.zip 解压到指定目录并修改 conf 目录下的 file.conf 配置文件

      • 备份 file.conf 文件

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

        service 模块

        1. service {
        2. vgroup_mapping.my_test_tx_group = "fsp_tx_group"
        3. }

        store 模块

        1. ## transaction log store
        2. store {
        3. ## store mode: file、db
        4. mode = "db"
        5. ## database store
        6. db {
        7. driver-class-name = "com.mysql.jdbc.Driver"
        8. url = "jdbc:mysql://127.0.0.1:3306/seata"
        9. user = "root"
        10. password = "root"
        11. }
        12. }
    3. mysql5.7 数据库新建库 seata,运行在 seata 安装目录中 conf 目录下的 db_store.sql

    1. create database seata;
    2. use seata;
    3. source D:\Devware\seata-server-0.9.0\conf\db_store.sql
    1. 修改 seata 安装目录下 conf 目录下的 registry.conf 配置文件
    1. registry {
    2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
    3. type = "nacos" #指明注册中心为nacos
    4. nacos {
    5. serverAddr = "localhost:8848" #修改nacos连接信息
    6. }
    1. 启动 Nacos 端口号8848,执行 startup.cmd -m standalone
    2. 启动 seata-server,运行 seata 安装目录中 bin 目录下的 seata-server.bat

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

    创建三个服务,一个订单服务,一个库存服务,一个账户服务。当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

    1. 建库
    1. CREATE DATABASE seata_order;
    2. CREATE DATABASE seata_storage;
    3. CREATE DATABASE seata_account;
    1. 按照上述 3 库分别建对应业务表
    1. #seata_order库下建t_order
    2. CREATE TABLE t_order (
    3. `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    4. `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
    5. `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
    6. `count` INT(11) DEFAULT NULL COMMENT '数量',
    7. `money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
    8. `status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结'
    9. ) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
    10. SELECT * FROM t_order;
    11. #seata_storage库下建t_storage 表
    12. CREATE TABLE t_storage (
    13. `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    14. `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
    15. `total` INT(11) DEFAULT NULL COMMENT '总库存',
    16. `used` INT(11) DEFAULT NULL COMMENT '已用库存',
    17. `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
    18. ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    19. INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
    20. VALUES ('1', '1', '100', '0', '100');
    21. SELECT * FROM t_storage;
    22. #seata_account库下建t_account 表
    23. CREATE TABLE t_account (
    24. `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
    25. `user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
    26. `total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
    27. `used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
    28. `residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
    29. ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    30. INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');
    31. SELECT * FROM t_account;
    1. 按照上述3库分别建对应的回滚日志表,导入 seata 安装目录 conf 下的 _undo_log.sql 文件
    source D:\Devware\seata-server-0.9.0\conf\db_undo_log.sql
    

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

    seata-order-service2001

    1. 新建 moudle,seata-order-service2001
    2. 修改 POM
    1. <dependencies>
    2. <!--nacos-->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    6. </dependency>
    7. <!--seata-->
    8. <dependency>
    9. <groupId>com.alibaba.cloud</groupId>
    10. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    11. <exclusions>
    12. <exclusion>
    13. <artifactId>seata-all</artifactId>
    14. <groupId>io.seata</groupId>
    15. </exclusion>
    16. </exclusions>
    17. </dependency>
    18. <dependency>
    19. <groupId>io.seata</groupId>
    20. <artifactId>seata-all</artifactId>
    21. <version>0.9.0</version>
    22. </dependency>
    23. <!--feign-->
    24. <dependency>
    25. <groupId>org.springframework.cloud</groupId>
    26. <artifactId>spring-cloud-starter-openfeign</artifactId>
    27. </dependency>
    28. <!--web-actuator-->
    29. <dependency>
    30. <groupId>org.springframework.boot</groupId>
    31. <artifactId>spring-boot-starter-web</artifactId>
    32. </dependency>
    33. <dependency>
    34. <groupId>org.springframework.boot</groupId>
    35. <artifactId>spring-boot-starter-actuator</artifactId>
    36. </dependency>
    37. <!--mysql-druid-->
    38. <dependency>
    39. <groupId>mysql</groupId>
    40. <artifactId>mysql-connector-java</artifactId>
    41. <version>5.1.37</version>
    42. </dependency>
    43. <dependency>
    44. <groupId>com.alibaba</groupId>
    45. <artifactId>druid-spring-boot-starter</artifactId>
    46. <version>1.1.10</version>
    47. </dependency>
    48. <dependency>
    49. <groupId>org.mybatis.spring.boot</groupId>
    50. <artifactId>mybatis-spring-boot-starter</artifactId>
    51. <version>2.0.0</version>
    52. </dependency>
    53. <dependency>
    54. <groupId>org.springframework.boot</groupId>
    55. <artifactId>spring-boot-starter-test</artifactId>
    56. <scope>test</scope>
    57. </dependency>
    58. <dependency>
    59. <groupId>org.projectlombok</groupId>
    60. <artifactId>lombok</artifactId>
    61. <optional>true</optional>
    62. </dependency>
    63. </dependencies>
    1. 写 YML
    1. server:
    2. port: 2001
    3. spring:
    4. application:
    5. name: seata-order-service
    6. cloud:
    7. alibaba:
    8. seata:
    9. #自定义事务组名称需要与seata-server中的对应
    10. tx-service-group: fsp_tx_group
    11. nacos:
    12. discovery:
    13. server-addr: localhost:8848
    14. datasource:
    15. driver-class-name: com.mysql.jdbc.Driver
    16. url: jdbc:mysql://localhost:3306/seata_order
    17. username: root
    18. password: 123456
    19. feign:
    20. hystrix:
    21. enabled: false
    22. logging:
    23. level:
    24. io:
    25. seata: info
    26. mybatis:
    27. mapperLocations: classpath:mapper/*.xml
    1. 配置文件
    • file.conf
    1. transport {
    2. # tcp udt unix-domain-socket
    3. type = "TCP"
    4. #NIO NATIVE
    5. server = "NIO"
    6. #enable heartbeat
    7. heartbeat = true
    8. #thread factory for netty
    9. thread-factory {
    10. boss-thread-prefix = "NettyBoss"
    11. worker-thread-prefix = "NettyServerNIOWorker"
    12. server-executor-thread-prefix = "NettyServerBizHandler"
    13. share-boss-worker = false
    14. client-selector-thread-prefix = "NettyClientSelector"
    15. client-selector-thread-size = 1
    16. client-worker-thread-prefix = "NettyClientWorkerThread"
    17. # netty boss thread size,will not be used for UDT
    18. boss-thread-size = 1
    19. #auto default pin or 8
    20. worker-thread-size = 8
    21. }
    22. shutdown {
    23. # when destroy server, wait seconds
    24. wait = 3
    25. }
    26. serialization = "seata"
    27. compressor = "none"
    28. }
    29. service {
    30. vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称
    31. default.grouplist = "127.0.0.1:8091"
    32. enableDegrade = false
    33. disable = false
    34. max.commit.retry.timeout = "-1"
    35. max.rollback.retry.timeout = "-1"
    36. disableGlobalTransaction = false
    37. }
    38. client {
    39. async.commit.buffer.limit = 10000
    40. lock {
    41. retry.internal = 10
    42. retry.times = 30
    43. }
    44. report.retry.count = 5
    45. tm.commit.retry.count = 1
    46. tm.rollback.retry.count = 1
    47. }
    48. ## transaction log store
    49. store {
    50. ## store mode: file、db
    51. mode = "db"
    52. ## file store
    53. file {
    54. dir = "sessionStore"
    55. # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    56. max-branch-session-size = 16384
    57. # globe session size , if exceeded throws exceptions
    58. max-global-session-size = 512
    59. # file buffer size , if exceeded allocate new buffer
    60. file-write-buffer-cache-size = 16384
    61. # when recover batch read size
    62. session.reload.read_size = 100
    63. # async, sync
    64. flush-disk-mode = async
    65. }
    66. ## database store
    67. db {
    68. ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    69. datasource = "dbcp"
    70. ## mysql/oracle/h2/oceanbase etc.
    71. db-type = "mysql"
    72. driver-class-name = "com.mysql.jdbc.Driver"
    73. url = "jdbc:mysql://127.0.0.1:3306/seata"
    74. user = "root"
    75. password = "123456"
    76. min-conn = 1
    77. max-conn = 3
    78. global.table = "global_table"
    79. branch.table = "branch_table"
    80. lock-table = "lock_table"
    81. query-limit = 100
    82. }
    83. }
    84. lock {
    85. ## the lock store mode: local、remote
    86. mode = "remote"
    87. local {
    88. ## store locks in user's database
    89. }
    90. remote {
    91. ## store locks in the seata's server
    92. }
    93. }
    94. recovery {
    95. #schedule committing retry period in milliseconds
    96. committing-retry-period = 1000
    97. #schedule asyn committing retry period in milliseconds
    98. asyn-committing-retry-period = 1000
    99. #schedule rollbacking retry period in milliseconds
    100. rollbacking-retry-period = 1000
    101. #schedule timeout retry period in milliseconds
    102. timeout-retry-period = 1000
    103. }
    104. transaction {
    105. undo.data.validation = true
    106. undo.log.serialization = "jackson"
    107. undo.log.save.days = 7
    108. #schedule delete expired undo_log in milliseconds
    109. undo.log.delete.period = 86400000
    110. undo.log.table = "undo_log"
    111. }
    112. ## metrics settings
    113. metrics {
    114. enabled = false
    115. registry-type = "compact"
    116. # multi exporters use comma divided
    117. exporter-list = "prometheus"
    118. exporter-prometheus-port = 9898
    119. }
    120. support {
    121. ## spring
    122. spring {
    123. # auto proxy the DataSource bean
    124. datasource.autoproxy = false
    125. }
    126. }
    • registry.conf
    1. registry {
    2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
    3. type = "nacos"
    4. nacos {
    5. serverAddr = "localhost:8848"
    6. namespace = ""
    7. cluster = "default"
    8. }
    9. eureka {
    10. serviceUrl = "http://localhost:8761/eureka"
    11. application = "default"
    12. weight = "1"
    13. }
    14. redis {
    15. serverAddr = "localhost:6379"
    16. db = "0"
    17. }
    18. zk {
    19. cluster = "default"
    20. serverAddr = "127.0.0.1:2181"
    21. session.timeout = 6000
    22. connect.timeout = 2000
    23. }
    24. consul {
    25. cluster = "default"
    26. serverAddr = "127.0.0.1:8500"
    27. }
    28. etcd3 {
    29. cluster = "default"
    30. serverAddr = "http://localhost:2379"
    31. }
    32. sofa {
    33. serverAddr = "127.0.0.1:9603"
    34. application = "default"
    35. region = "DEFAULT_ZONE"
    36. datacenter = "DefaultDataCenter"
    37. cluster = "default"
    38. group = "SEATA_GROUP"
    39. addressWaitTime = "3000"
    40. }
    41. file {
    42. name = "file.conf"
    43. }
    44. }
    45. config {
    46. # file、nacos 、apollo、zk、consul、etcd3
    47. type = "file"
    48. nacos {
    49. serverAddr = "localhost"
    50. namespace = ""
    51. }
    52. consul {
    53. serverAddr = "127.0.0.1:8500"
    54. }
    55. apollo {
    56. app.id = "seata-server"
    57. apollo.meta = "http://192.168.1.204:8801"
    58. }
    59. zk {
    60. serverAddr = "127.0.0.1:2181"
    61. session.timeout = 6000
    62. connect.timeout = 2000
    63. }
    64. etcd3 {
    65. serverAddr = "http://localhost:2379"
    66. }
    67. file {
    68. name = "file.conf"
    69. }
    70. }
    1. domain

      • CommonResult
      1. @Data
      2. @AllArgsConstructor
      3. @NoArgsConstructor
      4. public class CommonResult
      5. {
      6. private Integer code;
      7. private String message;
      8. private T data;
      9. public CommonResult(Integer code, String message)
      10. {
      11. this(code,message,null);
      12. }
      13. }
      • Order
      1. @Data
      2. @AllArgsConstructor
      3. @NoArgsConstructor
      4. public class Order
      5. {
      6. private Long id;
      7. private Long userId;
      8. private Long productId;
      9. private Integer count;
      10. private BigDecimal money;
      11. /**
      12. * 订单状态:0:创建中;1:已完结
      13. */
      14. private Integer status;
      15. }
    2. Dao 接口实现及实现

      • OrderDao
      1. @Mapper
      2. public interface OrderDao {
      3. /**
      4. * 创建订单
      5. */
      6. void create(Order order);
      7. /**
      8. * 修改订单金额
      9. */
      10. void update(@Param("userId") Long userId, @Param("status") Integer status);
      11. }
      • resource 目录下添加 mapper 文件夹后添加 OrderMapper.xml
      1. <?xml version="1.0" encoding="UTF-8" ?>
      2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
      3. <mapper namespace="com.xiaobai.springcloud.alibaba.dao.OrderDao">
      4. <resultMap id="BaseResultMap" type="com.xiaobai.springcloud.alibaba.domain.Order">
      5. <id column="id" property="id" jdbcType="BIGINT"/>
      6. <result column="user_id" property="userId" jdbcType="BIGINT"/>
      7. <result column="product_id" property="productId" jdbcType="BIGINT"/>
      8. <result column="count" property="count" jdbcType="INTEGER"/>
      9. <result column="money" property="money" jdbcType="DECIMAL"/>
      10. <result column="status" property="status" jdbcType="INTEGER"/>
      11. </resultMap>
      12. <insert id="create">
      13. INSERT INTO `t_order` (`id`, `user_id`, `product_id`, `count`, `money`, `status`)
      14. VALUES (NULL, #{userId}, #{productId}, #{count}, #{money}, 0);
      15. </insert>
      16. <update id="update">
      17. UPDATE `t_order`
      18. SET status = 1
      19. WHERE user_id = #{userId} AND status = #{status};
      20. </update>
      21. </mapper>
    3. Service接口及实现

      • OrderService
      1. public interface OrderService {
      2. /**
      3. * 创建订单
      4. */
      5. void create(Order order);
      6. }
      • OrderServiceImpl
      1. @Service
      2. @Slf4j
      3. public class OrderServiceImpl implements OrderService
      4. {
      5. @Resource
      6. private OrderDao orderDao;
      7. @Resource
      8. private StorageService storageService;
      9. @Resource
      10. private AccountService accountService;
      11. /**
      12. * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
      13. * 简单说:
      14. * 下订单->减库存->减余额->改状态
      15. */
      16. @Override
      17. @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
      18. public void create(Order order) {
      19. log.info("------->下单开始");
      20. //本应用创建订单
      21. orderDao.create(order);
      22. //远程调用库存服务扣减库存
      23. log.info("------->order-service中扣减库存开始");
      24. storageService.decrease(order.getProductId(),order.getCount());
      25. log.info("------->order-service中扣减库存结束");
      26. //远程调用账户服务扣减余额
      27. log.info("------->order-service中扣减余额开始");
      28. accountService.decrease(order.getUserId(),order.getMoney());
      29. log.info("------->order-service中扣减余额结束");
      30. //修改订单状态为已完成
      31. log.info("------->order-service中修改订单状态开始");
      32. orderDao.update(order.getUserId(),0);
      33. log.info("------->order-service中修改订单状态结束");
      34. log.info("------->下单结束");
      35. }
      36. }
      • StorageService
      1. @FeignClient(value = "seata-storage-service")
      2. public interface StorageService {
      3. /**
      4. * 扣减库存
      5. */
      6. @PostMapping(value = "/storage/decrease")
      7. CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
      8. }
      • AccountService
      1. @FeignClient(value = "seata-account-service")
      2. public interface AccountService {
      3. /**
      4. * 扣减账户余额
      5. */
      6. //@RequestMapping(value = "/account/decrease", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
      7. @PostMapping("/account/decrease")
      8. CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
      9. }
    4. controller

    1. @RestController
    2. public class OrderController {
    3. @Autowired
    4. private OrderService orderService;
    5. /**
    6. * 创建订单
    7. */
    8. @GetMapping("/order/create")
    9. public CommonResult create( Order order) {
    10. orderService.create(order);
    11. return new CommonResult(200, "订单创建成功!");
    12. }
    13. }
    1. Config配置

      • MyBatisConfig
      1. @Configuration
      2. @MapperScan({"com.xiaobai.springcloud.alibaba.dao"})
      3. public class MyBatisConfig {
      4. }
      5. }
      • DataSourceProxyConfig
      1. @Configuration
      2. public class DataSourceProxyConfig {
      3. @Value("${mybatis.mapperLocations}")
      4. private String mapperLocations;
      5. @Bean
      6. @ConfigurationProperties(prefix = "spring.datasource")
      7. public DataSource druidDataSource(){
      8. return new DruidDataSource();
      9. }
      10. @Bean
      11. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
      12. return new DataSourceProxy(dataSource);
      13. }
      14. @Bean
      15. public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
      16. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      17. sqlSessionFactoryBean.setDataSource(dataSourceProxy);
      18. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
      19. sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
      20. return sqlSessionFactoryBean.getObject();
      21. }
      22. }

    seata-storage-service2002

    1. 新建moudle,seata-storage-service2002

    2. 改 POM

    1. <dependencies>
    2. <!--nacos-->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    6. </dependency>
    7. <!--seata-->
    8. <dependency>
    9. <groupId>com.alibaba.cloud</groupId>
    10. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    11. <exclusions>
    12. <exclusion>
    13. <artifactId>seata-all</artifactId>
    14. <groupId>io.seata</groupId>
    15. </exclusion>
    16. </exclusions>
    17. </dependency>
    18. <dependency>
    19. <groupId>io.seata</groupId>
    20. <artifactId>seata-all</artifactId>
    21. <version>0.9.0</version>
    22. </dependency>
    23. <!--feign-->
    24. <dependency>
    25. <groupId>org.springframework.cloud</groupId>
    26. <artifactId>spring-cloud-starter-openfeign</artifactId>
    27. </dependency>
    28. <dependency>
    29. <groupId>org.springframework.boot</groupId>
    30. <artifactId>spring-boot-starter-web</artifactId>
    31. </dependency>
    32. <dependency>
    33. <groupId>org.springframework.boot</groupId>
    34. <artifactId>spring-boot-starter-test</artifactId>
    35. <scope>test</scope>
    36. </dependency>
    37. <dependency>
    38. <groupId>org.mybatis.spring.boot</groupId>
    39. <artifactId>mybatis-spring-boot-starter</artifactId>
    40. <version>2.0.0</version>
    41. </dependency>
    42. <dependency>
    43. <groupId>mysql</groupId>
    44. <artifactId>mysql-connector-java</artifactId>
    45. <version>5.1.37</version>
    46. </dependency>
    47. <dependency>
    48. <groupId>com.alibaba</groupId>
    49. <artifactId>druid-spring-boot-starter</artifactId>
    50. <version>1.1.10</version>
    51. </dependency>
    52. <dependency>
    53. <groupId>org.projectlombok</groupId>
    54. <artifactId>lombok</artifactId>
    55. <optional>true</optional>
    56. </dependency>
    57. </dependencies>
    1. 写 YML
    1. server:
    2. port: 2002
    3. spring:
    4. application:
    5. name: seata-storage-service
    6. cloud:
    7. alibaba:
    8. seata:
    9. tx-service-group: fsp_tx_group
    10. nacos:
    11. discovery:
    12. server-addr: localhost:8848
    13. datasource:
    14. driver-class-name: com.mysql.jdbc.Driver
    15. url: jdbc:mysql://localhost:3306/seata_storage
    16. username: root
    17. password: 123456
    18. logging:
    19. level:
    20. io:
    21. seata: info
    22. mybatis:
    23. mapperLocations: classpath:mapper/*.xml
    1. 配置文件

      • file.conf
      1. transport {
      2. # tcp udt unix-domain-socket
      3. type = "TCP"
      4. #NIO NATIVE
      5. server = "NIO"
      6. #enable heartbeat
      7. heartbeat = true
      8. #thread factory for netty
      9. thread-factory {
      10. boss-thread-prefix = "NettyBoss"
      11. worker-thread-prefix = "NettyServerNIOWorker"
      12. server-executor-thread-prefix = "NettyServerBizHandler"
      13. share-boss-worker = false
      14. client-selector-thread-prefix = "NettyClientSelector"
      15. client-selector-thread-size = 1
      16. client-worker-thread-prefix = "NettyClientWorkerThread"
      17. # netty boss thread size,will not be used for UDT
      18. boss-thread-size = 1
      19. #auto default pin or 8
      20. worker-thread-size = 8
      21. }
      22. shutdown {
      23. # when destroy server, wait seconds
      24. wait = 3
      25. }
      26. serialization = "seata"
      27. compressor = "none"
      28. }
      29. service {
      30. #vgroup->rgroup
      31. vgroup_mapping.fsp_tx_group = "default"
      32. #only support single node
      33. default.grouplist = "127.0.0.1:8091"
      34. #degrade current not support
      35. enableDegrade = false
      36. #disable
      37. disable = false
      38. #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
      39. max.commit.retry.timeout = "-1"
      40. max.rollback.retry.timeout = "-1"
      41. disableGlobalTransaction = false
      42. }
      43. client {
      44. async.commit.buffer.limit = 10000
      45. lock {
      46. retry.internal = 10
      47. retry.times = 30
      48. }
      49. report.retry.count = 5
      50. tm.commit.retry.count = 1
      51. tm.rollback.retry.count = 1
      52. }
      53. transaction {
      54. undo.data.validation = true
      55. undo.log.serialization = "jackson"
      56. undo.log.save.days = 7
      57. #schedule delete expired undo_log in milliseconds
      58. undo.log.delete.period = 86400000
      59. undo.log.table = "undo_log"
      60. }
      61. support {
      62. ## spring
      63. spring {
      64. # auto proxy the DataSource bean
      65. datasource.autoproxy = false
      66. }
      67. }
      • registry.conf
      1. registry {
      2. # file 、nacos 、eureka、redis、zk
      3. type = "nacos"
      4. nacos {
      5. serverAddr = "localhost:8848"
      6. namespace = ""
      7. cluster = "default"
      8. }
      9. eureka {
      10. serviceUrl = "http://localhost:8761/eureka"
      11. application = "default"
      12. weight = "1"
      13. }
      14. redis {
      15. serverAddr = "localhost:6381"
      16. db = "0"
      17. }
      18. zk {
      19. cluster = "default"
      20. serverAddr = "127.0.0.1:2181"
      21. session.timeout = 6000
      22. connect.timeout = 2000
      23. }
      24. file {
      25. name = "file.conf"
      26. }
      27. }
      28. config {
      29. # file、nacos 、apollo、zk
      30. type = "file"
      31. nacos {
      32. serverAddr = "localhost"
      33. namespace = ""
      34. cluster = "default"
      35. }
      36. apollo {
      37. app.id = "fescar-server"
      38. apollo.meta = "http://192.168.1.204:8801"
      39. }
      40. zk {
      41. serverAddr = "127.0.0.1:2181"
      42. session.timeout = 6000
      43. connect.timeout = 2000
      44. }
      45. file {
      46. name = "file.conf"
      47. }
      48. }
    2. domain

      • CommanResult
      1. @Data
      2. @AllArgsConstructor
      3. @NoArgsConstructor
      4. public class CommonResult
      5. {
      6. private Integer code;
      7. private String message;
      8. private T data;
      9. public CommonResult(Integer code, String message)
      10. {
      11. this(code,message,null);
      12. }
      13. }
      • Storage
      1. @Data
      2. public class Storage {
      3. private Long id;
      4. /**
      5. * 产品id
      6. */
      7. private Long productId;
      8. /**
      9. * 总库存
      10. */
      11. private Integer total;
      12. /**
      13. * 已用库存
      14. */
      15. private Integer used;
      16. /**
      17. * 剩余库存
      18. */
      19. private Integer residue;
      20. }
    3. Dao接口及实现

      • StorageDao
      1. @Mapper
      2. public interface StorageDao {
      3. /**
      4. * 扣减库存
      5. */
      6. void decrease(@Param("productId") Long productId, @Param("count") Integer count);
      7. }
      • resources文件夹下新建mapper文件夹后添加 StorageMapper.xml
      1. <mapper namespace="com.xiaobai.springcloud.alibaba.dao.StorageDao">
      2. <resultMap id="BaseResultMap" type="com.xiaobai.springcloud.alibaba.domain.Storage">
      3. <id column="id" property="id" jdbcType="BIGINT"/>
      4. <result column="product_id" property="productId" jdbcType="BIGINT"/>
      5. <result column="total" property="total" jdbcType="INTEGER"/>
      6. <result column="used" property="used" jdbcType="INTEGER"/>
      7. <result column="residue" property="residue" jdbcType="INTEGER"/>
      8. </resultMap>
      9. <update id="decrease">
      10. UPDATE t_storage
      11. SET used = used + #{count},
      12. residue = residue - #{count}
      13. WHERE product_id = #{productId}
      14. </update>
      15. </mapper>
    4. Service接口及实现

      • StorageService
      1. public interface StorageService {
      2. /**
      3. * 扣减库存
      4. */
      5. void decrease(Long productId, Integer count);
      6. }
      • StorageServiceImpl
      1. @Service
      2. public class StorageServiceImpl implements StorageService {
      3. private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
      4. @Resource
      5. private StorageDao storageDao;
      6. /**
      7. * 扣减库存
      8. */
      9. @Override
      10. public void decrease(Long productId, Integer count) {
      11. LOGGER.info("------->storage-service中扣减库存开始");
      12. storageDao.decrease(productId,count);
      13. LOGGER.info("------->storage-service中扣减库存结束");
      14. }
      15. }
    5. Controller

    1. @RestController
    2. public class StorageController {
    3. @Autowired
    4. private StorageService storageService;
    5. /**
    6. * 扣减库存
    7. */
    8. @RequestMapping("/storage/decrease")
    9. public CommonResult decrease(Long productId, Integer count) {
    10. storageService.decrease(productId, count);
    11. return new CommonResult(200,"扣减库存成功!");
    12. }
    13. }
    1. Config配置

      • MyBatisConfig
      1. @Configuration
      2. @MapperScan({"com.xiaobai.springcloud.alibaba.dao"})
      3. public class MyBatisConfig {
      4. }
      • DataSourceProxyConfig
      1. @Configuration
      2. public class DataSourceProxyConfig {
      3. @Value("${mybatis.mapperLocations}")
      4. private String mapperLocations;
      5. @Bean
      6. @ConfigurationProperties(prefix = "spring.datasource")
      7. public DataSource druidDataSource(){
      8. return new DruidDataSource();
      9. }
      10. @Bean
      11. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
      12. return new DataSourceProxy(dataSource);
      13. }
      14. @Bean
      15. public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
      16. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      17. sqlSessionFactoryBean.setDataSource(dataSourceProxy);
      18. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
      19. sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
      20. return sqlSessionFactoryBean.getObject();
      21. }
      22. }
    2. 主启动

    1. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    2. @EnableDiscoveryClient
    3. @EnableFeignClients
    4. public class SeataStorageServiceApplication2002 {
    5. public static void main(String[] args) {
    6. SpringApplication.run(SeataStorageServiceApplication2002.class, args);
    7. }
    8. }

    seata-account-service2003

    1. 新建moudle,seata-account-service2003
    2. 改 POM
    1. <dependencies>
    2. <!--nacos-->
    3. <dependency>
    4. <groupId>com.alibaba.cloud</groupId>
    5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    6. </dependency>
    7. <!--seata-->
    8. <dependency>
    9. <groupId>com.alibaba.cloud</groupId>
    10. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    11. <exclusions>
    12. <exclusion>
    13. <artifactId>seata-all</artifactId>
    14. <groupId>io.seata</groupId>
    15. </exclusion>
    16. </exclusions>
    17. </dependency>
    18. <dependency>
    19. <groupId>io.seata</groupId>
    20. <artifactId>seata-all</artifactId>
    21. <version>0.9.0</version>
    22. </dependency>
    23. <!--feign-->
    24. <dependency>
    25. <groupId>org.springframework.cloud</groupId>
    26. <artifactId>spring-cloud-starter-openfeign</artifactId>
    27. </dependency>
    28. <dependency>
    29. <groupId>org.springframework.boot</groupId>
    30. <artifactId>spring-boot-starter-web</artifactId>
    31. </dependency>
    32. <dependency>
    33. <groupId>org.springframework.boot</groupId>
    34. <artifactId>spring-boot-starter-test</artifactId>
    35. <scope>test</scope>
    36. </dependency>
    37. <dependency>
    38. <groupId>org.mybatis.spring.boot</groupId>
    39. <artifactId>mybatis-spring-boot-starter</artifactId>
    40. <version>2.0.0</version>
    41. </dependency>
    42. <dependency>
    43. <groupId>mysql</groupId>
    44. <artifactId>mysql-connector-java</artifactId>
    45. <version>5.1.37</version>
    46. </dependency>
    47. <dependency>
    48. <groupId>com.alibaba</groupId>
    49. <artifactId>druid-spring-boot-starter</artifactId>
    50. <version>1.1.10</version>
    51. </dependency>
    52. <dependency>
    53. <groupId>org.projectlombok</groupId>
    54. <artifactId>lombok</artifactId>
    55. <optional>true</optional>
    56. </dependency>
    57. </dependencies>
    1. 写YML
    1. server:
    2. port: 2003
    3. spring:
    4. application:
    5. name: seata-account-service
    6. cloud:
    7. alibaba:
    8. seata:
    9. tx-service-group: fsp_tx_group
    10. nacos:
    11. discovery:
    12. server-addr: localhost:8848
    13. datasource:
    14. driver-class-name: com.mysql.jdbc.Driver
    15. url: jdbc:mysql://localhost:3306/seata_account
    16. username: root
    17. password: 123456
    18. feign:
    19. hystrix:
    20. enabled: false
    21. logging:
    22. level:
    23. io:
    24. seata: info
    25. mybatis:
    26. mapperLocations: classpath:mapper/*.xml
    1. 配置文件

      • file.conf
      1. transport {
      2. # tcp udt unix-domain-socket
      3. type = "TCP"
      4. #NIO NATIVE
      5. server = "NIO"
      6. #enable heartbeat
      7. heartbeat = true
      8. #thread factory for netty
      9. thread-factory {
      10. boss-thread-prefix = "NettyBoss"
      11. worker-thread-prefix = "NettyServerNIOWorker"
      12. server-executor-thread-prefix = "NettyServerBizHandler"
      13. share-boss-worker = false
      14. client-selector-thread-prefix = "NettyClientSelector"
      15. client-selector-thread-size = 1
      16. client-worker-thread-prefix = "NettyClientWorkerThread"
      17. # netty boss thread size,will not be used for UDT
      18. boss-thread-size = 1
      19. #auto default pin or 8
      20. worker-thread-size = 8
      21. }
      22. shutdown {
      23. # when destroy server, wait seconds
      24. wait = 3
      25. }
      26. serialization = "seata"
      27. compressor = "none"
      28. }
      29. service {
      30. vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称
      31. default.grouplist = "127.0.0.1:8091"
      32. enableDegrade = false
      33. disable = false
      34. max.commit.retry.timeout = "-1"
      35. max.rollback.retry.timeout = "-1"
      36. disableGlobalTransaction = false
      37. }
      38. client {
      39. async.commit.buffer.limit = 10000
      40. lock {
      41. retry.internal = 10
      42. retry.times = 30
      43. }
      44. report.retry.count = 5
      45. tm.commit.retry.count = 1
      46. tm.rollback.retry.count = 1
      47. }
      48. ## transaction log store
      49. store {
      50. ## store mode: file、db
      51. mode = "db"
      52. ## file store
      53. file {
      54. dir = "sessionStore"
      55. # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
      56. max-branch-session-size = 16384
      57. # globe session size , if exceeded throws exceptions
      58. max-global-session-size = 512
      59. # file buffer size , if exceeded allocate new buffer
      60. file-write-buffer-cache-size = 16384
      61. # when recover batch read size
      62. session.reload.read_size = 100
      63. # async, sync
      64. flush-disk-mode = async
      65. }
      66. ## database store
      67. db {
      68. ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
      69. datasource = "dbcp"
      70. ## mysql/oracle/h2/oceanbase etc.
      71. db-type = "mysql"
      72. driver-class-name = "com.mysql.jdbc.Driver"
      73. url = "jdbc:mysql://127.0.0.1:3306/seata"
      74. user = "root"
      75. password = "123456"
      76. min-conn = 1
      77. max-conn = 3
      78. global.table = "global_table"
      79. branch.table = "branch_table"
      80. lock-table = "lock_table"
      81. query-limit = 100
      82. }
      83. }
      84. lock {
      85. ## the lock store mode: local、remote
      86. mode = "remote"
      87. local {
      88. ## store locks in user's database
      89. }
      90. remote {
      91. ## store locks in the seata's server
      92. }
      93. }
      94. recovery {
      95. #schedule committing retry period in milliseconds
      96. committing-retry-period = 1000
      97. #schedule asyn committing retry period in milliseconds
      98. asyn-committing-retry-period = 1000
      99. #schedule rollbacking retry period in milliseconds
      100. rollbacking-retry-period = 1000
      101. #schedule timeout retry period in milliseconds
      102. timeout-retry-period = 1000
      103. }
      104. transaction {
      105. undo.data.validation = true
      106. undo.log.serialization = "jackson"
      107. undo.log.save.days = 7
      108. #schedule delete expired undo_log in milliseconds
      109. undo.log.delete.period = 86400000
      110. undo.log.table = "undo_log"
      111. }
      112. ## metrics settings
      113. metrics {
      114. enabled = false
      115. registry-type = "compact"
      116. # multi exporters use comma divided
      117. exporter-list = "prometheus"
      118. exporter-prometheus-port = 9898
      119. }
      120. support {
      121. ## spring
      122. spring {
      123. # auto proxy the DataSource bean
      124. datasource.autoproxy = false
      125. }
      126. }
      • registry.conf
      1. registry {
      2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      3. type = "nacos"
      4. nacos {
      5. serverAddr = "localhost:8848"
      6. namespace = ""
      7. cluster = "default"
      8. }
      9. eureka {
      10. serviceUrl = "http://localhost:8761/eureka"
      11. application = "default"
      12. weight = "1"
      13. }
      14. redis {
      15. serverAddr = "localhost:6379"
      16. db = "0"
      17. }
      18. zk {
      19. cluster = "default"
      20. serverAddr = "127.0.0.1:2181"
      21. session.timeout = 6000
      22. connect.timeout = 2000
      23. }
      24. consul {
      25. cluster = "default"
      26. serverAddr = "127.0.0.1:8500"
      27. }
      28. etcd3 {
      29. cluster = "default"
      30. serverAddr = "http://localhost:2379"
      31. }
      32. sofa {
      33. serverAddr = "127.0.0.1:9603"
      34. application = "default"
      35. region = "DEFAULT_ZONE"
      36. datacenter = "DefaultDataCenter"
      37. cluster = "default"
      38. group = "SEATA_GROUP"
      39. addressWaitTime = "3000"
      40. }
      41. file {
      42. name = "file.conf"
      43. }
      44. }
      45. config {
      46. # file、nacos 、apollo、zk、consul、etcd3
      47. type = "file"
      48. nacos {
      49. serverAddr = "localhost"
      50. namespace = ""
      51. }
      52. consul {
      53. serverAddr = "127.0.0.1:8500"
      54. }
      55. apollo {
      56. app.id = "seata-server"
      57. apollo.meta = "http://192.168.1.204:8801"
      58. }
      59. zk {
      60. serverAddr = "127.0.0.1:2181"
      61. session.timeout = 6000
      62. connect.timeout = 2000
      63. }
      64. etcd3 {
      65. serverAddr = "http://localhost:2379"
      66. }
      67. file {
      68. name = "file.conf"
      69. }
      70. }
    2. domain

      • CommonResult
      1. @Data
      2. @AllArgsConstructor
      3. @NoArgsConstructor
      4. public class CommonResult
      5. {
      6. private Integer code;
      7. private String message;
      8. private T data;
      9. public CommonResult(Integer code, String message)
      10. {
      11. this(code,message,null);
      12. }
      13. }
      • Account
      1. @Data
      2. @AllArgsConstructor
      3. @NoArgsConstructor
      4. public class Account {
      5. private Long id;
      6. /**
      7. * 用户id
      8. */
      9. private Long userId;
      10. /**
      11. * 总额度
      12. */
      13. private BigDecimal total;
      14. /**
      15. * 已用额度
      16. */
      17. private BigDecimal used;
      18. /**
      19. * 剩余额度
      20. */
      21. private BigDecimal residue;
      22. }
    3. Dao接口及实现

      • AccountDao
      1. @Mapper
      2. public interface AccountDao {
      3. /**
      4. * 扣减账户余额
      5. */
      6. void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
      7. }
      • resources文件夹下新建mapper文件夹后添加AccountMapper.xml
      1. <mapper namespace="com.xiaobai.springcloud.alibaba.dao.AccountDao">
      2. <resultMap id="BaseResultMap" type="com.xiaobai.springcloud.alibaba.domain.Account">
      3. <id column="id" property="id" jdbcType="BIGINT"/>
      4. <result column="user_id" property="userId" jdbcType="BIGINT"/>
      5. <result column="total" property="total" jdbcType="DECIMAL"/>
      6. <result column="used" property="used" jdbcType="DECIMAL"/>
      7. <result column="residue" property="residue" jdbcType="DECIMAL"/>
      8. </resultMap>
      9. <update id="decrease">
      10. UPDATE t_account
      11. SET
      12. residue = residue - #{money},used = used + #{money}
      13. WHERE
      14. user_id = #{userId};
      15. </update>
      16. </mapper>
    4. Service接口及实现

      • AccountService
      1. public interface AccountService {
      2. /**
      3. * 扣减账户余额
      4. * @param userId 用户id
      5. * @param money 金额
      6. */
      7. void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
      8. }
      • AccountServiceImpl
      1. /**
      2. * 账户业务实现类
      3. * Created by zzyy on 2019/11/11.
      4. */
      5. @Service
      6. public class AccountServiceImpl implements AccountService {
      7. private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
      8. @Resource
      9. AccountDao accountDao;
      10. /**
      11. * 扣减账户余额
      12. */
      13. @Override
      14. public void decrease(Long userId, BigDecimal money) {
      15. LOGGER.info("------->account-service中扣减账户余额开始");
      16. //模拟超时异常,全局事务回滚
      17. //暂停几秒钟线程
      18. //try { TimeUnit.SECONDS.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); }
      19. accountDao.decrease(userId,money);
      20. LOGGER.info("------->account-service中扣减账户余额结束");
      21. }
      22. }
    5. Controller

    1. @RestController
    2. public class AccountController {
    3. @Resource
    4. AccountService accountService;
    5. /**
    6. * 扣减账户余额
    7. */
    8. @RequestMapping("/account/decrease")
    9. public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
    10. accountService.decrease(userId,money);
    11. return new CommonResult(200,"扣减账户余额成功!");
    12. }
    13. }
    1. Config配置

      • MyBatisConfig
      1. @Configuration
      2. @MapperScan({"com.xiaobai.springcloud.alibaba.dao"})
      3. public class MyBatisConfig {
      4. }
      • DataSourceProxyConfig
      1. @Configuration
      2. public class DataSourceProxyConfig {
      3. @Value("${mybatis.mapperLocations}")
      4. private String mapperLocations;
      5. @Bean
      6. @ConfigurationProperties(prefix = "spring.datasource")
      7. public DataSource druidDataSource(){
      8. return new DruidDataSource();
      9. }
      10. @Bean
      11. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
      12. return new DataSourceProxy(dataSource);
      13. }
      14. @Bean
      15. public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
      16. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      17. sqlSessionFactoryBean.setDataSource(dataSourceProxy);
      18. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
      19. sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
      20. return sqlSessionFactoryBean.getObject();
      21. }
      22. }
    2. 主启动

    1. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    2. @EnableDiscoveryClient
    3. @EnableFeignClients
    4. public class SeataAccountMainApp2003
    5. {
    6. public static void main(String[] args)
    7. {
    8. SpringApplication.run(SeataAccountMainApp2003.class, args);
    9. }
    10. }

    订单/库存/账户业务微服务测试

    1. 添加订单:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
    2. 初次测试,t_order、t_storage、t_account 三个 表添加数据均符合预期;
    3. 给 AccountServiceImpl 添加超时,再次测试;
    4. 再次测试,当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1。而且由于feign的重试机制,账户余额还有可能被多次扣减;
    5. 使用 @GlobalTransactional 对 OrderServiceImpl 进行全局事务处理;
    6. 再次测试,下单后数据库数据并没有任何改变,记录都添加不进来。

    补充内容

    再看TC/TM/RM三大组件

    分布式事务的执行流程:

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

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

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

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

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

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

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

    源码逻辑:

    Spring Cloud Netflix 项目进入维护模式,将不再开发新的组件,SpringCloud性能上不 能满足互联企业的发展需求。但互联网发展又迫切需要解决微服务的方案,因此龙头企业阿里应运而生推出了Spring Cloud Alibaba新一代的微服务架构解决方案。

    如果你还没有掌握这套主流技术,现在想要在最短的时间里吃透它。

    那么这份SpringCloud Alibaba实战笔记你一定不能错过!(还有一份SpringCloud Alibaba理论知识笔记同步打包分享)

    免费获取方式:需要这份 《SpringCloud Alibaba实战笔记》 请点赞、转发、收藏之后发送 私信【Spring】 获取完整下载路径的方式!

  • 相关阅读:
    软考2020高级架构师下午案例分析第4题:关于Redis数据类型、持久化、内存淘汰机制
    android之TextView自由选择复制
    【核酸检测人员安排】python实现-附ChatGPT解析
    端口转发及防火墙过滤设置
    ubuntu 系统解决GitHub无法访问问题
    网络层的七七八八
    gammatone 滤波器详解及其MATLAB代码实现
    05_SpingBoot 集成MyBatis【逆向工程】
    “配置DHCP服务器和DHCP中继的网络自动配置实验“
    vue-puzzle-vcode完成验证码拖拽
  • 原文地址:https://blog.csdn.net/Candyz7/article/details/128197649