Skip to content

SpringCloudAlibaba微服务框架入门

约 8791 字大约 29 分钟

SpringCloudAlibaba官网

SpringCloudAlibaba GitHub

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

Nacos (NamingConfigurationService缩写),一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。(相当于eureka+config+bus)

Nacos服务器源码打包,部署

  1. git下载nacos源码
   git clone https://github.com/alibaba/nacos.git
  1. 修改nacos配置文件,添加数据库配置
   #*************** Config Module Related Configurations ***************#
   ### If use MySQL as datasource:
   spring.datasource.platform=mysql
   
   ### Count of DB:
   db.num=1
   
   ### Connect URL of DB:
   db.url.0=jdbc:mysql://172.16.211.101:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
   db.user.0=root
   db.password.0=Leej090710.
  1. mysql数据库中创建nacos_config数据库,并导入数据库数据
   create databases nacos_config;
   use nacos_config;
   source /app/nacos/conf/nacos-config.sql;
  1. 启动nacos代码,启动类在console模块中

  2. 浏览器访问localhost:8848/nacos进入后台管理界面

Nacos服务注册中心配置

生产者微服务

  1. 创建项目,添加依赖
   <dependencies>
       <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-starter-test</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
   </dependencies>
  1. 启动类
   //Nacos支持服务发现注解
   @EnableDiscoveryClient
   @SpringBootApplication
   public class NacosProvicer9001Application {
       public static void main(String[] args) {
           SpringApplication.run(NacosProvicer9001Application.class);
       }
   }
  1. 配置文件
   server.port=9001
   spring.application.name=cloud-nacos-provider
   # 配置nacos服务地址
   spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
   management.endpoints.web.exposure.include=*
  1. 创建测试controller
   @RequestMapping("/nacos/payment")
   @RestController
   public class NacosPaymentController {
       @Value("${server.port}")
       private String currPort;
   
       @GetMapping("/test")
       public String getTest(){
           return "Nacos 服务生产者已启动,端口号:"+currPort+"\t==========>"+System.currentTimeMillis();
       }
   }
  1. 启动服务,浏览器访问:localhost:9001/nacos/payment/test,测试服务部署是否成功

  2. IDEA中通过run configuration界面复制9001服务启动器,添加VM options参数,创建和9001同样的微服务

   -DServer.port=9003

消费者微服务

  1. pom文件、启动类、配置文件和生产者相同

  2. 添加RestTemplate配置类

   @Configuration
   public class AppRestTemplateConfig {
     	//nacos继承了ribbon,原生支持负载均衡
       @LoadBalanced
       @Bean
       public RestTemplate getRestTemplate(){
           return new RestTemplate();
       }
   }
  1. 创建controller类调用生产者服务
   @RestController
   @RequestMapping("/nacos/order")
   @Slf4j
   public class NacosOrderController {
       @Autowired
       private RestTemplate restTemplate;
   
       private String PROVIDER_URL="http://cloud-nacos-provider";
   
   
       @GetMapping("/test")
       public String testPayment(){
           return restTemplate.getForObject(PROVIDER_URL+"/nacos/payment/test",String.class);
       }
   }
  1. 启动服务,浏览器访问http://localhost:83/nacos/order/test,并多次调用,查看是否实现负载均衡

服务注册中心对比

NacosEurekaConsulCoreDNSZookeeper
一致性协议cp+apapcp/cp
健康检查TCP/HTTP/MySQL/ClientBeatClientBeatTCP/HTTP/gRPC/Cmd/ClientBeat
负载均衡权重/DSL/metadata/CMDBRibbonFabioRR/
雪崩保护支持支持不支持不支持不支持
自动注销实例支持支持不支持不支持支持
访问协议HTTP/DNS/UDPHTTPHTTP/DNSDNSTCP
监听支持支持支持支持不支持支持
多数据中心支持支持支持不支持不支持
跨注册中心支持不支持支持不支持不支持
SpringCloud集成支持支持支持不支持不支持
Dubbo集成支持不支持不支持不支持支持
K8s集成支持不支持支持支持不支持

Nacos支持CP和AP一致性协议切换。(其中,C指数据一致性,即所有节点在同一时间看到的数据都是一致的;A指高可用性,即对所有节点的请求都会得到响应;)。

  1. 如果不需要存储服务级别的信息切服务实例通过nacos-client注册,并能够保持心跳上报,就可以选择AP模式,当前主流服务,如SpringCloud和Dubbo都适用于AP模式,AP模式为了服务的可用性牺牲了一致性,因此,AP模式下只支持注册临时实例。

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

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

Nacos做服务配置中心

基本配置

  1. Nacos管理页面,配置管理 > 配置列表 > +进入新增配置文件界面

  2. 创建项目,添加nacos配置客户端依赖、服务发现依赖

   <dependencies>
       <!--nacos 配置中心-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
       </dependency>
       <!--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>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
   </dependencies>
  1. 编辑bootstrap.properties配置文件
   server.port=3377
   spring.application.name=nacos-config-client
   spring.cloud.nacos.discovery.server-addr=localhost:8848
   spring.cloud.nacos.config.server-addr=localhost:8848
   # 指定配置文件格式为properties文件
   spring.cloud.nacos.config.file-extension=properties
   spring.profiles.active=dev
   # 配置文件匹配格式如下,即nacos-config-client-dev.properties
   # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
  1. 创建启动类
   @SpringBootApplication
   @EnableDiscoveryClient
   public class NacosConfigClientApp {
       public static void main(String[] args) {
           SpringApplication.run(NacosConfigClientApp.class);
       }
   }
  1. 创建controller类
   @RestController
   @RequestMapping("/nacos/config")
   @RefreshScope
   @Slf4j
   public class NacosConfigController {
       @Value("${server.port}")
       private String currPort;
       @Value("${config.info}")
       private String configInfo;
   
       @GetMapping("/info")
       public String getConfigInfo(){
           String res="Nacos Config Client: "+currPort+"\tMy Config Info = "+configInfo;
           log.info(res);
           return res;
       }
   }
  1. 启动服务,获取配置信息

  2. Nacos管理页面修改配置文件,重新获取配置信息,可见配置信息已经同步变化

