SpringCloud相关知识
本文笔记跟随于尚硅谷雷丰阳老师的视频
目录
一.分布式基础
1.分布式基础知识图
2.相关技术栈
3.系统架构的演变
3.1.单体架构
3.2.集群架构
3.3.分布式架构
编辑编辑4.创建微服务项目
4.1环境准备
4.2版本选择
编辑 4.3项目结构图
二.Nacos(注册配置中心)
1.nacos示意图
1.1注册中心
1.2配置中心
2.下载安装
编辑
3.注册中心
3.1测试服务注册
3.2测试服务发现
3.3远程调用
3.3.1环境搭建及实现
3.3.2负载均衡的实现远程调用
3.4注册中心宕机了,远程调用服务还能用吗?
4.配置中心
4.1基本使用
4.2动态刷新
4.3Nacos中的数据集和application.properties有相同的配置项,哪个生效?
4.4数据隔离
5.Nacos总结
三.OpenFeign(远程调用组件)
1.OpenFeign远程调用流程
1.1声明式实现---业务间API的调用
1.2声明式实现--第三方API的调用
1.3面试题:客户端负载均衡与服务端负载均衡的区别
2.进阶配置
2.1日志
2.2超时控制
2.3超时配置
2.4重试机制
2.5拦截器
2.6兜底返回Fallback机制
3.总结
四.Sentinel(服务保护,限流熔断降级)
1.基础
1.1资源和规则编辑编辑
1.2工作原理
1.3整合使用
1.3.1控制台下载启动
1.3.2在服务里整合Sentinel场景
2.异常处理
2.1web接口异常处理
2.2@SentinelResource异常处理
2.3OpenFeign调用
编辑 3.流量控制规则(FlowRule)
3.1阈值类型
3.2流控模式
3.2.1直接策略
3.2.2链路策略
编辑 3.2.3关联策略
3.3流控效果
3.3.1快速失败(直接拒绝)
3.3.2Warm Up(预热/冷启动)
3.3.3匀速排队
4.熔断规则(DegradeRule)
4.1断路器工作原理
4.2熔断策略
4.2.1慢调用比列
4.2.2异常比例
4.2.3异常数
5.热点规则(热点参数限流)
5.1环境搭建
5.2热点参数限流
6.总结
五.Gateway(网关)
1.网关功能(核心:路由,断言,过滤器)
2.创建网关
3.路由
3.1规则配置
3.2工作原理
4.断言
4.1长短写法
4.2测试Query的断言规则
4.3自定义断言工厂
5.过滤器
编辑
5.1基本使用
5.2默认Filter
5.3自定义过滤器工厂
5.4全局跨域
6.总结
六.Seata(分布式事务)
1.基础
1.1组成
1.2环境搭建
1.3本地事务测试
1.4打通远程链路
1.5架构原理
1.6整合Seata
2.原理
2.1二阶提交协议流程
2.2四种事务模式
2.2.1AT模式
2.2.2XA模式
2.2.3TCC模式
2.2.4Saga模式
一.分布式基础
1.分布式基础知识图
2.相关技术栈
3.系统架构的演变
3.1.单体架构
3.2.集群架构
3.3.分布式架构

