jgcs123

导航

 

126_Sentinel系统规则

官方文档

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

 

系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

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

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

link

 

127_SentinelResource配置(上)

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

启动Nacos成功

启动Sentinel成功

Module - cloudalibaba-sentinel-service8401

@RestController
public class RateLimitController {
   
   @GetMapping("/byResource")
   @SentinelResource(value = "byResource",blockHandler = "handleException")
   public CommonResult byResource() {
       return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
  }
   
   public CommonResult handleException(BlockException exception) {
       return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
  }
}

 

配置流控规则

配置步骤

 

图形配置和代码关系

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

测试

1秒钟点击1下,OK

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

{"code":444, "message":"com.alibaba.csp.sentinel.slots.block.flow.FlowException\t 服务不可用", "data":null}

 

额外问题

此时关闭问服务8401 -> Sentinel控制台,流控规则消失了


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

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

业务类RateLimitController

@RestController
public class RateLimitController
{
...

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

 

Sentinel控制台配置

 

测试

上面兜底方案面临的问题

  1. 系统默认的,没有体现我们自己的业务要求。

  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。

  3. 每个业务方法都添加—个兜底的,那代码膨胀加剧。

  4. 全局统—的处理方法没有体现。

 

128_SentinelResource配置(中)

客户自定义限流处理逻辑

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

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

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

 

RateLimitController

@RestController
public class RateLimitController {
...

   @GetMapping("/rateLimit/customerBlockHandler")
   @SentinelResource(value = "customerBlockHandler",
           blockHandlerClass = CustomerBlockHandler.class,//<-------- 自定义限流处理类
           blockHandler = "handlerException2")//<-----------
   public CommonResult customerBlockHandler()
  {
       return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
  }
}

 

Sentinel控制台配置

 

启动微服务后先调用一次 - http://localhost:8401/rateLimit/customerBlockHandler。然后,多次快速刷新http://localhost:8401/rateLimit/customerBlockHandler。刷新后,我们自定义兜底方法的字符串信息就返回到前端。

 

129_SentinelResource配置(下)

@SentinelResource 注解

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为 EntryType.OUT

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException

  • fallback /fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:

    • 返回值类型必须与原函数返回值类型一致;

    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。

    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;

    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。

    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

link

 

Sentinel主要有三个核心Api:

  1. SphU定义资源

  2. Tracer定义统计

  3. ContextUtil定义了上下文

130_Sentinel服务熔断Ribbon环境预说

sentinel整合ribbon+openFeign+fallback

Ribbon系列

  • 启动nacos和sentinel

  • 提供者9003/9004

  • 消费者84

 

提供者9003/9004

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

POM

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

 

YML

server:
port: 9003

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

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

 

记得修改不同的端口号

主启动

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

 

业务类

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

   //模拟数据库
   public static HashMap<Long,Payment> hashMap = new HashMap<>();
   static
  {
       hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
       hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
       hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
  }

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

}

 

测试地址 - http://localhost:9003/paymentSQL/1


消费者84

新建cloudalibaba-consumer-nacos-order84

POM

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

 

YML

server:
port: 84

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

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

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

 

主启动

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

 

业务类

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

 

ApplicationContextConfig

@Configuration
public class ApplicationContextConfig {

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

 

CircleBreakerController

@RestController
@Slf4j
public class CircleBreakerController {
   public static final String SERVICE_URL = "http://nacos-payment-provider";

   @Resource
   private RestTemplate restTemplate;

   @RequestMapping("/consumer/fallback/{id}")
   @SentinelResource(value = "fallback")//没有配置
   public CommonResult<Payment> fallback(@PathVariable Long id)
  {
       CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

       if (id == 4) {
           throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
      }else if (result.getData() == null) {
           throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
      }

       return result;
  }
   
}

修改后请重启微服务

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

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

目的

  • fallback管运行异常

  • blockHandler管配置违规

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

没有任何配置

只配置fallback

只配置blockHandler

fallback和blockHandler都配置

忽略属性

131_Sentinel服务熔断无配置

没有任何配置 - 给用户error页面,不友好

@RestController
@Slf4j
public class CircleBreakerController {
   public static final String SERVICE_URL = "http://nacos-payment-provider";