分类配置

命名空间 > 分组 > 服务(namespace > group > service),默认情况下namespace=public,group=DEFAULT_GROUP,Cluster=default

namespace主要用来实现隔离,如存在开发、生产、测试三个环境,可以创建三个namespace,不同namespace之间相互隔离。

group可以吧不同的微服务划分到不同的分组里,不同的分组使用不同的配置信息。

service表示服务,一个service可以包含多个cluster(集群),Cluster是对微服务的虚拟划分,如:杭州机房的微服务集群名称为HZ,广州机房的微服务集群名称为GZ,可以让同一个机房中的微服务互相调用,提升性能。

instance表示微服务实例。

分组配置

  1. 在nacos管理页面创建配置文件时,修改GROUP名称,如:DEV_GROUP

  2. 在微服务端增加GROUP的配置

   spring.cloud.nacos.config.group=DEV_GROUP
  1. 浏览器访问获取最新配置信息

命名空间配置

  1. 在nacos管理页面 > 命名空间中新增命名空间,如:sahnghai

  2. 在配置管理页面可以看到多了shanghai选项,在此页中新增配置文件

  3. 微服务端增加namespace的配置

   # 此处应该是namespace id,不是namespace名称
   spring.cloud.nacos.config.namespace=b433593d-0064-47a4-82b7-348ca60a7098
  1. 浏览器访问获取最新配置

Nacos集群和持久化配置

Nacos默认自带嵌入式数据库derby。 0.7 版本之后,开始支持MySQL作为一致性存储。配置集群时,需要切换到Mysql数据库作为默认存储。Nacos解压后,在conf文件中有一个nacos-config.sql文件,为mysql数据库初始化文件,需要提前将数据库信息初始化到MySQL中。

持久化配置

实现持久化配置需要在conf文件下的application.properties文件中新增以下配置,重启后即可实现持久化配置

#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://172.16.211.101:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=Leej090710.

集群配置

  1. 下载Nacos包,并解压文件

  2. 修改application.properties配置文件,实现持久化

  3. 修改conf/cluster.conf配置文件

   172.16.211.101:8846
   172.16.211.101:8847
   172.16.211.101:8848
  1. 修改bin/startup.sh文件,实现按不同端口号启动
   # 增加参数a
   while getopts ":m:f:s:c:p:a:" opt
   do
       case $opt in
           a)  
               PORT=$OPTARG;;
           m)  
               MODE=$OPTARG;;
           f)  
               FUNCTION_MODE=$OPTARG;;
           s)  
               SERVER=$OPTARG;;
           c)  
               MEMBER_LIST=$OPTARG;;
           p)  
               EMBEDDED_STORAGE=$OPTARG;;
           ?)  
           echo "Unknown parameter"
           exit 1;; 
       esac
   done
   
   # 143行
   nohup $JAVA -Dserver.port=${PORT} ${JAVA_OPT} nacos.nacos >> ${BASE_DIR}/logs/start.out 2>&1 &
  1. 配置nginx,作为nacos集群vip,配置文件为/usr/local/nginx/conf/nginx.confnginx安装
   #修改代理
   upstream cluster{
       server 127.0.0.1:8846;
       server 127.0.0.1:8847;
       server 127.0.0.1:8848;
   }
   #修改监听
   server {
       listen       1111;
       server_name  localhost;
   
       #修改代理
       location / {
           proxy_pass http://cluster;
       }
       
       error_page   500 502 503 504  /50x.html;
       location = /50x.html {
           root   html;
       }
   }
  1. 启动nacos集群
   sh ./bin/startup.sh -a 8846
   sh ./bin/startup.sh -a 8847
   sh ./bin/startup.sh -a 8848
   # 查看集群启动情况
   ps -ef|grep nacos|grep -v grep|wc -l

注意Nacos服务占用内存较大,单台Nacos服务需要虚拟机配置内存超过2G

  1. 启动nginx
   # 启动nginx
   ./naginx
   # 刷新配置文件
   ./nginx -s reload
   # 关闭nginx
   ./nginx -s stop
  1. 浏览器访问http://172.16.211.101:1111/nacos进入nacos管理界面

  2. nacos客户端配置文件nacos地址需要修改为nginx的访问路径,实现服务注册

Sentinel服务熔断和限流

Sentinel,面向云原生微服务的流量监控、熔断降级组件。Sentinel中文介绍Sentinel官网

HystrixSentinel
需要自己手动搭建监控平台单独一个组件,可以独立出来
没有web界面进行细粒度化的配置流量、速率监控,服务熔断、降级直接界面化的细粒度统一配置

Sentinel分为两部分,核心库和控制台。核心库(Java客户端)不依赖于任何框架库,能够运行于所有java运行时环境,同时对Dubbo/SpringCloud等框架有较好的支持;控制台(Dashboard)基于Springboot开发,打包后可以直接运行,不需要额外的tomcat应用器。