4.创建微服务项目
4.1环境准备
- 引入SpringCloud、SpringCloudAlibaba相关依赖
- 注意版本适配(不推荐使用最新的Spring Boot版本)
4.2版本选择
4.3项目结构图
二.Nacos(注册配置中心)
1.nacos示意图
1.1注册中心
1.2配置中心
2.下载安装
将压缩包解压到非中文无空格的目录下,进入bin目录,在目录列表输入cmd启动
启动命令: startup.cmd -m standalone
启动成功后可以看到8848的端口:
浏览器输入访问命令:http://localhost:8848/nacos
配置管理---->配置中心 服务管理---->服务中心 集群管理---->集群中心
至此,nacos下载安装完成!
3.注册中心
3.1测试服务注册
(1) 项目架构图:
(2)在两个服务的pom文件中导入web依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
(3)编写主程序启动类OrderMainApplication和ProductMainApplication
OrderMainApplication:
package com.zhang;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@EnableDiscoveryClient
@SpringBootApplication
public class OrderMainApplication {public static void main(String[] args) {SpringApplication.run(OrderMainApplication.class, args);}
}
ProductMainApplication:
package com.zhang;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ProductMainApplication {public static void main(String[] args) {SpringApplication.run(ProductMainApplication.class,args);}
}
(4)编写properties配置文件
order的:
spring.application.name=service-orderdemo
server.port=8000spring.cloud.nacos.server-addr=127.0.0.1:8848
product的:
spring.application.name=service-productdemo
server.port=9000spring.cloud.nacos.server-addr=127.0.0.1:8848
(5)启动服务,输入localhost:8848/nacos
在idea中可以直接复制配置多个微服务:
3.2测试服务发现
(1)先在主启动类中加上@EnableDiscoveryClient//开启服务发现功能
(2)编写测试类,测试DiscoveryClient和NacosServiceDiscovery两种服务发现API
package com.zhang;import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import com.alibaba.nacos.api.exception.NacosException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;import java.util.List;@SpringBootTest
public class OrderTest {@AutowiredDiscoveryClient discoveryClient;//使用哪个注册中心都可以调用的API@AutowiredNacosServiceDiscovery nacosServiceDiscovery;//使用nacos注册中心可以调用的API@Testvoid discoveryClientTest() {for (String service : discoveryClient.getServices()) {System.out.println("service:" + service);//获取ip主机地址和port端口号List<ServiceInstance> instances = discoveryClient.getInstances(service);for (ServiceInstance instance : instances) {System.out.println("id:" + instance.getHost() + ";" + "port:" + instance.getPort());}}}
//*********************************************************************************************************@Testvoid nacosServiceDiscoveryDiscoveryTest() throws NacosException {for (String service : nacosServiceDiscovery.getServices()) {System.out.println("service:"+service);//获取ip主机地址和port端口号List<ServiceInstance> instance =nacosServiceDiscovery.getInstances(service);for (ServiceInstance serviceInstance : instance) {System.out.println("ip:"+serviceInstance.getHost()+";"+"port:"+serviceInstance.getPort());}}}
}
3.3远程调用
3.3.1环境搭建及实现
代码演示:
实体类模块
订单和商品的实体类单独出一个model
订单:
package com.zhang.order.bean;import com.zhang.product.bean.Product;
import lombok.Data;import java.math.BigDecimal;
import java.util.List;@Data
public class Order {private int id;//订单idprivate BigDecimal totalAmount;//订单总金额private int userId;private String nickName;private String address;private List<Product> productList;}
商品:
package com.zhang.product.bean;import lombok.Data;import java.math.BigDecimal;@Data
public class Product {private int id;private String productName;private BigDecimal price;private int num;
}
订单模块
(1)controller
package com.zhang.order.controller;import com.zhang.order.bean.Order;
import com.zhang.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class OrderController {@AutowiredOrderService orderService;//创建订单@GetMapping("/create")public Order getOrder(int productId,int userId) {Order order = orderService.createOrder(productId, userId);return order;}}
(2)service
package com.zhang.order.service;import com.zhang.order.bean.Order;public interface OrderService {Order createOrder(int productId,int userId);
}
package com.zhang.order.service;import com.zhang.order.bean.Order;
import com.zhang.product.bean.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService{@AutowiredDiscoveryClient discoveryClient;@AutowiredRestTemplate restTemplate;@Overridepublic Order createOrder(int productId, int userId) {Order order = new Order();Product product = getProductFromRemote(productId);order.setId(1);//总金额需要远程查询商品单价和购买数量,相乘得出//product.getPrice().multiply(new BigDecimal(product.getNum()))order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(1);order.setNickName("张三");order.setAddress("上海南京路");//远程查询商品列表order.setProductList(Arrays.asList(product));return order;}//远程调用商品数据public Product getProductFromRemote(int productId){//1.获取到商品服务的所有IP主机地址和port端口//getInstances:获取实例List<ServiceInstance> instances = discoveryClient.getInstances("service-productdemo");ServiceInstance serviceInstance = instances.get(0);//远程的URL地址String url = "Http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/product/"+productId;log.info("远程请求路径:{}",url);//2.给远程发送请求Product product = restTemplate.getForObject(url, Product.class);return product;}
}
(3)配置类config
package com.zhang.order.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
//远程调用的配置类
public class OrderServiceConfig {@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}
商品模块
(1)controller
package com.zhang.product.controller;import com.zhang.product.bean.Product;
import com.zhang.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ProductController {@AutowiredProductService productService;@GetMapping("/product/{id}")//查询商品public Product getProduct(@PathVariable("id") int productId) {Product product= productService.getProductById(productId);return product;}}
(2)service
package com.zhang.product.service;import com.zhang.product.bean.Product;public interface ProductService {Product getProductById(int productId);
}
package com.zhang.product.service;import com.zhang.product.bean.Product;
import org.springframework.stereotype.Service;import java.math.BigDecimal;@Service
public class ProductServiceImpl implements ProductService{@Overridepublic Product getProductById(int productId) {Product product = new Product();product.setId(productId);product.setProductName("苹果"+productId);product.setPrice(new BigDecimal(99));product.setNum(3);return product;}}
(3)配置类config
package com.zhang.product.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
//远程调用的配置类
public class ProductServiceConfig {@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}
3.3.2负载均衡的实现远程调用
前面已经实现普通的从订单服务远程调用商品服务,接下来我们要负载均衡的从订单服务调用商品服务。
(1)先在订单模块pom.xml导入负载均衡依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>
(2)测试一下负载均衡方式
编写一个订单模块的测试类LoadBanlanceTest
package com.zhang;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;@SpringBootTest
public class LoadBalancerTest {@Autowired//LoadBalancerClient自带了负载均衡算法LoadBalancerClient loadBalancerClient;@Testvoid Teat(){//获取某个微服务的单个实例ServiceInstance choose = loadBalancerClient.choose("service-productdemo");System.out.println("choose="+choose.getHost() + choose.getPort());//可以做多个,从而多个获取某个微服务的单个实例,从而就能按照轮询的规则进行负载均衡得获取实例choose = loadBalancerClient.choose("service-productdemo");System.out.println("choose="+choose.getHost() + choose.getPort());choose = loadBalancerClient.choose("service-productdemo");System.out.println("choose="+choose.getHost() + choose.getPort());}
}
(3)在订单模块的接口实现类里实现负载均衡调用商品模块
先自动注入LoadBalancerClient
@Autowired //一定要在pom.xml中导入spring-cloud-starter-loadbalancer LoadBalancerClient loadBalancerClient;
//进阶二:负载均衡的远程调用商品数据public Product getProductFromRemoteWithLoadBalancer(int productId){//1.获取到商品服务的所有IP主机地址和port端口//loadBalancerClient.choose:获取某个微服务的单个实例ServiceInstance choose = loadBalancerClient.choose("service-productdemo");//远程的URL地址String url = "Http://"+choose.getHost()+":"+choose.getPort()+"/product/"+productId;log.info("远程请求路径:{}",url);//2.给远程发送请求Product product = restTemplate.getForObject(url, Product.class);return product;}
基于注解的负载均衡实现:
先在order的配置类中的远程调用方法RestTemplate上添加@LoadBalanced
//进阶三:基于注解式负载均衡的远程调用商品数据public Product getProductFromRemoteWithAnnotation(int productId){String url = "Http://service-productdemo/product/"+productId;//给远程发送请求//restTemplate在发送请求前会动态的把service-productdemo这个微服务负载均衡的替换成IP地址和端口Product product = restTemplate.getForObject(url, Product.class);return product;}
3.4注册中心宕机了,远程调用服务还能用吗?
订单服务远程调用商品服务 正常的远程调用流程:
1.RestTemplate通过"Http://service-productdemo/product/"+productId获取微服务的名字
2.RestTemplate访问注册中心获取微服务的所有实例地址访问列表
3.RestTemplate通过负载均衡算法选择某个实例地址进行访问
4.把实例的地址访问列表更新到本地实例缓存中,这个实例缓存和注册中心同步
注册中心宕机后分两种情况
情况一:RestTemplate访问过注册中心,把实例地址列表更新到本地实例缓存中了,远程调用不再依赖注册中心,实例缓存中存在的实例可以通过远程调用进行访问了。
情况二:RestTemplate没有把实例地址列表更新到本地实例缓存中,远程访问某个实例地址时还需先去访问注册中心获取微服务的实例地址。
4.配置中心
4.1基本使用
(1)在service中导入nacos配置中心依赖
<!--配置中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
(2)编写application.properties配置文件
将数据集导入到nacos中
(3)在nacos中新增一个配置中心
(4)在oredr的controller中导入配置中心的配置内容
(5)访问localhost:8000/config
如果整个项目中只有部分微服务导入了配置中心,其余的没导入,还想让其他微服务正常运行
在他们的properties配置文件中导入配置中心或者禁用导入检查。
4.2动态刷新
(1)把nacos中的数据集内容单独弄成一个properties类
名字需要对应数据集内容
(2)编写controller
配置监听
通过NacosConfigManager组件来实现监听配置变化
OrderMainApplication的代码:
//配置文件监听//1.项目启动时就监听配置文件变化//2.发生变化后,拿到变化值//3.发送邮件@BeanApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){//NacosConfigManager这个参数的组件会自动从springboot里取// return new ApplicationRunner() {
// @Override
// public void run(ApplicationArguments args) throws Exception {
//
// }
// }//因为ApplicationRunner是一个接口式函数,所以可以用lambda表达式简写return args -> {//nacosConfigManager实时的监听service-orderdemo.properties数据集的变化ConfigService configService = nacosConfigManager.getConfigService();configService.addListener("service-orderdemo.properties","DEFAULT_GROUP", new Listener() {@Overridepublic Executor getExecutor() {//返回一个大小为4的线程池return Executors.newFixedThreadPool(4);}@Overridepublic void receiveConfigInfo(String configInfo) {System.out.println("变化的配置信息:"+configInfo);System.out.println("发送邮件...");}});System.out.println("=============================");};}
4.3Nacos中的数据集和application.properties有相同的配置项,哪个生效?
引入配置中心的目的就是为了对这些微服务的配置进行统一管理,一旦以微服务中存在的配置项为准,配置中心里的配置项就白写了,更无法统一对这些微服务的配置进行统一管理了。所以是Nacos的数据集生效。
配置项的优先级遵循:后导入优先,外部优先
4.4数据隔离
通过命名空间来区分多套环境
通过分组来区分多个微服务
(1)创建一个新的配置文件application.yaml
server:port: 8000
spring:application:name: server-orderdemocloud:nacos:server-addr: 127.0.0.1:8848config:namespace: dev
#加载dev命名空间下的两个配置文件common.properties和database.peoperties,都属于order订单微服务下的config:import:- nacos:common.properties?group=order- nacos:database.properties?group=order
(2)在properties配置类中新增数据库地址配置内容
(3)在controller类中也新增获取数据库地址的内容
可以通过改变命名空间的内容来动态切换环境
如果不同环境下导入的配置文件名称、数量、类型不一致,可以采用yaml文件的多文档模式
server:port: 8000
spring:#激活dev环境profiles:active: devapplication:name: server-orderdemocloud:nacos:server-addr: 127.0.0.1:8848#:后跟的是默认环境namespace: ${spring.config.active:dev}
#加载dev命名空间下的两个配置文件common.properties和database.peoperties,都属于order订单微服务下的---
spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=orderactivate:#动态连接某个命名空间,:后跟的是默认环境on-profile: dev
---
spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=orderactivate:on-profile: prod
spring.config.import是spring官方推出的一种加载外部属性源的方式。
5.Nacos总结
三.OpenFeign(远程调用组件)
1.OpenFeign远程调用流程
OpenFeign是声明式远程调用,RestTemplate是编程式远程调用
1.1声明式实现---业务间API的调用
service中添加相关依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
启动类上添加@EnableFeignClients注解
编写ProductFeignClient接口
package com.zhang.order.feign;import com.zhang.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;//声明这是个Feign客户端,用来发送Feign远程调用
//value的值指的是请求的目标微服务
@FeignClient(value = "service-productdemo")
public interface ProductFeignClient {//MVC注解的两套使用逻辑//1.作用在controller上,表示接收这样的请求//2.作用在feign上,表示发送这样的请求@GetMapping("/product/{id}")//@RequestHeader("token") String token,参数中添加这个表示调用这个方法传进来的令牌作用到请求头上面Product getProduct(@PathVariable("id") int productId);}
改造OrderServiceImpl
总结:目前已经实现了业务间API的远程调用,订单服务访问商品服务,在FeignClient中写上订单微服务的名字,feign就会自动连上注册中心获取商品的地址,然后去访问。
1.2声明式实现--第三方API的调用
第三方API的调用不需要注册中心,比如:给气象局发送一个请求获取当天天气,给支付宝发送一个请求获取这笔支付的流水信息,给微信发送一个请求获取到微信登录的用户数据。这些都可以阅读第三方文档获取固定地址,不需要注册中心。
编写一个WeatherFeignClient客户端,@FeignClient参数里的value可以自定义,url就按照接口文档里写,请求方式为POST
编写完Feign客户端后再编写一个测试类,传上认证参数,令牌信息以及城市ID
总结:向url这个位置再拼接上@PostMapping参数里的地址发送请求,把认证信息,令牌信息,城市ID放在对应的请求头,请求参数里,不用访问注册中心即可进行访问调用。
小技巧:
访问自己写的业务API,在Feign客户端中直接复制对方微服务Controller中的签名
如果是访问第三方API,则根据第三方API接口文档编写。
1.3面试题:客户端负载均衡与服务端负载均衡的区别
客户端负载均衡:
订单调用商品,会由OpenFeign编写一个远程调用的方法,流程是OpenFeign会先到注册中心获取商品的地址列表,然后根据负载均衡算法选择一个地址,然后向这个地址发起请求。此时负载均衡发生在客户端。
服务端负载均衡:
比如OpenFeign调用墨迹天气查看天气情况会直接请求墨迹天气,不去访问注册中心,墨迹天气服务端会有多个天气查询服务,但根据API接口文档只会对外显示一个固定的地址,当多个客户端(OpenFeign,其他APP或者汽车手机)访问这个固定地址时,墨迹天气客户端收到这个请求后会把该请求随机转给一个天气查询服务器进行查询。此时负载均衡发生在服务端。
2.进阶配置
2.1日志
现在application.yaml中配置日志,添加上feign的包名,日志级别为debug
logging:level:com.zhang.order.feign: debug
再在配置文件OrderServiceConfig中添加日志全纪录组件
2.2超时控制
2.3超时配置
对feign客户端进行超时配置,后续记得注销掉,我们单写一个yaml文件application-feign.yaml
首先在原有的application.xml中导入feign客户端
application-feign.yaml代码:
当配置多个feign客户端时,有精确设置就用精确设置,没有就用默认设置
说明:这是 Feign 提供的日志级别配置,它控制 Feign 请求和响应的详细日志,Logger.Level.FULL
代表 打印所有请求和响应的详细信息
- 请求方法(GET、POST等)
- 请求URL
- 请求头
- 请求体
- 响应状态码
- 响应头
- 响应体
- 请求的执行时间
2.4重试机制
在service-order的config中加入重试机制
//加入重试机制@BeanRetryer retryer() {return new Retryer.Default();}
采用默认的重试机制设置,默认重试5次,初始间隔100毫秒,后续每次乘1.5,最多间隔1秒
2.5拦截器
全局拦截器
模拟在订单远程调用商品的请求的请求头中加入X-Token信息。
编写一个请求拦截器XTokenRequestInterceptor,需要加上@Component注解让拦截器生效
package com.zhang.order.interceptor;import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;import java.util.UUID;@Component//让这个拦截器生效,需要加入到spring容器中
public class XTokenRequestInterceptor implements RequestInterceptor {/***请求拦截器* @param requestTemplate 请求模版*/@Overridepublic void apply(RequestTemplate requestTemplate) {System.out.println("requestTemplate 已启动........................ = "+requestTemplate);//在请求头中加入随机数作为令牌数据requestTemplate.header("X-Token", UUID.randomUUID().toString());}
}
在商品服务中测试获取到的请求头数据
局部拦截器
2.6兜底返回Fallback机制
在service-orderdemo的pom.xml中导入依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
创建兜底回调类ProductFeignClientFallback
package com.zhang.order.feign.fallback;import com.zhang.order.feign.ProductFeignClient;
import com.zhang.product.bean.Product;
import org.springframework.stereotype.Component;import java.math.BigDecimal;@Component
public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getProduct(int productId) {System.out.println("兜底回调启动........");Product product = new Product();product.setId(productId);product.setProductName("未知商品");product.setPrice(new BigDecimal(0));product.setNum(0);return product;}
}
在ProductFeignClient接口中声明,远程调用后会调用 ProductFeignClientFallback来进行兜底回调
在配置文件中开启兜底回调,注意层级关系
模拟远程调用失败
关闭超时机制和重试机制
停掉service-productdemo服务
3.总结
四.Sentinel(服务保护,限流熔断降级)
1.基础
1.1资源和规则

1.2工作原理
1.3整合使用
1.3.1控制台下载启动
下载 sentinel dashboard控制台
在目录列表输入cmd启动:
java -Dserver.port=8081 -Dcsp.sentinel.dashboard.server=localhost:8081 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.8.jar
这里自己指定运行jar包的端口号
账号密码都是sentinel
1.3.2在服务里整合Sentinel场景
在微服务里整上Sentinel场景,配置连接上Sentinel的控制台,这些应用的资源可以通过代码或者注解在控制台定义相应的规则
配置连接+热加载(服务一启动就加载)
在每个服务的配置文件中都配置上
service-order服务中的yaml文件:
service-product的properties文件:
spring.cloud.sentinel.transport.dashboard=localhost:8081 spring.cloud.sentinel.eager=true
访问localhost:8081进入sentinel控制台
在创建订单这个资源上添加@SentinelResource(value = "createOrder"),value = "createOrder"代表该资源名称,从而对这个资源进行保护,后期可以进行流控等操作。
2.异常处理
2.1web接口异常处理
model模块编写异常类
package com.zhang.common;import lombok.Data;/*** 异常类*/
@Data
public class R {private Integer code;private String msg;private Object data;public static R ok() {R r = new R();r.setCode(200);return r;}public static R ok(String msg,Object data) {R r = new R();r.setCode(200);r.setMsg(msg);r.setData(data);return r;}public static R error() {R r = new R();r.setCode(500);return r;}public static R error(Integer code,String msg) {R r = new R();r.setCode(code);r.setMsg(msg);return r;}
}
在service-order服务中写一个MyBlockExceptionHandler的实现类
package com.zhang.exception;import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhang.common.R;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;import java.io.PrintWriter;@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,String resourceName, BlockException e) throws Exception {//response.setStatus(500); //too many requestsresponse.setContentType("application/json;charset=utf-8");//内容类型PrintWriter writer = response.getWriter();R error = R.error(500, resourceName + " 被Sentinel限制了,原因:" + e.getClass());String json = objectMapper.writeValueAsString(error);writer.write(json);writer.flush();writer.close();}
}
配置流控规则
QPS设置每秒只允许1个请求
效果:
违反流控规则就走MyBlockExceptionHandler异常
2.2@SentinelResource异常处理
@SentinelResource一般标注在非Controller的层,想对那些方法进行保护就加这个注解
编写blockHandler方法或者fallback方法处理异常
blockHandler="异常处理方法",这个方法中加参数: BlockException exception
fallback="异常处理方法",这个方法中加参数:Throwable exception
二者作用一样
在OrderServiceImpl类中编写:
如果没有违反规则,调用真实的数据,违反规则就调用blockHandler指定的兜底数据
2.3OpenFeign调用
走openfeign的fallback异常
3.流量控制规则(FlowRule)
可作为资源的有:
- sentinel自动探测的web接口请求路径
- @SentinelResource注解标注的方法
- 远程调用的完整请求描述
3.1阈值类型
QPS:统计每秒请求数,底层是一个计数器
并发线程数:统计并发线程数
集群
集群和流控模式是相背离的,二选一
3.2流控模式
3.2.1直接策略
直接对当前的资源进行请求限制。默认的流控模式。
3.2.2链路策略
关闭统一上下文
OrderController中添加秒杀订单方法
链路策略的意思就是,当对创建订单方法这个资源进行保护时,分两种情况,第一种普通创建订单不受限制,第二种秒杀创建时受限制。
3.2.3关联策略
在OrderController中增加读写数据库的方法
单独访问/readDb或者/writeDb,多少请求都能成功
当/writeDb请求量非常大的时候,突然访问/readDb,则会崩溃,走自定义BlockExceptionHandler的实现类
使用场景:当系统里出现资源竞争的时候,使用关联策略进行限制。例如这里只有写量特别大的时候才会限制读,其他不限制。
3.3流控效果
3.3.1快速失败(直接拒绝)
作用:处理不了的请求直接丢弃。交给Web接口异常处理MyBlockExceptionHandler。
在MyBlockExceptionHandler异常处理中加上450的状态码
通过国产软件APIPost
进行压力测试
3.3.2Warm Up(预热/冷启动)
作用:慢慢将每秒的请求达到峰值,有个预热机制。
比如我这里QPS是10,需要经过3秒才能达到10,前三秒是慢慢将能接收请求数量提上来
3.3.3匀速排队
sentinel匀速排队等待策略是漏铜算法(Leaky Bucket)和虚拟队列等待机制实现的。
4.熔断规则(DegradeRule)
4.1断路器工作原理
通过断路器可以切断不稳定的调用,并且能快速返回请求不积压,避免雪崩效应。
断路器像一个标识,A服务远程调用B,不用实际发送请求,先看一下断路器状态,闭合即可调用,打开即不可调用,半开即需要先发送一个请求探测B服务的可用性再做后续操作
我们会设置一个统计时长(比如统计5s内的请求数)和最小请求数(在统计时长内最少有多少个请求),断路器默认是关闭状态,就比如5s内断路器收到100个请求,通过三种熔断策略(慢调用比例,异常比例,异常数)来决定断路器的开闭。断路器打开后有一个熔断时长,超过这个时长后,断路器变为半开状态,然后A服务发送一个探测请求,B服务正常断路器就关闭,否则就打开。
4.2熔断策略
4.2.1慢调用比列
模拟实验:
在OrderController中设置睡眠时间
熔断策略选用慢调用比例,在5秒内如果有80%的请求响应时间大于1秒,则触发熔断。
由于是在远程调用处设置了熔断策略,所以熔断之后走openFeign的fallback处理,此处发生熔断后直接回调,不发远程请求
4.2.2异常比例
熔断策略选用异常比例,在5秒内有80%的请求发生了异常,就会出发30s的熔断
4.2.3异常数
统计时长内发生异常的数量达到设置的异常数,则发生熔断。
5.热点规则(热点参数限流)
5.1环境搭建
目前 Sentinel 自带的 adapter 仅 Dubbo 方法埋点带了热点参数,其它适配模块(如 Web)默认不支持热点规则,可通过自定义埋点方式指定新的资源名并传入希望的参数。注意自定义埋点的资源名不要和适配模块生成的资源名重复,否则会导致重复统计。
所以我们自定义一个方法作为回调方法。
//秒杀创建订单@GetMapping("/seckill")@SentinelResource(value = "seckill-order",fallback = "seckillFallback")public Order secKill(@RequestParam("productId") int productId,@RequestParam("userId") int userId) {Order order = orderService.createOrder(productId, userId);order.setId(Integer.MAX_VALUE);return order;}public Order secKillFallback(int productId, int userId, BlockException exception) {System.out.println("seckFallback....回调正在启用");Order order = new Order();order.setUserId(userId);order.setId(productId);order.setAddress("异常信息"+exception.getClass());return order;}
5.2热点参数限流
模拟热点参数限流
需求1:
参数索引为1对应着seckill方法的第2个参数userId受到限制,如果传的参数没有userId,会有一个默认值,频繁访问也会流控
可以修改相关代码:
这样才可以不被流控。
总结:需求1验证了携带此参数的请求会被流控,不携带不会被流控。
需求2:6号用户是vvip,不被限流
参数类型指的是用户id的类型,参数值指的是哪个用户是vvip用户,限流阈值越大代表越不被限流。
需求3:666号商品下架了,不允许被访问
在seckill-order中新增一个热点规则,参数索引为0,代表对商品id这个参数限流,参数值代表666号商品,限流阈值为0,代表不能访问。
没有走兜底回调,是因为回调方法中的异常类型为BlockException流控异常,blockHandler专门处理流控异常,fallback不仅可以处理流控异常还可以处理业务异常,所以BlockException需要改为Throwable,这样可以捕捉到所有异常。
6.总结
五.Gateway(网关)
1.网关功能(核心:路由,断言,过滤器)
Reactive Server响应式网关要比传统式网关Server MVC性能要好一些
2.创建网关
由于网关不属于业务,属于架构组件的一部分,所以我们新建一个moudle,导入相关依赖,写个主启动类
<dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency></dependencies>
编写个配置文件application.yaml
3.路由
3.1规则配置
创建路由规则,以/api/order/***开头的请求交给订单服务,以api/product/***开头的请求交给商品服务。
方式:在配置文件中配置路由规则
编写一个application-route.yaml的配置文件,并且引入到application.yaml配置文件中
还需要再网关的pom.xml中导入负载均衡的依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>
并且在订单和商品服务的controller类分别添加基准路径
3.2工作原理
网关转发请求的原理:
请求发送到网关后,网关根据自己的路由表规则通过断言机制进行匹配,匹配到哪个规则就按照这个规则把请求转到指定的目的地。转之前通过HandlerMapping和WebHandler这两个组件拿到这个规则下的Filter,请求往下转的时候先经过Filter的前置方法转给目的地,目的地处理完之后把响应交给网关倒序经过每一个Filter,最终把响应交给指定的客户端。
规则有前后顺序和优先级,优先匹配优先级高的规则,没有优先级优先匹配在前面的。
4.断言
4.1长短写法
短写法:
长写法:
name的取值取决于断言工厂类前面的内容
args的取值取决于config配置类里面的参数名
matchTrailingSlash的作用:无论url是否有/结尾都能匹配相同的路由
断言名只需要写这些前缀就好
各种断言规则
4.2测试Query的断言规则
必须访问网关的/search路径,带着q参数并且值为haha的才可以匹配正确的路由
4.3自定义断言工厂
根据QueryRoutePredicateFactory仿写
@Component
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {public VipRoutePredicateFactory() {super(Config.class);}//段格式传参顺序@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param", "value");}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {// localhost/search?q=haha&user=leifengyangServerHttpRequest request = serverWebExchange.getRequest();String first = request.getQueryParams().getFirst(config.param);return StringUtils.hasText(first) && first.equals(config.value);}};}@Validatedpublic static class Config {@NotEmptyprivate String param;@NotEmptyprivate String value;public @NotEmpty String getParam() {return param;}public void setParam(@NotEmpty String param) {this.param = param;}public @NotEmpty String getValue() {return value;}public void setValue(@NotEmpty String value) {this.value = value;}}
}
5.过滤器
5.1基本使用
- RewritePath=/api/product/?(?<segment>.*), /$\{segment}
这条规则的作用是:将路径 /api/product/{任意内容}
重写为 /{任意内容}
。换句话说,它去掉了 /api/order/
前缀,保留了后面的路径部分。
比如:/api/product/abc
会被重写为 /abc,
这类配置通常用于将请求从一个路径重定向到另一个路径,或者将路径中的某些部分移除。
5.2默认Filter
Filter可选列表
5.3自定义过滤器工厂
目前我们有一个需求:对发出去的请求,添加一个一次性令牌的响应头,这个令牌可以是jwt或者uuid,添加这个一次性令牌响应头的过滤器需要我们自定义,只作用于订单微服务。
@Component
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {@Overridepublic GatewayFilter apply(NameValueConfig config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.fromRunnable(()->{//注意这里是responseServerHttpResponse response = exchange.getResponse();HttpHeaders headers = response.getHeaders();String value = config.getValue();if ("uuid".equalsIgnoreCase(value)) {value = UUID.randomUUID().toString();}if ("jwt".equalsIgnoreCase(value)) {value = "JWT";}headers.add(config.getName(), value);}));}};}
}
测试:
5.4全局跨域
跨域问题就是如果不配置,前后端只能同域名同端口进行交互,配置后可以跨域名和端口。
适用于所有路径([/**])。
允许任何来源(allowedOriginPatterns: '*') 访问 API。
允许所有 HTTP 头部(allowedHeaders: '*')。
允许所有请求方法(allowedMethods: '*'),包括 GET、POST、PUT、DELETE 等。
[/**] 其中 /**
代表所有 API 路径
注意:[/**]
必须加方括号 []
,否则 YAML 解析可能会出错
6.总结
可以不用过,订单直接去注册中心获取商品的所有地址,然后订单选择一个去调用。
也可以经过网关,但是没必要,如果一定经过网关,则需要修改一下内容
六.Seata(分布式事务)
参考:快速启动 | Apache Seata
1.基础
1.1组成
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
TM 要求 TC 开始新的全局事务。 TC 生成一个代表全局事务的 XID。 XID 通过微服务的调用链传播。
RM将本地事务作为XID对应的全局事务的一个分支注册到TC。(RM负责注册本地事务,根据XID将本地事务作为一个分支事务注册到全局事务TC)
TM请求TC提交或回滚XID对应的全局事务。TC驱动XID对应的全局事务下的所有分支事务完成分支提交或回滚。
1.2环境搭建
先导入seata模块
连接mysql,执行sql创建库和表
CREATE DATABASE IF NOT EXISTS `storage_db`;
USE `storage_db`;
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (`id`int(11) NOT NULL AUTO_INCREMENT,`commodity_code`varchar(255) DEFAULT NULL,`count`int(11) DEFAULT 0,PRIMARY KEY(`id`),UNIQUE KEY(`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO storage_tbl (commodity_code, count) VALUES ('P0001', 100);
INSERT INTO storage_tbl (commodity_code, count) VALUES ('B1234', 10);
DROP TABLE IF EXISTS `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;CREATE DATABASE IF NOT EXISTS `order_db`;
USE `order_db`;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (`id`int(11) NOT NULL AUTO_INCREMENT,`user_id`varchar(255) DEFAULT NULL,`commodity_code` varchar(255) DEFAULT NULL,`count`int(11) DEFAULT 0,`money`int(11) DEFAULT 0,PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `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;CREATE DATABASE IF NOT EXISTS `account_db`;
USE `account_db`;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (`id`int(11) NOT NULL AUTO_INCREMENT,`user_id`varchar(255) DEFAULT NULL,`money`int(11) DEFAULT 0,PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO account_tbl (user_id, money) VALUES ('1', 10000);DROP TABLE IF EXISTS `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;
1.3本地事务测试
有@Transactional,但没有@EnableTransactionManagement,异常数据也能回滚,为什么?
因为 Spring Boot 默认开启了事务管理,不需要 @EnableTransactionManagement 也能回滚数据。如果你在 Spring 传统项目里,可能就需要手动启用事务管理。所以还是启动类加上@EnableTransactionManagemen。
这些事务回滚在seata-account, seata-storage单个服务里是生效的,如果seata-order要远程调用seata-account,有异常订单不会创建,但是远程调用的余额扣减却成功了。分布式事务使用这两个注解无效。
1.4打通远程链路
采购发送扣库存和下订单两个远程调用请求,在采购服务中分别编写OrderFeignClient和StorageFeignClient,并且在主程序中写入FeignClient
OrderFeignClient:
package com.zhang.business.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = "seata-order")
public interface OrderFeignClient {/*** 创建订单* @param userId* @param commodityCode* @param orderCount* @return*/@GetMapping("/create")String create(@RequestParam("userId") String userId,@RequestParam("commodityCode") String commodityCode,@RequestParam("count") int orderCount);
}
StorageFeignClient:
package com.zhang.business.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = "seata-storage")
public interface StorageFeignClient {/*** 扣减库存* @param commodityCode* @param count* @return*/@GetMapping("/deduct")String deduct(@RequestParam("commodityCode") String commodityCode,@RequestParam("count") Integer count);
}
订单会发送减余额的远程调用请求,在订单服务中再编写AccountFeignClient,同时在主程序写入FeignClient
package com.zhang.order.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = "seata-account")
public interface AccountFeignClient {/*** 扣减账户余额* @return*/@GetMapping("/debit")String debit(@RequestParam("userId") String userId,@RequestParam("money") int money);
}
至此, 采购远程调用订单和扣减库存服务,订单服务又远程调用扣减余额服务已经完成。
如果在保存订单后发生了逻辑运算错误,订单可以回滚,扣减余额和库存不能回滚,这就导致了分布式事务数据不一致问题。
订单可以回滚但扣减余额不能回滚是因为订单服务标注了@Transactional事务注解,订单有自己的订单数据库,保存订单是保存在了订单库里面,如果保存订单后出现问题,意味着连上这个订单库的数据库连接出现了问题,所以只会回滚订单的插入,而远程调用扣减余额并不会回滚。
1.5架构原理
1.6整合Seata
引入Seata服务器解决分布式事务问题,使用步骤:
- 下载seata服务端:https://seata.apache.org/zh-cn/release-history/seata-server,注意服务端的版本要和客户端的对应。
- 解压缩后在bin目录启动: seata-server.bat ,web端:http://127.0.0.1:7091,账号密码全为seata,8091是TC协调者的TCP端口
- 导入seata依赖
<!--分布式事务--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency>
在每个seata服务的resources里配置file.conf文件
service {#事务的分组,默认是default_tx_group,"default"为分组名vgroupMapping.default_tx_group = "default"#seata服务器的地址列表default.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false}
引入seata只需要在最大的入口采购服务中引入@GlobalTransactional全局事务注解即可,其余的库存和订单以及金额服务皆为分支事务,分支事务微服务中启动类上加入@EnableTransactionManagement,在具体事务service方法上加@Transactional
2.原理
2.1二阶提交协议流程
seata如何通过@GlobalTransactional全局事务注解控制分布式事务?
首先采购这个服务在添加上@GlobalTransactional注解后,会向TC服务器注册全局事务,每个全局事务都有一个ID,全局事务启动。然后再去执行分支事务。
第一阶段:
- 分支事务会去解析扣减库存的sql。
- 在解析之前会根据sql的where条件查询前镜像,从而得到前镜像
- 然后执行业务sql
- 根据id查询后镜像,得到后镜像
- 将前后镜像插入到undo_log回滚日志表中
- seata客户端会向seata服务端注册分支事务,同时给记录添加全局锁(相当于mysql的行级锁)
- 进行本地事务提交,将业务数据和undo_log回滚日志数据一块提交,保存到自己的数据库
- 向TC服务器汇报自己本地事务提交成功/失败的状态
第一阶段成功:
第二阶段:分支提交
- TC服务端收到TC客户端提交成功的请求,会立即响应OK
- 给异步任务队列中添加异步任务,该异步任务就是异步和批量的删除undo_log记录
第一阶段失败:
第二阶段:分支回滚
- TC客户端收到TC服务端提出的回滚请求,开启一个本地事务
- 该本地事务通过全局事务id和分支事务id找到undo_log日志数据
- 数据校验,对比后镜像和当前数据是否一致,不一致说明被外界操作修改,进行事务数据覆盖等操作
- 数据回滚,拿到前镜像数据更新该记录,然后删除undo_log日志
- 所有事务执行完后释放所有的全局锁
2.2四种事务模式(都是二阶段提交)
参考:Seata 是什么? | Apache Seata和尚硅谷2025最新SpringCloud速通-操作步骤(详细)_尚硅谷2025最新springcloud速通-操作步骤(详细)-CSDN博客
2.2.1AT模式
前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
-
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
写隔离
- 一阶段本地事务提交前,需要确保先拿到 全局锁 。
- 拿不到 全局锁 ,不能提交本地事务。
- 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
2.2.2XA模式
第一阶段不会提交数据,阻塞事务请求,在第二阶段确认提交再提交或者回滚。全局锁+MySQL行锁在第一阶段就开启,事务一开始就用阻塞模式,性能差。AT和XA区别是AT第一阶段执行完SQL释放行锁,XA是到第二阶段才提交SQL导致行锁从开始到最后,阻塞时间长性能差。但二者都是一直持有seata的全局锁的。
2.2.3TCC模式
主要是广义上的事务,需要写侵入式的代码。举例业务需要三个事务,一个事务改数据库,一个发短信,一个发邮件,这就用AT和XA行不通了,无法回滚,如果全局事务失败,只能进行补偿性操作,例如再发邮件和短信提醒对方扣款失败或者订单失败等。
2.2.4Saga模式
用于长事务,一时半会执行不完的事务。例如请假审批,其他模式都用了锁,如果长期锁在那是对系统是非常大的阻塞。saga是基于消息队列做的,后续有替代方案,所以这个几乎不用。