   @Resource
   private RestTemplate restTemplate;

   @RequestMapping("/consumer/fallback/{id}")
   @SentinelResource(value = "fallback")//没有配置
   public CommonResult<Payment> fallback(@PathVariable Long id)
  {
       CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

       if (id == 4) {
           throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
      }else if (result.getData() == null) {
           throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
      }

       return result;
  }
   
}

132_Sentinel服务熔断只配置fallback

fallback只负责业务异常

@RestController
@Slf4j
public class CircleBreakerController {
   
   public static final String SERVICE_URL = "http://nacos-payment-provider";

   @Resource
   private RestTemplate restTemplate;

   @RequestMapping("/consumer/fallback/{id}")
   //@SentinelResource(value = "fallback")//没有配置
   @SentinelResource(value = "fallback", fallback = "handlerFallback") //fallback只负责业务异常
   public CommonResult<Payment> fallback(@PathVariable Long id) {
       CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

       if (id == 4) {
           throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
      }else if (result.getData() == null) {
           throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
      }

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

 

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

页面返回结果:

{"code":444,"message":"兜底异常nandlerFal1back, exception内容illegalkrgumentEBxceptiorn,非法参数异常……","data":{"id":4,"seria:"null"}}

 

133_Sentinel服务熔断只配置blockHandler

blockHandler只负责sentinel控制台配置违规

@RestController
@Slf4j
public class CircleBreakerController
{
   public static final String SERVICE_URL = "http://nacos-payment-provider";

   @Resource
   private RestTemplate restTemplate;

   @RequestMapping("/consumer/fallback/{id}")
   //@SentinelResource(value = "fallback") //没有配置
   //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
   @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
   public CommonResult<Payment> fallback(@PathVariable Long id)
  {
       CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

       if (id == 4) {
           throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
      }else if (result.getData() == null) {
           throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
      }

       return result;
  }
   //本例是fallback
/*   public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
       Payment payment = new Payment(id,"null");
       return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
   }*/
   
   //本例是blockHandler
   public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
       Payment payment = new Payment(id,"null");
       return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
  }
}

 

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

134_Sentinel服务熔断fallback和blockHandler都配置

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

@RestController
@Slf4j
public class CircleBreakerController
{
   public static final String SERVICE_URL = "http://nacos-payment-provider";

   @Resource
   private RestTemplate restTemplate;

   @RequestMapping("/consumer/fallback/{id}")
   //@SentinelResource(value = "fallback") //没有配置
   //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
   //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
   @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
   public CommonResult<Payment> fallback(@PathVariable Long id)
  {
       CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

       if (id == 4) {
           throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
      }else if (result.getData() == null) {
           throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
      }

       return result;
  }
   //本例是fallback
   public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
       Payment payment = new Payment(id,"null");
       return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
  }
   //本例是blockHandler
   public CommonResult blockHandler(@PathVariable  Long id,BlockException blockException) {
       Payment payment = new Payment(id,"null");
       return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
  }
}

 

135_Sentinel服务熔断exceptionsToIgnore

exceptionsToIgnore,忽略指定异常,即这些异常不用兜底方法处理。

@RestController
@Slf4j
public class CircleBreakerController    

  ...
   
   @RequestMapping("/consumer/fallback/{id}")
   @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
           exceptionsToIgnore = {IllegalArgumentException.class})//<-------------
   public CommonResult<Payment> fallback(@PathVariable Long id)
  {
       CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

       if (id == 4) {
           //exceptionsToIgnore属性有IllegalArgumentException.class,
           //所以IllegalArgumentException不会跳入指定的兜底程序。
           throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
      }else if (result.getData() == null) {
           throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
      }

       return result;
  }

...
}

136_Sentinel服务熔断OpenFeign

修改84模块

  • 84消费者调用提供者9003

