Spring Cloud Alibaba全家桶——微服务网关Gateway组件

Posted 小新要变强

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud Alibaba全家桶——微服务网关Gateway组件相关的知识,希望对你有一定的参考价值。

前言

本文小新为大家带来 微服务网关Gateway组件 相关知识,具体内容包括微服务网关Gateway组件(包括:Gateway核心概念Gateway工作原理),Spring Cloud Gateway环境搭建路由断言工厂(Route Predicate Factories)配置过滤器工厂( Gateway Filter Factories)配置全局过滤器(Global Filters)配置Gateway跨域配置(CORS Configuration)Gateway整合sentinel流控降级网关高可用等进行详尽介绍~

不积跬步,无以至千里;不积小流,无以成江海。每天进步一点点,在成为强者的路上,小新与大家共同成长!

📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)

↩️本文上接:Spring Cloud Alibaba全家桶(九)——分布式事务组件Seata


目录

微服务网关Gateway组件

网关简介

大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?

如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去用。

这样的架构,会存在着诸多的问题:

  • 每个业务都会需要鉴权、限流、权限校验、跨域等逻辑,如果每个业务都各自为战,自己造轮子实现一遍,会很蛋疼,完全可以抽出来,放到一个统一的地方去做。
  • 如果业务量比较简单的话,这种方式前期不会有什么问题,但随着业务越来越复杂,比如淘宝、亚马逊打开一个页面可能会涉及到数百个微服务协同工作,如果每一个微服务都分配一个域名的话,一方面客户端代码会很难维护,涉及到数百个域名,另一方面是连接数的瓶颈,想象一下你打开一个APP,通过抓包发现涉及到了数百个远程调用,这在移动端下会显得非常低效。
  • 后期如果需要对微服务进行重构的话,也会变的非常麻烦,需要客户端配合你一起进行改造,比如商品服务,随着业务变的越来越复杂,后期需要进行拆分成多个微服务,这个时候对外提供的服务也需要拆分成多个,同时需要客户端配合你进行改造,非常蛋疼。

上面的这些问题可以借助API网关来解决:

所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。

添加上API网关之后,系统的架构图变成了如下所示:

我们也可以观察下,我们现在的整体架构图:

一、Spring Cloud Gateway简介

网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。

Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul1.0。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能。

Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。

Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。

其他的网关组件:

在SpringCloud微服务体系中,有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway。

网上很多地方都说Zuul是阻塞的,Gateway是非阻塞的,这么说是不严谨的,准确的讲Zuul1.x是阻塞的,而在2.x的版本中,Zuul也是基于Netty,也是非阻塞的,如果一定要说性能,其实这个真没多大差距。

而官方出过一个测试项目,创建了一个benchmark的测试项目:spring-cloud-gateway-bench,其中对比了以下网管组件:

Spring Cloud Gateway官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

Spring Cloud Gateway 功能特征:

  • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 支持路径重写;
  • 集成 Spring Cloud 服务发现功能(Nacos、Eruka);
  • 可集成流控降级功能(Sentinel、Hystrix);
  • 可以对路由指定易于编写的 Predicate(断言)和 Filter(过滤器)。

1️⃣Gateway核心概念

  • 路由(route)

路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。

  • 断言(predicates)

Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。

  • 过滤器(Filter)

SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。

2️⃣Gateway工作原理

执行流程大体如下:

  • (1)Gateway Client向Gateway Server发送请求
  • (2)请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
  • (3)然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping
  • (4)RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
  • (5)如果过断言成功,由FilteringWebHandler创建过滤器链并调用
  • (6)请求会一次经过PreFilter–微服务–PostFilter的方法,最终返回响应

二、Spring Cloud Gateway环境搭建

1️⃣引入依赖

<!‐‐ gateway网关 ‐‐>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring‐cloud‐starter‐gateway</artifactId>
</dependency>

注意: 会和spring-webmvc的依赖冲突,需要排除spring-webmvc。

2️⃣编写yml配置文件

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: lb://alibaba-order-seata  #需要转发的地址   lb: 使用nacos中的本地负载均衡策略  order-service服务名
          #断言规则 用于路由规则的匹配
          predicates:
            ‐ Path=/product‐serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            ‐ StripPrefix=1 # 转发之前去掉1层路径.

3️⃣集成Nacos

现在在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从注册中心获取此地址。

🍀(1)引入依赖

<!‐‐ nacos服务注册与发现 ‐‐>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring‐cloud‐starter‐alibaba‐nacos‐discovery</artifactId>
</dependency>