Sentinel控制台安装

  1. 下载Sentinel控制台jar包
  2. 执行java -jar命令,启动SentinelDashboard服务
  3. 浏览器访问localhost:8080进入管理界面,用户名、密码为sentinel

Sentinel客户端基本配置

  1. 创建项目,添加pom依赖
   <dependencies>
       <!--centinel依赖包-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
       </dependency>
       <!--持久化需要用到-->
       <dependency>
           <groupId>com.alibaba.csp</groupId>
           <artifactId>sentinel-datasource-nacos</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-openfeign</artifactId>
       </dependency>
       <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-starter-test</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
           <scope>runtime</scope>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
   </dependencies>
  1. 配置文件增加sentinel配置
   server.port=8401
   spring.application.name=cloud-sentinel-service
   spring.cloud.nacos.discovery.server-addr=172.16.211.101:1111
   # sentinel控制台位置
   spring.cloud.sentinel.transport.dashboard=localhost:8080
   # 默认为8719端口,被占用后会自动从8719 +1依次扫描,直到找到未被占用的端口
   spring.cloud.sentinel.transport.port=8719
   management.endpoints.web.exposure.include=*
  1. 创建服务监控测试controller
   @RestController
   @RequestMapping("/sentinel")
   public class FlowLimitController {
   
       @GetMapping("/test")
       public String test(){
           return "==========Test A===========";
       }
   
       @GetMapping("/test2")
       public String test2(){
           return "==========Test B===========";
       }
   }
  1. 访问监控测试,访问路径localhost:8401/sentinel/test

  2. Sentinel控制台查看微服务实时监控

SentinelDashboard配置流控规则

阈值类型

  1. QPS,表示每秒访问次数
  2. 单机阈值,表示每秒钟访问次数超过1次,则进入流量监控管理
  3. 线程数,表示处理请求时,如果线程数超过阈值,则进入流量监控管理

流控模式

  1. 直接,QPS超过1后,直接采取操作(失败、警告、等待)

  2. 关联,当关联的资源B达到阈值后(即B的QPS超过1),就限流自己。常用于支付接口达到阈值,限流订单接口

    以上配置保存后,通过postman集合访问/sentinel/text2接口,循环20次,每300毫秒访问一次。循环开始后浏览器访问/sentinel/test接口,可以看出,test接口返回Blocked by Sentinel (flow limiting)

  3. 链路

流控效果

  1. 快速失败,直接失败,返回默认失败结果,源码来自com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

  2. Warm Up(预热),阈值 / coldFactor=初始单机阈值,最开始阈值为初始单机阈值,经过预热时长后才会达到阈值。coldFactor(冷加载因子)默认为3,源码来自com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

    以上配置表示:QPS激增后,前10秒为预热时间,此时单机阈值为3 / 3 = 1,当QPS超过1则直接限流,10秒之后,单机阈值改为3,即当QPS超过3后限流。通常用于,秒杀系统在开启的瞬间会有很大访问量上来,预热是为了保护系统,慢慢的把流量放进来。

  3. 排队等待,所有请求进入系统后以均匀的速度通过,这种效果下,阈值类型必须是QPS,否则无效。

    以上配置表示,每秒处理1个请求,超过1个请求则排队等待(匀速排队),等待超时时间时间500毫秒。匀速排队方式会严格控制请求通过的时间,对应的是漏桶算法

    这种方式主要用于处理间隔性突发的流量。如,消息队列,在某一秒有大量的消息到来,而接下来的几秒处于空现状他,我们希望系统能够在接下来的空闲时间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

SentinelDashboard配置降级规则

Sentinel熔断降级会在调用链路中某一个资源出现不稳定状态(如调用超时或异常比例升高)时,对这个资源的调用进行限制,让请求快速失败,避免影响到其他资源而导致级联错误,当资源被降级后,在接下来的时间窗口之内,对该资源的调用都自动熔断(默认时抛出DegradeException异常),和Hystrix不同的是Sentinel的熔断没有半开状态。

降级策略包括:RT(平均响应时间)、异常比例、异常数三种

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

  2. 异常比例,QPS >= 5且异常比例(每秒异常总数占通过量的比值)值超过阈值时,触发降级,时间窗口期结束后,关闭降级;

  3. 异常数,异常数(分钟统计)超过阈值时,触发降级,时间窗口结束后,关闭降级。若窗口时间小于60s,则结束熔断状态后仍可能再进入熔断;

SentinelDashboard配置热点规则

热点参数限流文档

REST请求http://localhost:8080/payment?param1=1&param2=2中,对带参数param1的请求限流,对其他不带参数param1的请求不限流。这种限流规则需要使用热点限流规则配置实现。

热点限流规则配置

  1. 创建需要热点限流的方法(此方法需要带参数)并注解标明限流后的降级方法
   /**
    * @Desc: @SentinelResource注解表示Sentinel降级配置
    *          value属性:标识符
    *					如果不配置value属性,则资源名为rest地址,可在簇点链路中添加热点规则
    *          blockHandler属性:降级方法名,不注明此属性会直接返回500
    * @Author: MysteriousGT
    * @Date: 2021/3/9
    * @Param: [p1, p2]
    * @Return: java.lang.String
    */
   @GetMapping("/hot/key")
   @SentinelResource(value = "testHotKey",blockHandler = "dealTestHotKey")
   public String testHotKey(@RequestParam(required = false) String p1,@RequestParam(required = false)String p2){
       return "========Test Hot Key==========>\tp1="+p1+"\t,p2="+p2;
   }
   
   public String dealTestHotKey(String p1, String p2, BlockException be){
       return "。==进入热点key降级handler方法==。";
   }
  1. 启动服务,在SentinelDashboard管理界面新增热点规则

  2. 浏览器访问对应的url,1秒内只包含第一个参数的访问,超过两次的会降级

   # 下列请求会降级
   http://localhost:8401/sentinel/hot/key?p1=abc
   http://localhost:8401/sentinel/hot/key?p1=abc&p2=add
   # 不会降级
   http://localhost:8401/sentinel/hot/key?p2=add
   http://localhost:8401/sentinel/hot/key