  • Feign组件一般是消费侧

POM

<!--SpringCloud openfeign -->

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

 

YML

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

 

业务类

带@Feignclient注解的业务接口,fallback = PaymentFallbackService.class

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
   @GetMapping(value = "/paymentSQL/{id}")
   public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

 

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

 

Controller

@RestController
@Slf4j
public class CircleBreakerController {

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

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

 

主启动

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

 

测试 - http://localhost:84/consumer/paymentSQL/1

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

熔断框架比较

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

 

137_Sentinel持久化规则

是什么

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

怎么玩

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

步骤

修改cloudalibaba-sentinel-service8401

POM

<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

 

YML

server:
port: 8401

spring:
application:
  name: cloudalibaba-sentinel-service
cloud:
  nacos:
    discovery:
      server-addr: localhost:8848 #Nacos服务注册中心地址
  sentinel:
    transport:
      dashboard: localhost:8080 #配置Sentinel dashboard地址
      port: 8719
    datasource: #<---------------------------关注点,添加Nacos数据源配置
      ds1:
        nacos:
          server-addr: localhost:8848
          dataId: cloudalibaba-sentinel-service
          groupId: DEFAULT_GROUP
          data-type: json
          rule-type: flow

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

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

 

添加Nacos业务规则配置

 

配置内容解析

[{
   "resource": "/rateLimit/byUrl",
   "IimitApp": "default",
   "grade": 1,
   "count": 1,
   "strategy": 0,
   "controlBehavior": 0,
   "clusterMode": false
}]
  • resource:资源名称;

  • limitApp:来源应用;

  • grade:阈值类型,0表示线程数, 1表示QPS;

  • count:单机阈值;

  • strategy:流控模式,0表示直接,1表示关联,2表示链路;

  • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;

  • clusterMode:是否集群。

 

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

 

快速访问测试接口 - http://localhost:8401/rateLimit/byUrl - 页面返回Blocked by Sentinel (flow limiting)

停止8401再看sentinel - 停机后发现流控规则没有了

 

重新启动8401再看sentinel

138_分布式事务问题由来

分布式前

  • 单机单库没这个问题

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

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

 

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

139_Seata术语

是什么

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

官方网址

能干嘛

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

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

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

  • 三组件概念

    • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。

    • TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。

    • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

 

处理过程:

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

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

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

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

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

 

140_Seata-Server安装

去哪下

发布说明: https://github.com/seata/seata/releases

怎么玩

本地@Transactional

全局@GlobalTransactional

SEATA 的分布式交易解决方案

 

我们只需要使用一个 @GlobalTransactional 注解在业务方法上:

Seata-Server安装

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

下载版本 - 0.9.0

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

先备份原始file.conf文件

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

file.conf

service模块

service {
  ##fsp_tx_group是自定义的
  vgroup_mapping.my.test.tx_group="fsp_tx_group"
  default.grouplist = "127.0.0.1:8091"
  enableDegrade = false
  disable = false
  max.commitretry.timeout= "-1"
  max.ollbackretry.timeout= "-1"
}

 

store模块

## transaction log store
store {
## store mode: file, db
## 改成db
mode = "db"

## file store
file {
dir = "sessionStore"

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

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

 

mysql5.7数据库新建库seata,在seata库里建表

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

-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `global_table` (
 `xid` varchar(128)  not null,
 `transaction_id` bigint,
 `status` tinyint not null,
 `application_id` varchar(32),
 `transaction_service_group` varchar(32),
 `transaction_name` varchar(128),
 `timeout` int,
 `begin_time` bigint,
 `application_data` varchar(2000),
 `gmt_create` datetime,
 `gmt_modified` datetime,
 primary key (`xid`),
 key `idx_gmt_modified_status` (`gmt_modified`, `status`),
 key `idx_transaction_id` (`transaction_id`)
);

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

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

 

修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件

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

nacos {
## 加端口号
  serverAddr = "localhost:8848"
  namespace = ""
  cluster = "default"
}
...
}

 

目的是:指明注册中心为nacos,及修改nacos连接信息

先启动Nacos端口号8848 nacos\bin\startup.cmd

再启动seata-server - seata-server-0.9.0\seata\bin\seata-server.bat

 

141_Seata业务数据库准备

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

分布式事务业务说明

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

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

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

一言蔽之,下订单—>扣库存—>减账户(余额)。

创建业务数据库

  • seata_ order:存储订单的数据库;

  • seata_ storage:存储库存的数据库;

  • seata_ account:存储账户信息的数据库。

建库SQL

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

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

  • seata_order库下建t_order表

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

    SELECT * FROM t_order;

     

  • seata_storage库下建t_storage表

    CREATE TABLE t_storage (
    `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
    `total` INT(11) DEFAULT NULL COMMENT '总库存',
    `used` INT(11) DEFAULT NULL COMMENT '已用库存',
    `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
    ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

    INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
    VALUES ('1', '1', '100', '0','100');

    SELECT * FROM t_storage;

     

  • seata_account库下建t_account表

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

INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '1000', '0', '1000');

SELECT * FROM t_account;

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

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

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

  • 建表SQL

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

 

142_Seata之Order-Module配置搭建

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

seata-order-service2001

POM

    <dependencies>
       <!--nacos-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
       </dependency>
       <!--seata-->
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
           <exclusions>
               <exclusion>
                   <artifactId>seata-all</artifactId>
                   <groupId>io.seata</groupId>
               </exclusion>
           </exclusions>
       </dependency>
       <dependency>
           <groupId>io.seata</groupId>
           <artifactId>seata-all</artifactId>
           <version>0.9.0</version>
       </dependency>
       <!--feign-->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-openfeign</artifactId>
       </dependency>
       <!--web-actuator-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
       <!--mysql-druid-->
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>5.1.37</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid-spring-boot-starter</artifactId>
           <version>1.1.10</version>
       </dependency>
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.0.0</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
   </dependencies>

 

配置文件

YML

server:
port: 2001

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

feign:
hystrix:
  enabled: false

logging:
level:
  io:
    seata: info

mybatis:
mapperLocations: classpath:mapper/*.xml

 

file.conf

transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
  boss-thread-prefix = "NettyBoss"
  worker-thread-prefix = "NettyServerNIOWorker"
  server-executor-thread-prefix = "NettyServerBizHandler"
  share-boss-worker = false
  client-selector-thread-prefix = "NettyClientSelector"
  client-selector-thread-size = 1
  client-worker-thread-prefix = "NettyClientWorkerThread"
  # netty boss thread size,will not be used for UDT
  boss-thread-size = 1
  #auto default pin or 8
  worker-thread-size = 8
}
shutdown {
  # when destroy server, wait seconds
  wait = 3
}
serialization = "seata"
compressor = "none"
}

service {

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

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


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

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

## file store
file {
  dir = "sessionStore"

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

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

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

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

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

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

support {
## spring
spring {
  # auto proxy the DataSource bean
  datasource.autoproxy = false
}
}

 

registry.conf

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

nacos {
  serverAddr = "localhost:8848"
  namespace = ""
  cluster = "default"
}
eureka {
  serviceUrl = "http://localhost:8761/eureka"
  application = "default"
  weight = "1"
}
redis {
  serverAddr = "localhost:6379"
  db = "0"
}
zk {
  cluster = "default"
  serverAddr = "127.0.0.1:2181"
  session.timeout = 6000
  connect.timeout = 2000
}
consul {
  cluster = "default"
  serverAddr = "127.0.0.1:8500"
}
etcd3 {
  cluster = "default"
  serverAddr = "http://localhost:2379"
}
sofa {
  serverAddr = "127.0.0.1:9603"
  application = "default"
  region = "DEFAULT_ZONE"
  datacenter = "DefaultDataCenter"
  cluster = "default"
  group = "SEATA_GROUP"
  addressWaitTime = "3000"
}
file {
  name = "file.conf"
}
}

config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"

nacos {
  serverAddr = "localhost"
  namespace = ""
}
consul {
  serverAddr = "127.0.0.1:8500"
}
apollo {
  app.id = "seata-server"
  apollo.meta = "http://192.168.1.204:8801"
}
zk {
  serverAddr = "127.0.0.1:2181"
  session.timeout = 6000
  connect.timeout = 2000
}
etcd3 {
  serverAddr = "http://localhost:2379"
}
file {
  name = "file.conf"
}
}

 

domain

...

143_Seata之Order-Module撸码(上)

Dao接口及实现

...

Service接口及实现

  • OrderService

    • OrderServiceImpl

  • StorageService

  • AccountService

 

144_Seata之Order-Module撸码(下)

Controller

...

Config配置

  • MyBatisConfig

  • DataSourceProxyConfig

 

主启动

 

145_Seata之Storage-Module说明

与seata-order-service2001模块大致相同

seata- storage - service2002

POM(与seata-order-service2001模块大致相同)

YML

server:
port: 2002

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

logging:
level:
  io:
    seata: info

mybatis:
mapperLocations: classpath:mapper/*.xml

 

file.conf(与seata-order-service2001模块大致相同)

registry.conf(与seata-order-service2001模块大致相同)

domain

...

CommonResult(与seata-order-service2001模块大致相同)

Dao接口及实现

...

Service接口及实现

...

Controller

...

Config配置(与seata-order-service2001模块大致相同)

主启动(与seata-order-service2001模块大致相同)

146_Seata之Account-Module说明

与seata-order-service2001模块大致相同

seata- account- service2003

POM(与seata-order-service2001模块大致相同)

YML

server:
port: 2003

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

feign:
hystrix:
  enabled: false

logging:
level:
  io:
    seata: info

mybatis:
mapperLocations: classpath:mapper/*.xml

 

file.conf(与seata-order-service2001模块大致相同)

registry.conf(与seata-order-service2001模块大致相同)

domain

...

CommonResult(与seata-order-service2001模块大致相同)

Dao接口及实现

...

Service接口及实现

...

Controller

...

Config配置(与seata-order-service2001模块大致相同)

主启动(与seata-order-service2001模块大致相同)

147_Seata之@GlobalTransactional验证

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

数据库初始情况:

 

正常下单 - http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

数据库正常下单后状况:

 

超时异常,没加@GlobalTransactional

模拟AccountServiceImpl添加超时

@Service
public class AccountServiceImpl implements AccountService {

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


   @Resource
   AccountDao accountDao;

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

 

另外,OpenFeign的调用默认时间是1s以内,所以最后会抛异常。

数据库情况

 

故障情况

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

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

 

超时异常,加了@GlobalTransactional

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

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
   
  ...

   /**
    * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
    * 简单说:下订单->扣库存->减余额->改状态
    */
   @Override
   //rollbackFor = Exception.class表示对任意异常都进行回滚
   @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
   public void create(Order order)
  {
...
  }
}

 

还是模拟AccountServiceImpl添加超时,下单后数据库数据并没有任何改变,记录都添加不进来,达到出异常,数据库回滚的效果

148_Seata之原理简介

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

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

2020起始,用1.0以后的版本。Alina Gingertail

 

分布式事务的执行流程

  • TM开启分布式事务(TM向TC注册全局事务记录) ;

  • 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态) ;

  • TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务) ;

  • TC汇总事务信息,决定分布式事务是提交还是回滚;

  • TC通知所有RM提交/回滚资源,事务二阶段结束。

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

  • 是什么

前提

  • 基于支持本地 ACID 事务的关系型数据库。

  • Java 应用,通过 JDBC 访问数据库。

整体机制

两阶段提交协议的演变:

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

  • 二阶段:

    • 提交异步化,非常快速地完成。

    • 回滚通过一阶段的回滚日志进行反向补偿。

link

 

  • 一阶段加载

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

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

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

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

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

 

  • 二阶段提交

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

 

  • 二阶段回滚

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

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

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

 

补充

 

(6条消息) Spring Cloud 学习笔记(3 / 3)_KISS-CSDN博客

 

posted on 2021-08-31 20:11  Dongdong98  阅读(21)  评论(0编辑  收藏  举报