🍀(2)编写yml配置文件

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    #配置Nacos
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: lb://alibaba-order-seata  #需要转发的地址   lb: 使用nacos中的本地负载均衡策略  order-service服务名
          #断言规则 用于路由规则的匹配
          predicates:
            ‐ Path=/product‐serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            ‐ StripPrefix=1 # 转发之前去掉1层路径.

简写: 去掉关于路由的配置,自动寻找服务

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    #配置Nacos
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    # gateway的配置
    gateway:
      discovery:
        locator:
          enabled: true  #是否启动自动识别nacos服务  

🍀(3)测试

这时候,就发现只要按照网关地址/微服务/接口的格式去访问,就可以得到成功响应。

三、路由断言工厂(Route Predicate Factories)配置

参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

作用: 当请求gateway的时候, 使用断言对请求进行匹配, 如果匹配成功就路由转发, 如果匹配失败就返回404

类型: 内置,自定义

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:

  • 基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

  • (1) AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
  • (2) BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
  • (3) BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

ZonedDateTime.now():

After=2019‐12‐31T23:59:59.789+08:00[Asia/Shanghai]
  • 基于远程地址的断言工厂

RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中

RemoteAddr=192.168.1.1/24
  • 基于Cookie的断言工厂

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。

‐Cookie=chocolate, ch.
  • 基于Header的断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配。

‐Header=X‐Request‐Id, \\d+
  • 基于Host的断言工厂

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

‐Host=**.testhost.org
  • 基于Method请求方法的断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

‐Method=GET
  • 基于Path请求路径的断言工厂

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

‐Path=/foo/segment 
  • 基于Query请求参数的断言工厂

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。

‐Query=baz, ba.
  • 基于路由权重的断言工厂

WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。