参数例外项:配置当参数p1的值为某一固定值时,他的QPS值可以单独设定。

当此接口存在运行时异常时会直接报错,不会进入限流blockHandler方法。

SentinelDashboard系统自适应限流

系统自适应限流官方文档

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

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

  • 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 达到阈值即触发系统保护。

自定义服务降级(@SentinelResource)

自定义降级兜底方法,类似Hystrix,某个方法出现问题,直接执行对应的降级方法,在Hystrix中使用注解@HystrixCommand实现,在Sentinel中则使用@SentinelResource注解

自定义限流降级处理逻辑

  1. 创建限流降级处理类,类中创建两个方法
   /**
    * @Author: MysteriousGT
    * @Date: 2021/3/9 1:41 下午
    * @Desc: 自定义block handler类
    */
   public class CustomerBlockHandler {
       public static CommonResult handlerException(BlockException e) {
           return new CommonResult(444, "客户自定义全局block处理方法====>\t方法 1");
       }
   
       public static CommonResult handlerException2(BlockException e) {
           return new CommonResult(444, "客户自定义全局block处理方法===>\t方法 2");
       }
   }
  1. 在controller类的接口中使用@SentinelResource注解标明降级处理方法
   /**
    * @Desc: 使用自定义的Block异常处理类中的方法处理降级
    *          如果不使用blockHandlerClass属性,则使用此类中的方法处理阿尼龟
    * @Author: MysteriousGT
    * @Date: 2021/3/9
    * @Param: []
    * @Return: com.sunyog.springcloud.entities.CommonResult
    */
   @GetMapping("/customer/block/handler")
   @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class,
       blockHandler = "handlerException2")
   public CommonResult customerBlockHandler() {
       return new CommonResult(200, "客户自定义block处理方法", new Payment(2020L, "serial00000"));
   }
  1. 重启服务后,在sentinelDashboard页面新增限流规则

  2. 访问对应的接口,查看限流降级是否实现

自定义异常fallback

fallback也是@sentinelResource注解的一个属性,使用方法和blockHandler属性一样。fallback属性所指向的方法表示当发生异常或错误时,走fallback方法。

如果即配置了fallback,也配置了blockhandler,当即发生了异常,又发生了限流,则走blockHandler方法

Sentinel使用代码方式自定义限流的三个核心API

  1. SphU 用于定义规则
  2. Tracer 用于定义统计,如线程、QPS等
  3. ContextUtil 用于定义上下文

Sentinel对openFeign的支持

sentinel支持对openFeign形式的微服务调用的服务降级、熔断处理,实现步骤如下:

  1. 服务消费者添加openFeign依赖
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-openfeign</artifactId>
   </dependency>
  1. 配置文件添加feign对sentinel的支持
   # 激活feign对sentinel的支持
   feign.sentinel.enabled=true
  1. 启动类通过注解启动feign支持,使用@EnableFeignClients注解

  2. 定义openFeign微服务调用接口

   @FeignClient(value = "cloud-nacos-provider",fallback = PaymentFallbackService.class)
   public interface FeignPaymentService {
       @GetMapping("/nacos/payment/{id}")
       public CommonResult<Payment> getById(@PathVariable("id")Long id);
   }
  1. 实现openFeign微服务调用接口,用作fallback处理类
   @Component
   public class PaymentFallbackService implements FeignPaymentService{
       @Override
       public CommonResult<Payment> getById(Long id) {
           return new CommonResult<>(444,"feign sentinel服务降级返回");
       }
   }
  1. controller层调用openFeign微服务调用接口
   @Resource
   private FeignPaymentService feignPaymentService;
   
   @GetMapping("/feign/get/{id}")
   public CommonResult feignGetById(@PathVariable("id")Long id){
       return feignPaymentService.getById(id);
   }
  1. 启动微服务生产者、消费者,测试接口正常

  2. 当微服务生产者发生异常或所有微服务生产者都宕机,会启动服务熔断措施

    注意:此时所有微服务生产者全部宕机

Sentinel规则持久化

在不进行任何配置的情况下,Sentinel的限流规则时临时的。如果微服务重启,则直前配置的所有相关限流规则都消失。

实现Sentinel规则持久化需要将所有配置规则持久化进Nacos保存,只要Nacos里的配置不删除,针对配置过的流控规则就持续有效。

配置步骤为:

  1. sentinel客户端添加sentinel在nacos上持久化依赖
   <!--sentinel配置持久化需要用到-->
   <dependency>
       <groupId>com.alibaba.csp</groupId>
       <artifactId>sentinel-datasource-nacos</artifactId>
   </dependency>
  1. sentinel客户端配置文件中添加sentinel持久化数据源
   # sentinel持久化配置,nacos数据源
   spring.cloud.sentinel.datasource.ds1.nacos.server-addr=172.16.211.101:1111
   spring.cloud.sentinel.datasource.ds1.nacos.data-id=cloud-sentinel-service
   spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
   spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
   spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
  1. nacos配置列表中添加配置,DataId为第 2 部配置文件中标注的DataId
   [{
       "resource":"/sentinel/rate/byUrl",
       "limitApp":"default",
       "grade":1,
       "count":1,
       "strategy":0,
       "controlBehavior":0,
       "clusterMode":false
   }]