routes:
  ‐ id: weight_route1
    uri: host1
    predicates:
      ‐ Path=/product/**
      ‐ Weight=group3, 1
  ‐ id: weight_route2
    uri: host2
    predicates:
      ‐ Path=/product/**
      ‐ Weight= group3, 9
  • 自定义路由断言工厂

自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。

  • (1)必须spring组件 bean
  • (2)类必须加上RoutePredicateFactory作为结尾
  • (3)必须继承AbstractRoutePredicateFactory
  • (4)必须声明静态内部类 声明属性来接收 配置文件中对应的断言的信息
  • (5)需要结合shortcutFieldOrder进行绑定
  • (6)通过apply进行逻辑判断 true就是匹配成功 false匹配失败

注意: 命名需要以 RoutePredicateFactory 结尾

@Component
public class CheckAuthRoutePredicateFactory
        extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> 



    public CheckAuthRoutePredicateFactory() 
        super(CheckAuthRoutePredicateFactory.Config.class);
    

    @Override
    public List<String> shortcutFieldOrder() 
        return Arrays.asList("name");
    

    @Override
    public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) 
        return new GatewayPredicate() 
            @Override
            public boolean test(ServerWebExchange exchange) 

                if(config.getName().equals("xushu"))
                    return true;
                
                return false;
            

        ;
    


    // 用于接收配置文件中 断言的信息
    @Validated
    public static class Config 
        private String name;

        public String getName() 
            return name;
        

        public void setName(String name) 
            this.name = name;
        
    


yml中配置:

server:
  port: 8088
spring:
  cloud:
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: http://localhost:8020  #需要转发的地址   lb: 使用nacos中的本地负载均衡策略  order-service服务名
          #断言规则 用于路由规则的匹配
          #测试:http://localhost:8888/order/findOrderByUserId/1
          predicates:
            - Path=/order/**
            - CheckAuth=xushu2

四、过滤器工厂( Gateway Filter Factories)配置

Gateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等。

参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

过滤器工厂作用参数
AddRequestHeader为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand 的名称
FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个preserveHostHeader=true 的 属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正 则表达式,重写后的值
SaveSession在转发请求之前,强制执行WebSession::save 操作
secureHeaders为原始响应添加一系列起安全作用的响应头无,支持修改这些安全响应头的值
SetPath修改原始的请求路径修改后的路径
SetResponseHeader修改原始响应中某个Header的值Header名称,修改后的值
SetStatus修改原始响应的状态码HTTP 状态码,可以是数字,也可以是字符串
StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
Retry针对不同的响应进行重试retries、statuses、methods、series
RequestSize设置允许接收最大请求包的大 小。如果请求包大小超过设置的值,则返回 413 Payload Too Large请求包大小,单位为字节,默认值为5M
ModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容
ModifyResponseBody修改原始响应体的内容修改后的响应体内容

1️⃣添加请求头

spring:
  cloud:
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: http://localhost:8020  #需要转发的地址
          #配置过滤器工厂
          filters:
            ‐ AddRequestHeader=X‐Request‐color, red #添加请求头

测试:http://localhost:8888/order/testgateway

@GetMapping("/testgateway")
public String testGateway(HttpServletRequest request) throws Exception 
  log.info("gateWay获取请求头X‐Request‐color:" +request.getHeader("X‐Request‐color"));
  return "success";


@GetMapping("/testgateway2")
public String testGateway(@RequestHeader("X‐Request‐color") String color) throws Exception 
  log.info("gateWay获取请求头X‐Request‐color:"+color);
  return "success";

2️⃣添加请求参数

spring:
  cloud:
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: http://localhost:8020  #目标微服务的请求地址和端口
          #配置过滤器工厂在这里插入代码片
          filters:
            ‐ AddRequestParameter=color, blue # 添加请求参数

测试:http://localhost:8888/order/testgateway3

@GetMapping("/testgateway3")
public String testGateway3(@RequestParam("color") String color) throws Exception 
  log.info("gateWay获取请求参数color:"+color);
  return "success";

3️⃣为匹配的路由统一添加前缀

spring:
  cloud:
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: http://localhost:8020  #目标微服务的请求地址和端口
          #配置过滤器工厂在这里插入代码片
          filters:
            ‐ PrefixPath=/mall‐order # 添加前缀 对应微服务需要配置context‐path

mall-order中需要配置:

server:
  servlet:
    context‐path: /mall‐order

测试:http://localhost:8888/order/findOrderByUserId/1====》 http://localhost:8020/mall­order/order/findOrderByUserId/1

4️⃣重定向操作

spring:
  cloud:
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: http://localhost:8020  #目标微服务的请求地址和端口
          #配置过滤器工厂在这里插入代码片
          filters:
            ‐ RedirectTo=302, https://www.baidu.com/ #重定向到百度

测试:http://localhost:8888/order/findOrderByUserId/1

5️⃣自定义过滤器工厂

继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。

@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory 

  @Override
  public GatewayFilter apply(NameValueConfig config) 
    return (exchange, chain)> 
      log.info("调用CheckAuthGatewayFilterFactory===" + config.getName() + ":" + config.getValue());
      return chain.filter(exchange);
    ;
  

配置自定义的过滤器工厂:

spring:
  cloud:
    # gateway的配置
    gateway:
      #路由规则
      routes:
        - id: order_route  # 路由的唯一标识,路由到order
          uri: http://localhost:8020  #目标微服务的请求地址和端口
          #配置过滤器工厂在这里插入代码片
          filters:
            ‐ CheckAuth=fox,

测试:

五、全局过滤器(Global Filters)配置

局部过滤器和全局过滤器区别:

  • 局部:局部针对某个路由, 需要在路由中进行配置
  • 全局:针对所有路由请求, 一旦定义就会投入使用

GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。

1️⃣LoadBalancerClientFilter

LoadBalancerClientFilter 会查看exchange的属性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的scheme是 lb,比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将 myservice 解析成实际的host和port,并替换掉ServerWebExchangeUtils. GATEWAY_REQUEST_URL_ATTR 的内容。

其实就是用来整合负载均衡器Ribbon的:

spring:
  cloud:
    gateway:
      routes:
        - id: order_route 
          uri: lb://mall‐order
          predicates:
            ‐ Path=/order/**

2️⃣自定义全局过滤器

@Component
public class LogFilter implements GlobalFilter 
  Logger log= LoggerFactory.getLogger(this.getClass());
  
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) 
    log.info(exchange.getRequest().getPath().value());
    return chain.filter(exchange);
  

六、Gateway跨域配置(CORS Configuration)


🍀(1)通过yml配置的方式

通过yml配置的方式参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration


spring:
  cloud:
    gateway:
      # 跨域配置
      globalcors:
        cors-configurations:
          '[/**]':   # 允许跨域访问的资源
            allowedOrigins: "*"   #跨域允许来源
            allowedMethods:
              - GET
              - POST

🍀(2)通过java配置的方式:

@Configuration
public class CorsConfig 
  @Bean
  public CorsWebFilter corsFilter() 
    CorsConfiguration config = new CorsConfiguration();
    config.addAllowedMethod("*");
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
    source.registerCorsConfiguration("/**", config);

    return new CorsWebFilter(source);
  

七、Gateway整合sentinel流控降级

网关作为内部系统外的一层屏障, 对内起到一定的保护作用, 限流便是其中之一。网关层的限流可以简单地针对不同路由进行限流,也可针对业务的接口进行限流,或者根据接口的特征分组限流。

参考文档:https://github.com/alibaba/Sentinel/wiki/网关限流

(1)添加依赖

Spring Cloud Alibaba全家桶——分布式事务组件Seata

前言

本文小新为大家带来 分布式事务组件Seata 相关知识,具体内容包括分布式事务简介(包括:事务简介本地事务分布式事务典型场景分布式事务理论基础分布式事务解决方案),分布式事务Seata使用(包括:Seata是什么Seata的三大角色Seata的设计思路Seata的设计亮点Seata存在的问题),Seata快速开始(包括:Seata Server(TC)环境搭建业务系统集成Client)等进行详尽介绍~

不积跬步,无以至千里;不积小流,无以成江海。每天进步一点点,在成为强者的路上,小新与大家共同成长!

📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)

↩️本文上接:Spring Cloud Alibaba全家桶(八)——Sentinel规则持久化


目录

分布式事务组件Seata

一、分布式事务简介

1️⃣事务简介

事务(Transaction) 是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。 这四个属性通常称为ACID特性。

  • 原子性(atomicity): 个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
  • 一致性(consistency): 事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务 的中间状态不能被观察到的。
  • 隔离性(isolation): 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数 据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚读)、串行化(serializable,解决幻读)。
  • 持久性(durability): 持久性也称永久性(permanence),指一个事务一旦提交,它对数据库 中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务,及时不能都很好的满足,也要考虑支持到什么程度。

2️⃣本地事务

大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务
(Local Transaction)
。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所
示:

在JDBC编程中,我们通过java.sql.Connection对象来开启、关闭或者提交事务。代码如下所
示:

Connection conn = ... //获取数据库连接
conn.setAutoCommit(false); //开启事务
try
  //...执行增删改查sql
  conn.commit(); //提交事务
catch (Exception e) 
  conn.rollback();//事务回滚
finally
  conn.close();//关闭链接

3️⃣分布式事务典型场景

当下互联网发展如火如荼,绝大部分公司都进行了数据库拆分和服务化(SOA)。在这种情况下,完
成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。

典型的分布式事务场景:

  • 跨库事务

跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。笔者见过
一个相对比较复杂的业务,一个业务中同时操作了9个库。下图演示了一个服务同时操作2个库的情
况:

  • 分库分表

通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。如
下图,将数据库B拆分成了2个库:

对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。如,对于sql:insert into user(id,name) values (1,"张三"),(2,"李四")。这条sql是操作单库的语法,单库情况下,可以保证事务的一致性。

但是由于现在进行了分库分表,开发人员希望将1号记录插入分库1,2号记录插入分库2。所以数据库中间件要将其改写为2条sql,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都
失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。

  • 服务化

微服务架构是目前一个比较一个比较火的概念。例如上面笔者提到的一个案例,某个应用同时操作了9个库,这样的应用业务逻辑必然非常复杂,对于开发人员是极大的挑战,应该拆分成不同的独立服务,以简化业务逻辑。拆分后,独立服务之间通过RPC框架来进行远程调用,实现彼此的通信。下图演示了一个3个服务之间彼此调用的架构:

Service A完成某个功能需要直接操作数据库,同时需要调用Service B和Service C,而Service B 又同时操作了2个数据库,Service C也操作了一个库。需要保证这些跨服务的对多个数据库的操作要不都成功,要不都失败,实际上这可能是 典型的分布式事务场景。

小结: 上述讨论的分布式事务场景中,无一例外的都直接或者间接的操作了多个数据库。如何保证事务的ACID特性,对于分布式事务实现方案而言,是非常大的挑战。同时,分布式事务实现方案还必须要考虑性能的问题,如果为了严格保证ACID特性,导致性能严重下降,那么对于一些要求快速响应的业务,是无法接受的。

4️⃣分布式事务理论基础

解决分布式事务,也有相应的规范和协议。分布式事务相关的协议有2PC、3PC。

由于三阶段提交协议3PC非常难实现,目前市面主流的分布式事务解决方案都是2PC协议。2PC具有普适性——协议一样的存在,目前绝大多数分布式解决方案都是以两阶段提交协议2PC为基础的。

2PC(两阶段提交,Two-Phase Commit)

顾名思义,分为两个阶段:Prepare 和 Commit。

🍀(1)Prepare:提交事务请求

基本流程如下图:

  • (1)询问协调者向所有参与者发送事务请求,询问是否可执行事务操作,然后等待各个参与者的响应。
  • (2)执行各个参与者接收到协调者事务请求后,执行事务操作(例如更新一个关系型数据库表中的记录),并将Undo 和 Redo 信息记录事务日志中。
  • (3)响应如果参与者成功执行了事务并写入 Undo 和 Redo 信息,则向协调者返回 YES 响应,否则返回 NO响应。当然,参与者也可能宕机,从而不会返回响应。

🍀(2)Commit:执行事务提交

执行事务提交分为两种情况,正常提交和回退。

正常提交事务

流程如下图:

  • (1)commit 请求 协调者向所有参与者发送 Commit 请求。
  • (2)事务提交 参与者收到 Commit 请求后,执行事务提交,提交完成后释放事务执行期占用的所有资源。
  • (3)反馈结果 参与者执行事务提交后向协调者发送 Ack 响应。
  • (4)完成事务 接收到所有参与者的 Ack 响应后,完成事务提交。

中断事务

在执行 Prepare 步骤过程中,如果某些参与者执行事务失败、宕机或与协调者之间的网络中断,那么协调者就无法收到所有参与者的 YES 响应,或者某个参与者返回了 No 响应,此时,协调者就会进入回退流程,对事务进行回退。流程如下图红色部分(将 Commit 请求替换为红色的 Rollback 请求):

  • (1)rollback 请求 协调者向所有参与者发送 Rollback 请求。
  • (2)事务回滚 参与者收到 Rollback 后,使用 Prepare 阶段的 Undo 日志执行事务回滚,完成后释放事务执行期占用的所有资源。
  • (3)反馈结果 参与者执行事务回滚后向协调者发送 Ack 响应。
  • (4)中断事务 接收到所有参与者的 Ack 响应后,完成事务中断。

2PC 存在的问题:

  • (1)同步阻塞 参与者在等待协调者的指令时,其实是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行。
    倘若参与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去。
  • (2)单点 在 2PC 中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源。如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后,可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的全部状态信息(例如已等待 Prepare 响应的时长等),所以也无法顺利处理上一个事务。
  • (3)数据不一致 Commit 事务过程中 Commit 请求/Rollback 请求可能因为协调者宕机或协调者与参与者网络问题丢失,那么就导致了部分参与者没有收到 Commit/Rollback 请求,而其他参与者则正常收到执行了Commit/Rollback 操作,没有收到请求的参与者则继续阻塞。这时,参与者之间的数据就不再一致了。当参与者执行 Commit/Rollback 后会向协调者发送 Ack,然而协调者不论是否收到所有的参与者的 Ack,该事务也不会再有其他补救措施了,协调者能做的也就是等待超时后像事务发起者返回一个“我不确定该事务是否成功”。
  • (4)环境可靠性依赖 协调者 Prepare 请求发出后,等待响应,然而如果有参与者宕机或与协调者之间的网络中断,都会导致协调者无法收到所有参与者的响应,那么在 2PC 中,协调者会等待一定时间,然后超时后,会触发事务中断,在这个过程中,协调者和所有其他参与者都是出于阻塞的。这种机制对网络问题常见的现实环境来说太苛刻了。

5️⃣分布式事务解决方案

常见分布式事务解决方案:

  • 1、 seata 阿里分布式事务框架
  • 2、 消息队列
  • 3、 saga
  • 4、 XA

他们有一个共同点,都是“两阶段(2PC)”。“两阶段”是指完成整个分布式事务,划分成两个步骤完成。
实际上,这四种常见的分布式事务解决方案,分别对应着分布式事务的四种模式:AT、TCC、Saga、XA;四种分布式事务模式,都有各自的理论基础,分别在不同的时间被提出;每种模式都有它的适用场景,同样每个模式也都诞生有各自的代表产品;而这些代表产品,可能就是我们常见的(全局事务、基于可靠消息、 大努力通知、TCC)。

下面我们分别来看4种模式(AT、TCC、Saga、XA)的分布式事务实现。

🍀(1)AT模式(auto transcation)

AT 模式是一种无侵入的分布式事务解决方案。

阿里seata框架,实现了该模式。

在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

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

  • 一阶段

在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

  • 二阶段提交

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

  • 二阶段回滚

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

AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能
轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。

🍀(2)TCC 模式(Try、Confirm and Cancel)

TCC 模式特点:

  • (1)侵入性比较强, 并且得自己实现相关事务控制逻辑
  • (2)在整个过程基本没有锁,性能更强

TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。

TCC 三个方法描述:

  • Try:资源的检测和预留;
  • Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
  • Cancel:预留资源释放;

对于TCC模式的实践过程中要注意以下事项:

  • (1)业务模型分2阶段设计

用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方
法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入
性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。

  • (2)允许空回滚

Cancel 接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这
时会触发 Cancel 接口,这时 Cancel 执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而 Cancel 又没有对应的业务数据可以进行回滚。

  • (3)防悬挂控制

悬挂的意思是:Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而 终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。

  • (4)幂等控制

幂等性的意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。因为网络抖动或拥堵可能会超时,事务管理器会对资源进行重试操作,所以很可能一个业务操作会被重复调用,为了不因为重复调用而多次占用资源,需要对服务设计时进行幂等控制,通常我们可以用事务 xid 或业务主键判重来控制。

🍀(3)saga模式

Saga 理论出自 Hector & Kenneth 1987发表的论文 Sagas。

saga模式的实现,是长事务解决方案。

Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正
补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

如图:T1-T3都是正向的业务流程,都对应着一个冲正逆向操作C1-C3

分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。

Saga 正向服务与补偿服务也需要业务开发者实现。因此是业务入侵的。

Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。

Saga 模式使用场景:

  • Saga 模式适用于业务流程长且需要保证事务终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
  • 事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。

Saga模式的优势:

  • 一阶段提交本地数据库事务,无锁,高性能;
  • 参与者可以采用事务驱动异步执行,高吞吐;
  • 补偿服务即正向服务的“反向”,易于理解,易于实现;

Saga模式的缺点:

  • Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。后续会讲到对于缺乏隔离性的应对措施。

与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:

  • 空补偿:逆向操作早于正向操作时;
  • 防悬挂控制:空补偿后要拒绝正向操作;
  • 幂等。

🍀(4)XA模式

XA是X/Open DTP组织(X/Open DTP group)定义的两阶段提交协议,XA被许多数据库(如
Oracle、DB2、SQL Server、MySQL)和中间件等工具(如CICS 和 Tuxedo)本地支持 。

X/Open DTP模型(1994)包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)。

XA接口函数由数据库厂商提供。XA规范的基础是两阶段提交协议2PC。

JTA(Java Transaction API) 是Java实现的XA规范的增强版 接口。

在XA模式下,需要有一个[全局]协调器,每一个数据库事务完成后,进行第一阶段预提交,并通知协
调器,把结果给协调器。协调器等所有分支事务操作完成、都预提交后,进行第二步;第二步:协调器通知每个数据库进行逐个commit/rollback。其中,这个全局协调器就是XA模型中的TM角色,每个分支事务各自的数据库就是RM。

MySQL 提供的XA实现:https://dev.mysql.com/doc/refman/5.7/en/xa.html

XA模式下的开源框架有: atomikos,其开发公司也有商业版本。

XA模式缺点: 事务粒度大。高并发下,系统可用性低。因此很少使用。

🍀(5)(AT、TCC、Saga、XA)模式分析

四种分布式事务模式,分别在不同的时间被提出,每种模式都有它的适用场景:

  • AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学 习成本。
  • TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。
  • Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成 层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供TCC 要求的接口,也可以使用 Saga 模式。
  • XA模式是分布式强一致性的解决方案,但性能低而使用较少。

总结:

分布式事务本身就是一个技术难题,业务中具体使用哪种方案还是需要不同的业务特点自行选择,但是我们也会发现,分布式事务会大大的提高流程的复杂度,会带来很多额外的开销工作,代码量上去了,业务复杂了,性能下跌了。

所以,当我们真实开发的过程中,能不使用分布式事务就不使用。

二、分布式事务Seata使用

1️⃣Seata是什么

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供 AT、TCC、SAGA 和XA 事务模式,为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS(Global Transaction Service 全局事务服务)

官网: https://seata.io/zh-cn/index.html

源码: https://github.com/seata/seata

官方Demo: https://github.com/seata/seata-samples

2️⃣Seata的三大角色

在 Seata 的架构中,一共有三个角色:

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

其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。

在 Seata 中,一个分布式事务的生命周期如下:

  • (1)TM 请求 TC 开启一个全局事务。TC 会生成一个 XID作为该全局事务的编号。XID,会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。当一进入事务方法中就会生成XID ,global_table 就是存储的全局事务信息 ;
  • (2)RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的 XID 进行关联。当运行数据库操作方法,branch_table 存储事务参与者;
  • (3)TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚;
  • (4)TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。

3️⃣Seata的设计思路

AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如图。

第一阶段业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。核心在于对业务sql进行解析,转换成undolog,并同时入库,这是怎么做的呢?

先抛出一个概念DataSourceProxy代理数据源,通过名字大家大概也能基本猜到是什么个操作,后面做具体分析参考官方文档: https://seata.io/zh-cn/docs/dev/mode/at-mode.html

第二阶段分布式事务操作成功,则TC通知RM异步删除undolog。

分布式事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

整体执行流程:

4️⃣Seata的设计亮点

相比与其它分布式事务框架,Seata架构的亮点主要有几个:

  • (1)应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性;
  • (2)将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚;
  • (3)通过全局锁实现了写隔离与读隔离。

5️⃣Seata存在的问题

  • 性能损耗

一条Update的SQL,则需要全局事务xid获取(与TC通讯)、before image(解析SQL,查询一次数据库)、after image(查询一次数据库)、insert undo log(写一次数据库)、before commit(与TC通讯,判断锁冲突),这些操作都需要一次远程通讯RPC,而且是同步的。另外undo log写入时blob字段的插入性能也是不高的。每条写SQL都会增加这么多开销,粗略估计会增加5倍响应时间。

  • 性价比

为了进行自动补偿,需要对所有交易生成前后镜像并持久化,可是在实际业务场景下,这个是成功率有多高,或者说分布式事务失败需要回滚的有多少比率?按照二八原则预估,为了20%的交易回滚,需要将80%的成功交易的响应时间增加5倍,这样的代价相比于让应用开发一个补偿交易是否是值得?

  • 全局锁——热点数据

相比XA,Seata 虽然在一阶段成功后会释放数据库锁,但一阶段在commit前全局锁的判定也拉长了对数据锁的占有时间,这个开销比XA的prepare低多少需要根据实际业务场景进行测试。全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点数据,这个问题会更加严重。

  • 全局锁——回滚锁释放时间

Seata在回滚时,需要先删除各节点的undo log,然后才能释放TC内存中的锁,所以如果第二阶段是回滚,释放锁的时间会更长。

  • 死锁问题

Seata的引入全局锁会额外增加死锁的风险,但如果出现死锁,会不断进行重试,最后靠等待全局锁超时,这种方式并不优雅,也延长了对数据库锁的占有时间。

三、Seata快速开始

1️⃣Seata Server(TC)环境搭建

Seata部署指南:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html

🍀步骤一:下载安装包

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

目前最新的版本为1.6.1:

🍀步骤二:存储模式配置

启动包: seata–>conf–>application.yml,修改store.mode=“db或者redis”

源码: 根目录–>seata-server–>resources–>application.yml,修改store.mode=“db或者redis”

1.5.0以下版本:

启动包: seata–>conf–>file.conf,修改store.mode=“db或者redis”

源码: 根目录–>seata-server–>resources–>file.conf,修改store.mode=“db或者redis”

🍀步骤三:建表(仅db存储模式)

新建表: 可以去seata提供的资源信息中下载:https://github.com/seata/seata/blob/1.6.1/script/server/db/mysql.sql

🍀步骤四:修改数据库连接|redis属性配置

启动包: seata–>conf–>application.example.yml中附带额外配置,将其db|redis相关配置复制至application.yml,进行修改store.db或store.redis相关属性。

源码: 根目录–>seata-server–>resources–>application.example.yml中附带额外配置,将其db|redis相关配置复制至application.yml,进行修改store.db或store.redis相关属性。

1.5.0以下版本:

启动包: seata–>conf–>file.conf,修改store.db或store.redis相关属性。

源码: 根目录–>seata-server–>resources–>file.conf,修改store.db或store.redis相关属性。

🍀步骤五:启动

码启动: 执行ServerApplication.java的main方法

命令启动:

seata-server.sh -h 127.0.0.1 -p 8091 -m db

1.5.0以下版本

源码启动: 执行Server.java的main方法

命令启动:

seata-server.sh -h 127.0.0.1 -p 8091 -m db -n 1 -e test

命令启动参数:

-h: 注册到注册中心的ip
-p: Server rpc 监听端口
-m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3及以上版本支持redis)
-n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突
-e: 多环境配置参考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html

2️⃣业务系统集成Client

🍀步骤一:添加seata依赖(建议单选)

  • 依赖seata-all
  • 依赖seata-spring-boot-starter,支持yml、properties配置(.conf可删除),内部已依赖seata-all
  • 依赖spring-cloud-alibaba-seata,内部集成了seata,并实现了xid传递

🍀步骤二:undo_log建表、配置参数(仅AT模式)

🍀步骤三:数据源代理(不支持自动和手动配置并存)

(1)如果使用seata-all:

  • 0.9.0版本开始seata支持自动代理数据源:
1.1.0: seata-all取消属性配置,改由注解@EnableAutoDataSourceProxy开启,并可选择jdk proxy或者cglib proxy
1.0.0: client.support.spring.datasource.autoproxy=true
0.9.0: support.spring.datasource.autoproxy=true

如果采用XA模式,@EnableAutoDataSourceProxy(dataSourceProxyMode = "XA")

  • 手动配置可参考下方的例子:
@Primary
@Bean("dataSource")
public DataSource dataSource(DataSource druidDataSource) 
    //AT 代理 二选一
    return new DataSourceProxy(druidDataSource);
    //XA 代理
    return new DataSourceProxyXA(druidDataSource)

(2)如果使用seata-starter

  • 使用自动代理数据源时,如果使用XA模式还需要调整配置文件

application.yml:

seata:
  data-source-proxy-mode: XA
  • 如何关闭seata-spring-boot-starter的数据源自动代理?

application.yml:

seata:
  enable-auto-data-source-proxy: false

🍀步骤四:初始化GlobalTransactionScanner

手动:

       @Bean
       public GlobalTransactionScanner globalTransactionScanner() 
           String applicationName = this.applicationContext.getEnvironment().getProperty("spring.application.name");
           String txServiceGroup = this.seataProperties.getTxServiceGroup();
           if (StringUtils.isEmpty(txServiceGroup)) 
               txServiceGroup = applicationName + "-fescar-service-group";
               this.seataProperties.setTxServiceGroup(txServiceGroup);
           
   
           return new GlobalTransactionScanner(applicationName, txServiceGroup);
       

自动:

引入seata-spring-boot-starter、spring-cloud-starter-alibaba-seata等jar

🍀步骤五:实现xid跨服务传递

  • 手动 参考源码integration文件夹下的各种rpc实现 module
  • 自动 springCloud用户可以引入spring-cloud-starter-alibaba-seata,内部已经实现xid传递

🍀业务使用——注解拦截

全局事务:

@GetMapping(value = "testCommit")
@GlobalTransactional
public Object testCommit(@RequestParam(name = "id",defaultValue = "1") Integer id,
    @RequestParam(name = "sum", defaultValue = "1") Integer sum) 
    Boolean ok = productService.reduceStock(id, sum);
    if (ok) 
        LocalDateTime now = LocalDateTime.now();
        Orders orders = new Orders();
        orders.setCreateTime(now);
        orders.setProductId(id);
        orders.setReplaceTime(now);
        orders.setSum(sum);
        orderService.save(orders);
        return "ok";
     else 
        return "fail";
    

TCC:

/**
 * 定义两阶段提交 name = 该tcc的bean名称,全局唯一 commitMethod = commit 为二阶段确认方法 rollbackMethod = rollback 为二阶段取消方法
 * useTCCFence=true 为开启防悬挂
 * BusinessActionContextParameter注解 传递参数到二阶段中
 *
 * @param params  -入参
 * @return String
 */