配置注释

   [{
       "resource":"/sentinel/rate/byUrl",//资源名称
       "limitApp":"default",//来源应用
       "grade":1,//阈值类型,0-线程数,1-QPS
       "count":1,//单机阈值
       "strategy":0,//流控模式,0-直接,1-关联,2-链路
       "controlBehavior":0,//流控效果,0-快速失效,1-warmUp,2-排队等待
       "clusterMode":false//是否集群
   }]
  1. 重启sentinel客户端服务,发送请求一次,可以在sentinel dashboard管理页面中看到配置的流控规则

  2. 流控规则配置只能在nacos页面修改,在sentinel dashboard上修改的配置不会被保存,服务重启后会还原

Seata处理分布式事务

分布式事务简介

分布式事务典型应用场景:

用户购买商品的业务逻辑需要由 3 个微服务提供支持,包括:

  1. 仓储服务:对给定的商品扣除仓储数量
  2. 订单服务:根据采购需求创建订单
  3. 支付服务:从用户账户中扣除余额

此时每个服务内部的数据一致性都能由本地的事物来保证,但是全局的数据一致性问题没有保证。

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

Seata简介

Seate(Simple Extensible Autonomour Transaction Architecture简单可扩展的自治事务框架)是一个分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

Seate官网

Seate下载地址,建议下载1.0.0GA版

Seata分布式事务处理过程中的一个ID和三个组件模型

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

Seata分布式事务的处理过程(默认AT模式)

  1. TM开启一个全局事务。TM向TC注册全局事务记录,全局事务创建成功并生成一个全局唯一的XID;
  2. XID在微服务调用链路中传播。按业务场景编排数据库、服务等资源;
  3. 在业务调用过程中,RM向TC注册分支事务,将其纳入到XID对全局事务的管辖;
  4. TM向TC发起针对XID的全局提交或回滚决议;
  5. TC调度XID下管辖的全部分支事务,汇总事务信息,决定分布式事务是提交还是回滚;
  6. TC通知所有RM,提交或回滚资源,整个分布式事务结束。

具体流程图如下:

其中,TC为Seata服务器,TM为事务发起方,即@GlobalTransaction注解标注的位置,RM为事务的数据参与方,可以理解为一个数据库即为一个RM

Seata服务端安装配置

  1. 下载Seata服务端程序;

  2. 修改registry配置文件,使用nacos作为服务注册中心,registry文件位置:seata/conf/registry.conf

   registry {
     # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
     # 修改位置1,配置nacos注册中心
     type = "nacos"
     loadBalance = "RandomLoadBalance"
     loadBalanceVirtualNodes = 10
   	# 修改2,nacos注册中心位置
     nacos {
       application = "seata-server"
       serverAddr = "172.16.211.101:1111"
       group = "SEATA_GROUP"
       namespace = ""
       cluster = "default"
       username = ""
       password = ""
     }
     eureka {
       serviceUrl = "http://localhost:8761/eureka"
       application = "default"
       weight = "1"
     }
     redis {
       serverAddr = "localhost:6379"
       db = 0
       password = ""
       cluster = "default"
       timeout = 0
     }
     zk {
       cluster = "default"
       serverAddr = "127.0.0.1:2181"
       sessionTimeout = 6000
       connectTimeout = 2000
       username = ""
       password = ""
     }
     consul {
       cluster = "default"
       serverAddr = "127.0.0.1:8500"
     }
     etcd3 {
       cluster = "default"
       serverAddr = "http://localhost:2379"
     }
     sofa {
       serverAddr = "127.0.0.1:9603"
       application = "default"
       region = "DEFAULT_ZONE"
       datacenter = "DefaultDataCenter"
       cluster = "default"
       group = "SEATA_GROUP"
       addressWaitTime = "3000"
     }
     file {
       name = "file.conf"
     }
   }
   
   config {
     # file、nacos 、apollo、zk、consul、etcd3
     type = "file"
   
     nacos {
       serverAddr = "127.0.0.1:8848"
       namespace = ""
       group = "SEATA_GROUP"
       username = ""
       password = ""
     }
     consul {
       serverAddr = "127.0.0.1:8500"
     }
     apollo {
       appId = "seata-server"
       apolloMeta = "http://192.168.1.204:8801"
       namespace = "application"
       apolloAccesskeySecret = ""
     }
     zk {
       serverAddr = "127.0.0.1:2181"
       sessionTimeout = 6000
       connectTimeout = 2000
       username = ""
       password = ""
     }
     etcd3 {
       serverAddr = "http://localhost:2379"
     }
     file {
       name = "file.conf"
     }
   }
  1. 修改file配置文件,位置seata/conf/file.conf
   ## transaction log store, only used in seata-server
   store {
     ## store mode: file、db、redis
     # 修改1,使用数据库存储
     mode = "db"
   
     ## file store property
     file {
       ## store location dir
       dir = "sessionStore"
       # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
       maxBranchSessionSize = 16384
       # globe session size , if exceeded throws exceptions
       maxGlobalSessionSize = 512
       # file buffer size , if exceeded allocate new buffer
       fileWriteBufferCacheSize = 16384
       # when recover batch read size
       sessionReloadReadSize = 100
       # async, sync
       flushDiskMode = async
     }
   
     ## database store property
     #修改2,配置数据库信息
     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://172.16.211.101:3306/seata"
       user = "root"
       password = "Leej090710."
       minConn = 5
       maxConn = 100
       globalTable = "global_table"
       branchTable = "branch_table"
       lockTable = "lock_table"
       queryLimit = 100
       maxWait = 5000
     }
   
     ## redis store property
     redis {
       host = "127.0.0.1"
       port = "6379"
       password = ""
       database = "0"
       minConn = 1
       maxConn = 10
       maxTotal = 100
       queryLimit = 100
     }
   }
  1. 在配置的数据库中创建响应数据表,global_table;branch_table;lock_table
   -- -------------------------------- The script used when storeMode is 'db' --------------------------------
   -- the table to store GlobalSession data
   CREATE TABLE IF NOT EXISTS `global_table`
   (
       `xid`                       VARCHAR(128) NOT NULL,
       `transaction_id`            BIGINT,
       `status`                    TINYINT      NOT NULL,
       `application_id`            VARCHAR(32),
       `transaction_service_group` VARCHAR(32),
       `transaction_name`          VARCHAR(128),
       `timeout`                   INT,
       `begin_time`                BIGINT,
       `application_data`          VARCHAR(2000),
       `gmt_create`                DATETIME,
       `gmt_modified`              DATETIME,
       PRIMARY KEY (`xid`),
       KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
       KEY `idx_transaction_id` (`transaction_id`)
   ) ENGINE = InnoDB
     DEFAULT CHARSET = utf8;
   
   -- the table to store BranchSession data
   CREATE TABLE IF NOT EXISTS `branch_table`
   (
       `branch_id`         BIGINT       NOT NULL,
       `xid`               VARCHAR(128) NOT NULL,
       `transaction_id`    BIGINT,
       `resource_group_id` VARCHAR(32),
       `resource_id`       VARCHAR(256),
       `branch_type`       VARCHAR(8),
       `status`            TINYINT,
       `client_id`         VARCHAR(64),
       `application_data`  VARCHAR(2000),
       `gmt_create`        DATETIME(6),
       `gmt_modified`      DATETIME(6),
       PRIMARY KEY (`branch_id`),
       KEY `idx_xid` (`xid`)
   ) ENGINE = InnoDB
     DEFAULT CHARSET = utf8;
   
   -- the table to store lock data
   CREATE TABLE IF NOT EXISTS `lock_table`
   (
       `row_key`        VARCHAR(128) NOT NULL,
       `xid`            VARCHAR(128),
       `transaction_id` BIGINT,
       `branch_id`      BIGINT       NOT NULL,
       `resource_id`    VARCHAR(256),
       `table_name`     VARCHAR(32),
       `pk`             VARCHAR(36),
       `gmt_create`     DATETIME,
       `gmt_modified`   DATETIME,
       PRIMARY KEY (`row_key`),
       KEY `idx_branch_id` (`branch_id`)
   ) ENGINE = InnoDB
     DEFAULT CHARSET = utf8;
  1. 启动Seata服务端程序
   sh seata-server.sh
  1. 注意:registry.conf和file.conf两个配置文件模版位置:https://github.com/seata/seata/tree/develop/script

微服务中使用Seata解决分布式事务问题

  1. 创建seata-order-service2001seata-storage-service2002seata-account-service2003三个微服务项目分别对应订单服务、库存服务、账户服务调用顺序为:
  1. 在三个微服务项目的resource目录下创建registry.conffile.conf两个配置文件,配置信息如下:

    1. file.conf
   transport {
     # tcp, unix-domain-socket
     type = "TCP"
     #NIO, NATIVE
     server = "NIO"
     #enable heartbeat
     heartbeat = true
     # the client batch send request enable
     enableClientBatchSendRequest = true
     #thread factory for netty
     threadFactory {
       bossThreadPrefix = "NettyBoss"
       workerThreadPrefix = "NettyServerNIOWorker"
       serverExecutorThread-prefix = "NettyServerBizHandler"
       shareBossWorker = false
       clientSelectorThreadPrefix = "NettyClientSelector"
       clientSelectorThreadSize = 1
       clientWorkerThreadPrefix = "NettyClientWorkerThread"
       # netty boss thread size
       bossThreadSize = 1
       #auto default pin or 8
       workerThreadSize = "default"
     }
     shutdown {
       # when destroy server, wait seconds
       wait = 3
     }
     serialization = "seata"
     compressor = "none"
   }
   service {
     #transaction service group mapping
     # my_test_tx_group为配置的分组信息
     vgroup_mapping.my_test_tx_group = "default"
     #only support when registry.type=file, please don't set multiple addresses
     default.grouplist = "127.0.0.1:8091"
     #degrade, current not support
     enableDegrade = false
     #disable seata
     disableGlobalTransaction = false
   }
   
   client {
     rm {
       asyncCommitBufferLimit = 10000
       lock {
         retryInterval = 10
         retryTimes = 30
         retryPolicyBranchRollbackOnConflict = true
       }
       reportRetryCount = 5
       tableMetaCheckEnable = false
       tableMetaCheckerInterval = 60000
       reportSuccessEnable = false
       sagaBranchRegisterEnable = false
       sagaJsonParser = jackson
       sagaRetryPersistModeUpdate = false
       sagaCompensatePersistModeUpdate = false
     }
     tm {
       commitRetryCount = 5
       rollbackRetryCount = 5
       defaultGlobalTransactionTimeout = 60000
       degradeCheck = false
       degradeCheckPeriod = 2000
       degradeCheckAllowTimes = 10
     }
     undo {
       dataValidation = true
       onlyCareUpdateColumns = true
       logSerialization = "jackson"
       logTable = "undo_log"
       compress {
         enable = true
         # allow zip, gzip, deflater, 7z, lz4, bzip2, default is zip
         type = zip
         # if rollback info size > threshold, then will be compress
         # allow k m g t
         threshold = 64k
       }
     }
   }
   log {
     exceptionRate = 100
   }
  1. registry.conf
   registry {
       # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
    type = "file"
    loadBalance = "RandomLoadBalance"
    loadBalanceVirtualNodes = 10
   
    nacos {
      application = "seata-server"
      serverAddr = "172.16.211.101:1111"
      group = "SEATA_GROUP"
      namespace = ""
      username = ""
      password = ""
    }
    eureka {
      serviceUrl = "http://localhost:8761/eureka"
      weight = "1"
    }
    redis {
      serverAddr = "localhost:6379"
      db = "0"
      password = ""
      timeout = "0"
    }
    zk {
      serverAddr = "127.0.0.1:2181"
      sessionTimeout = 6000
      connectTimeout = 2000
      username = ""
      password = ""
    }
    consul {
      serverAddr = "127.0.0.1:8500"
      aclToken = ""
    }
    etcd3 {
      serverAddr = "http://localhost:2379"
    }
    sofa {
      serverAddr = "127.0.0.1:9603"
      region = "DEFAULT_ZONE"
      datacenter = "DefaultDataCenter"
      group = "SEATA_GROUP"
      addressWaitTime = "3000"
    }
    file {
      name = "file.conf"
    }
    custom {
      name = ""
    }
   }
   
   config {
    # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig、custom
    type = "file"
   
    nacos {
      serverAddr = "127.0.0.1:8848"
      namespace = ""
      group = "SEATA_GROUP"
      username = ""
      password = ""
      dataId = "seata.properties"
    }
    consul {
      serverAddr = "127.0.0.1:8500"
      aclToken = ""
    }
    apollo {
      appId = "seata-server"
      apolloMeta = "http://192.168.1.204:8801"
      namespace = "application"
      apolloAccesskeySecret = ""
    }
    zk {
      serverAddr = "127.0.0.1:2181"
      sessionTimeout = 6000
      connectTimeout = 2000
      username = ""
      password = ""
    }
    etcd3 {
      serverAddr = "http://localhost:2379"
    }
    file {
      name = "file.conf"
    }
    custom {
      name = ""
    }
   }
  1. 创建各自的配置文件,添加相应的配置信息
   # 不同微服务配置各自的端口号
   server.port=2001
   # 不同微服务配置各自的服务名称
   spring.application.name=seata-order-service
   # nacos服务注册中心地址
   spring.cloud.nacos.discovery.server-addr=172.16.211.101:1111
   # 不同微服务配置各自的数据库信息
   spring.datasource.driver-class-name=com.mysql.jdbc.Driver
   spring.datasource.url=jdbc:mysql://172.16.211.101:3306/seata_order
   spring.datasource.username=root
   spring.datasource.password=Leej090710.
   # mybatis配置
   mybatis.mapper-locations=classpath*:/mapper/*.xml
   mybatis.configuration.map-underscore-to-camel-case=true
   # feign关闭hystrix
   feign.hystrix.enabled=false
   # 设定日志级别
   logging.level.root=info
   
   # seata配置
   spring.cloud.alibaba.seata.enabled=true
   # 此处配置自定义的seata事务分组名称
   spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
  1. 微服务中添加配置类,配置使用seata代理mybatis数据源
   package com.sunyog.cloudalibaba.config;
   
   import javax.sql.DataSource;
   
   import org.apache.ibatis.session.SqlSessionFactory;
   import org.mybatis.spring.SqlSessionFactoryBean;
   import org.mybatis.spring.annotation.MapperScan;
   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 com.alibaba.druid.pool.DruidDataSource;
   
   import io.seata.rm.datasource.DataSourceProxy;
   
   /**
    * @Author: MysteriousGT
    * @Date: 2021/3/10 3:08 下午
    * @Desc: 使用seata代理数据源
    */
   @Configuration
   @MapperScan(value = {"com.sunyog.cloudalibaba.dao"})
   public class MybatisConfig {
       @Value("${mybatis.mapper-locations}")
       private String mapperLocations;
   
       @Bean
       @ConfigurationProperties(prefix = "spring.datasource")
       public DataSource dataSource() {
           return new DruidDataSource();
       }
   
       @Bean
       public DataSourceProxy dataSourceProxy(DataSource dataSource) {
           return new DataSourceProxy(dataSource);
       }
   
       @Bean
       public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
           SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
           sqlSessionFactoryBean.setDataSource(dataSourceProxy);
           sqlSessionFactoryBean
               .setMapperLocations(new PathMatchingResourcePatternResolver().getResources(this.mapperLocations));
           org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
           configuration.setMapUnderscoreToCamelCase(true);
           sqlSessionFactoryBean.setConfiguration(configuration);
           sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
           return sqlSessionFactoryBean.getObject();
       }
   }
  1. 创建业务数据库seata_orderseata_accountseata_storage,分别在不同数据库中创建相关业务表,sql脚本如下:
   /*
    seata_order库
   */
   use seata_order;
   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 AUTO_INCREMENT COMMENT 'id',
     `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
     `product_id` bigint(11) DEFAULT NULL COMMENT '产品ID',
     `count` int(11) DEFAULT NULL COMMENT '数量',
     `money` decimal(11,2) DEFAULT NULL COMMENT '金额',
     `status` int(1) DEFAULT '0' COMMENT '订单状态:0创建中,1已完结',
     PRIMARY KEY (`id`)
   ) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
   
   SET FOREIGN_KEY_CHECKS = 1;
   /*
    seata_storage库
   */
   use seata_storage;
   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 AUTO_INCREMENT COMMENT 'id',
     `product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
     `total` int(11) DEFAULT NULL COMMENT '总库存',
     `used` int(11) DEFAULT NULL COMMENT '已使用库存',
     `residue` int(11) DEFAULT NULL COMMENT '剩余库存',
     PRIMARY KEY (`id`)
   ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
   
   SET FOREIGN_KEY_CHECKS = 1;
   /*
    seata_account库
   */
   use seata_account;
   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 AUTO_INCREMENT COMMENT 'id',
     `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
     `total` decimal(11,2) DEFAULT NULL COMMENT '总额度',
     `used` decimal(11,2) DEFAULT NULL COMMENT '已使用额度',
     `residue` decimal(11,2) DEFAULT '0.00' COMMENT '可用余额',
     PRIMARY KEY (`id`)
   ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
   
   SET FOREIGN_KEY_CHECKS = 1;
  1. 针对不同业务,添加相关业务代码,在主服务中(订单服务)业务代码中添加@GlobalTranscation注解开启分布式事务,代码位置:https://gitee.com/sunyog/springcloud

  2. 在每个业务数据库中创建undo_log表,用于存储分布式事务过程数据,数据库执行脚本如下:

   SET NAMES utf8mb4;
   SET FOREIGN_KEY_CHECKS = 0;
   
   -- ----------------------------
   -- Table structure for undo_log
   -- ----------------------------
   DROP TABLE IF EXISTS `undo_log`;
   CREATE TABLE `undo_log` (
     `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
     `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
     `xid` varchar(100) NOT NULL COMMENT 'global transaction id',
     `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
     `rollback_info` longblob NOT NULL COMMENT 'rollback info',
     `log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
     `log_created` datetime NOT NULL COMMENT 'create datetime',
     `log_modified` datetime NOT NULL COMMENT 'modify datetime',
     PRIMARY KEY (`id`),
     UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
   ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';
   
   SET FOREIGN_KEY_CHECKS = 1;
  1. 启动以上三个微服务,使用POSTMAN调用创建订单接口,主服务和子服务提交/回滚日志如下:
   # 子服务回滚日志
   2021-03-11 15:56:37.399  INFO 10932 --- [atch_RMROLE_1_8] i.s.r.d.undo.AbstractUndoExecutor        : Stop rollback because there is no data change between the before data snapshot and the after data snapshot.
   2021-03-11 15:56:37.410  INFO 10932 --- [atch_RMROLE_1_8] i.s.r.d.undo.AbstractUndoLogManager      : xid 172.16.211.1:8091:113305726515941376 branch 113305752289939457, undo_log deleted with GlobalFinished
   2021-03-11 15:56:37.413  INFO 10932 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
   2021-03-11 15:56:37.439  INFO 10932 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=172.16.211.1:8091:113305726515941376,branchId=113305751216197633,branchType=AT,resourceId=jdbc:mysql://172.16.211.101:3306/seata_storage,applicationData=null
   2021-03-11 15:56:37.439  INFO 10932 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 172.16.211.1:8091:113305726515941376 113305751216197633 jdbc:mysql://172.16.211.101:3306/seata_storage
   2021-03-11 15:56:37.466  INFO 10932 --- [atch_RMROLE_1_8] i.s.r.d.undo.AbstractUndoLogManager      : xid 172.16.211.1:8091:113305726515941376 branch 113305751216197633, undo_log deleted with GlobalFinished
   2021-03-11 15:56:37.468  INFO 10932 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
   
   #子服务提交日志
   2021-03-11 15:59:48.301  INFO 10932 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=172.16.211.1:8091:113306549954285568,branchId=113306551229353985,branchType=AT,resourceId=jdbc:mysql://172.16.211.101:3306/seata_storage,applicationData=null
   2021-03-11 15:59:48.303  INFO 10932 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch committing: 172.16.211.1:8091:113306549954285568 113306551229353985 jdbc:mysql://172.16.211.101:3306/seata_storage null
   2021-03-11 15:59:48.305  INFO 10932 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed
   
   #主服务提交日志
   2021-03-11 15:59:47.729  INFO 10912 --- [nio-2001-exec-5] i.seata.tm.api.DefaultGlobalTransaction  : [172.16.211.1:8091:113306549954285568] commit status: Committed
   2021-03-11 15:59:48.279  INFO 10912 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=172.16.211.1:8091:113306549954285568,branchId=113306550214332417,branchType=AT,resourceId=jdbc:mysql://172.16.211.101:3306/seata_order,applicationData=null
   2021-03-11 15:59:48.282  INFO 10912 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch committing: 172.16.211.1:8091:113306549954285568 113306550214332417 jdbc:mysql://172.16.211.101:3306/seata_order null
   2021-03-11 15:59:48.283  INFO 10912 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed
   2021-03-11 15:59:48.334  INFO 10912 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=172.16.211.1:8091:113306549954285568,branchId=113306553854988289,branchType=AT,resourceId=jdbc:mysql://172.16.211.101:3306/seata_order,applicationData=null
   2021-03-11 15:59:48.335  INFO 10912 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch committing: 172.16.211.1:8091:113306549954285568 113306553854988289 jdbc:mysql://172.16.211.101:3306/seata_order null
   2021-03-11 15:59:48.335  INFO 10912 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed
   
   #主服务回滚日志:==>无,任何一个字业务产生问题,主业务都会受到影响,一般会报错,没有回滚日志

Seata的分布式事务实现过程分析