@TwoPhaseBusinessAction(name = "beanName", commitMethod = "commit", rollbackMethod = "rollback", useTCCFence = true)
public void insert(@BusinessActionContextParameter(paramName = "params") Map<String, String> params) 
    logger.info("此处可以预留资源,或者利用tcc的特点,与AT混用,二阶段时利用一阶段在此处存放的消息,通过二阶段发出,比如redis,mq等操作");


/**
 * 确认方法、可以另命名,但要保证与commitMethod一致 context可以传递try方法的参数
 *
 * @param context 上下文
 * @return boolean
 */
public void commit(BusinessActionContext context) 
    logger.info("预留资源真正处理,或者发出mq消息和redis入库");


/**
 * 二阶段取消方法
 *
 * @param context 上下文
 * @return boolean
 */
public void rollback(BusinessActionContext context) 
    logger.info("预留资源释放,或清除一阶段准备让二阶段提交时发出的消息缓存");

🍀业务使用——切点表达式

全局事务:

    @Bean
    public AspectTransactionalInterceptor aspectTransactionalInterceptor () 
        return new AspectTransactionalInterceptor();
    

    @Bean
    public Advisor txAdviceAdvisor(AspectTransactionalInterceptor aspectTransactionalInterceptor ) 
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("配置切点表达式使全局事务拦截器生效");
        return new DefaultPointcutAdvisor(pointcut, aspectTransactionalInterceptor);
    

后记

👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

以上是关于Spring Cloud Alibaba全家桶——微服务网关Gateway组件的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Alibaba全家桶——微服务调用组件Feign

Spring Cloud Alibaba全家桶——微服务链路追踪SkyWalking

Spring Cloud Alibaba全家桶——微服务组件Nacos注册中心

Spring Cloud Alibaba全家桶——微服务组件Sentinel介绍与使用

Spring Cloud Alibaba全家桶——微服务组件Nacos配置中心

Spring Cloud Alibaba全家桶——Sentinel规则持久化