SpringCloud--Alibaba入门(OpenFeign、GateWay、Seata)

接着SpringCloud--Alibaba入门(Nacos+Sentinel),上次使用了SpringCloudAlibaba中的Nacos和Sentinel,其中Nacos作为微服务的核心,不仅仅拥有服务注册中心、服务发现,还有配置中心的功能,并且自带Ribbon;Sentinel为保障整体微服务架构的高可用,拥有流控、熔断等功能,剩下还有OpenFeign、GateWay、Seata。

一、OpenFeign

Netflix也是使用的OpenFeign,所以用法是一致的

创建项目,作为服务调用者:

1.基本使用

1.1 依赖

除了nacos服务发现依赖外,还需要OpenFeign依赖,OpenFeign是SpringCloud中的组件,需要注意和SpringBoot的版本关系:

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2021.0.1.0 Spring Cloud 2021.0.1 2.6.3
2.2.7.RELEASE Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2021.1 Spring Cloud 2020.0.1 2.4.2
2.2.6.RELEASE Spring Cloud Hoxton.SR9 2.3.2.RELEASE
2.1.4.RELEASE Spring Cloud Greenwich.SR6 2.1.13.RELEASE
2.2.1.RELEASE Spring Cloud Hoxton.SR3 2.2.5.RELEASE
2.2.0.RELEASE Spring Cloud Hoxton.RELEASE 2.2.X.RELEASE
2.1.2.RELEASE Spring Cloud Greenwich 2.1.X.RELEASE
2.0.4.RELEASE(停止维护,建议升级) Spring Cloud Finchley 2.0.X.RELEASE
1.5.1.RELEASE(停止维护,建议升级) Spring Cloud Edgware 1.5.X.RELEASE

父项目SpringBoot版本为2.3.12.RELEASE,所以导入SpringCloud的Hoxton.SR12依赖:

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.12.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        ...
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
        ...
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

子项目导入OpenFeign依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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-openfeign</artifactId>
        </dependency>
1.2 yml配置

yml配置还是和一般的消费者相同,需要指定nacos地址

server:
  port: 9002

spring:
  application:
    name: openfeign-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

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

1.3 远程调用接口

之前使用过openFeign了,所以这边也不多做介绍,openFeign需要定义一个接口来调用提供者的http接口,定义方式和调用的接口方法相同

这是想要调用的远程服务接口:

使用@FeignClient注解指定服务名,按照远程服务的http接口编写openFeign接口:

@Component
@FeignClient("provider")
public interface DemoFeignClient {

    @RequestMapping("/demo")
    public String demo();

}
1.4 编写controller

注入DemoFeignClient ,并调用远程服务:

@RestController
public class FeignDemoController {
    @Autowired
    private DemoFeignClient demoFeignClient;

    @RequestMapping("/feignDemo")
    public String feignDemo() {
        return demoFeignClient.demo();
    }
}
1.5 启动服务

定义启动类,并添加@EnableFeignClients注解:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OpenFeignConsumer9002Application {

    public static void main(String[] args) {
        SpringApplication.run(OpenFeignConsumer9002Application.class, args);
    }

}

启动消费者和提供者服务:

由于OpenFeign也集成了Ribbon,自带负载均衡效果:

2.服务降级

Ribbon本身就支持服务降级,当调用达到超时时间时,会自动失败,默认超时时间为1s。Sentinel则是支持更细粒度的资源控制,至于项目中使用Sentinel还是OpenFeign自带的Ribbon,看具体的需求

2.1 定义一个远程接口

远程接口中模拟超时,睡眠3s后再返回:

    @RequestMapping("/timeout")
    public String timeout() throws InterruptedException {
        Thread.sleep(3000);
        return "timeout";
    }
2.2 定义openFeign接口方法

远程调用timeout接口:

@Component
@FeignClient(value = "provider")
public interface DemoFeignClient {

    @RequestMapping("/demo")
    public String demo();


    @RequestMapping("/timeout")
    public String timeout();
}
2.3 配置超时时间

超时默认1s,我们也可以通过yml配置自定义超时时间:

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 2000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 2000
2.4 controller层中调用
    @RequestMapping("/feignTimeout")
    public String feignTimeout() {
        return demoFeignClient.timeout();
    }

访问效果,出现500错误:

后台抛出的异常为SocketTimeoutException,如果项目中使用,需要使用SpringMVC的异常处理方法来手动处理该异常:

3.整合Sentinel

之前Netflix中使用Hystrix,当服务降级发生,openFeign可以使用@FeignClient注解的fallback属性指定服务降级的异常处理,让请求返回的更友好,原因是Hystrix和openFeign已经整合了。同样的Sentinel与openFeign配合使用,也进行了整合

3.1 依赖

加入Sentinel的依赖:

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
3.2 yml配置

告知openFeign支持Sentinel:

# 开启sentinel
feign:
  sentinel:
    enabled: true
3.3 降级处理类

使用fallback属性指定自定义的服务降级处理类

@Component
@FeignClient(value = "provider",fallback = DemoFeignClientFallback.class)
public interface DemoFeignClient {

    @RequestMapping("/demo")
    public String demo();


    @RequestMapping("/timeout")
    public String timeout();
}

服务降级处理类实现openFeign接口:

@Component
public class DemoFeignClientFallback implements DemoFeignClient {
    @Override
    public String demo() {
        return "发生服务降级!";
    }

    @Override
    public String timeout() {
        return "发生服务降级!";
    }
}

此时再对接口进行访问:


注:@FeignClient的fallback和@SentinelResource的fallback效果相同,只能处理其他异常,不能处理限流异常(BlockException)

二、GateWay

GateWay就是网关,用于提供对外的统一接口,以防止暴露内部服务地址,相比于Zuul,GateWay拥有更高的性能,底层由Netty框架实现
GateWay的执行流程和过滤器Fliter相同,采用责任链模式,在请求前和请求后都可以对数据进行处理

GateWay核心:

  • 路由:表示一条反向代理的规则,由ID、目标uri、断言、过滤器等组成
  • 断言:http请求中资源的匹配规则,如:请求路径、请求头、请求参数等是否匹配
  • Filter:请求前和请求后的特殊处理,如:对请求头修改、返回结果修改

1.使用GateWay

创建项目:

1.1 依赖

导入nacos和gateway依赖,注意不需要web依赖:

        <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>
1.2 yml配置

除了服务发现外,还需要配置gateway相关,主要是针对路由规则进行配置:

server:
  port: 8050
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes: # 路由规则,可配置多个
        - id: provider # 定义一个id,一般与服务提供者的服务名相同
          uri: http://localhost:8001/provider
          predicates: # 断言,匹配路径用
            - Path=/** # ant匹配符,表示所有目录下的所有路径

1.3 启动服务
@SpringBootApplication
@EnableDiscoveryClient
public class GateWay8050Application {

    public static void main(String[] args) {
        SpringApplication.run(GateWay8050Application.class, args);
    }

}

访问GateWay反向代理的接口:

2.路由规则:配置类方式

除了在yml中配置,配置类也可以进行路由规则的配置,注入Spring容器,返回一个RouteLocator对象,方法入参为 RouteLocatorBuilder

@Configuration
public class GateWayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        // 构建多个路由routes
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        // 具体路由地址
        routes.route("consumer", r -> r.path("/consumer/**").uri("http://localhost:9001/consumer")).build();
        // 返回所有路由规则
        return routes.build();
    }

}

由于使用/**,会优先从yml配置的路由规则的服务进行调用,会出现404找不到资源,所以加上前缀consumer

并为该服务配置context-path

server:
  port: 9001
  servlet:
    context-path: /consumer

启动后调用:

注:该负载均衡效果是消费者调用时Ribbon实现的

3.自动路由

除了手动配置路由规则,GateWay还支持自动路由,由于GateWay本身就会注册到Nacos中,它会根据Nacos中注册的服务名进行匹配,并自带负载均衡,自动路由默认是关闭的,需要yml中配置:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true #标识作为服务提供者注册到nacos中

此时路由规则就不需要写了,启动后,通过服务名进行访问:

4.手动负载均衡

自动路由是根据服务名进行访问的,并自带负载均衡,但此时对外暴露服务名并不合适,想要在调用接口时去除服务名,并且带负载均衡效果,就需要进行手动配置路由规则,路由的uri使用lb协议:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true #标识作为服务提供者注册到nacos中
      routes: # 路由规则,可配置多个
        - id: provider # 定义一个id,一般与服务提供者的服务名相同
          uri: lb://provider # 表示使用负载均衡协议,后面跟上服务名
          predicates: # 断言,匹配路径用
            - Path=/** # ant匹配符,表示所有目录下的所有路径

此时访问就不需要带上服务名了,并且也是有负载均衡的效果:

5.断言

断言的种类有:

种类 描述
After 匹配在指定日期时间之后发生的请求。
Before 匹配在指定日期之前发生的请求。
Between 需要指定两个日期参数,设定一个时间区间,匹配此时间区间内的请求。
Cookie 需要指定两个参数,分别为name和regexp(正则表达式),也可以理解Key和Value,匹配具有给定名称且其值与正则表达式匹配的Cookie。
Header 需要两个参数header和regexp(正则表达式),也可以理解为Key和Value,匹配请求携带信息。
Host 匹配当前请求是否来自于设置的主机。
Method 可以设置一个或多个参数,匹配HTTP请求,比如GET、POST
Path 匹配指定路径下的请求,可以是多个用逗号分隔
Query 需要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合正则表达式。
RemoteAddr 匹配指定IP或IP段,符合条件转发。
Weight 需要两个参数group和weight(int),实现了路由权重功能,按照路由权重选择同一个分组中的路由

断言的使用主要看官网文档即可:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

6.过滤器

GateWay的过滤器和Servlet的过滤器与SpringBoot的拦截器差不多,都能够针对请求前和请求后进行处理。
官方提供的GateWay很多,使用yml配置即可,针对一些简单业务场景使用
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

自定义过滤器,需要实现GlobalFilterOrdered接口,Ordered接口用于设置过滤器的优先级:

public class CustomGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 请求前
        System.out.println("请求前...");
        MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
        System.out.println("请求参数:" + queryParams);
        Mono<Void> filter = chain.filter(exchange);
        // 其他过滤器执行后的请求结果
        System.out.println("请求后...");
        ServerHttpResponse response = exchange.getResponse();
        if (!queryParams.containsKey("token")) {
            response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return response.setComplete();
        }

        System.out.println(response.getStatusCode());
        return filter;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

注入到Spring容器中:

@Configuration
public class FilterConfig {

    @Bean
    public CustomGateWayFilter providerCustomGateWayFilter() {
        return new CustomGateWayFilter();
    }

}

日志打印:

三、分布式事务

分布式事务理论分别为CAP定律BASE理论

1.CAP

CAP由三个英文单词首字母组成,分别为:Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)

  • C(一致性):在分布式系统中,所有数据备份是否在同一时刻都是相同的
  • A(可用性):在集群某个节点故障后,整体能否响应客户端的读写请求
  • P(分区容错性):由通信带来的时限要求。在时限达到后,还未达成数据一致性,那么必须在C和A之间做出选择,要么快速响应客户端,要么保证达成数据一致后再响应客户端,后者可能永远无法响应

CAP定律证实:分布式事务由于网络等原因出现P后,只能够做到CP或AP,无法做到三者同时满足

2.BASE

BASE理论是基于CAP定律发展而来的,由于CAP的强一致性太苛刻了,BASE理论核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

BASE理论由三个英文短语组成:

  • Basically Available(基本可用)
    1.响应时间上,允许有一定时间的延迟
    2.系统功能上,允许服务降级的发生
  • Soft state(软状态)
    允许系统中的数据存在中间状态,并这种状态不影响整体可用性。即允许数据副本同步过程有一定的延迟
  • Eventually consistent(最终一致性)
    所有的数据副本,在经过一段时间同步后达到相同

四、Seata

Seata是提供处理分布式事务解决方案的一个组件,官网:https://seata.io/zh-cn/docs/overview/what-is-seata.html,首先来了解下什么是2PC

1.2PC

2PC就是两阶段提交协议,将整个分布式事务分为两个阶段:

  • P(准备阶段):由事务管理器通知每个事务成员执行本地事务,并形成Undo/Redo日志,Undo日志是记录修改前的日志,用于回滚操作,Redo日志是记录修改后的日志,用于提交操作
  • C(提交阶段):如果某个事务成员响应超时或失败,那么事务管理器通知每个成员回滚,如果全员通过,那么通知提交;由每个事务成员根据事务管理器的具体通知执行提交还是回滚,并释放资源

2.Seata三大角色

Seata也是基于2PC协议,并在2PC基础上进一步抽象,将分布式事务种的成员分为三个角色:

  • TC(Transaction Coordinator)- 事务协调者
    维护全局和分支事务状态,通知各成员全局事务的提交或回滚
  • TM(Transaction Manager)- 事务管理器
    发起全局准备事务、全局提交或回滚事务,交由TC处理
  • RM(Resource Manager)- 资源管理器
    分支事务具体参与者,注册和通知TC分支事务状态,TM也是RM

3.Seata支持的模式

模式 描述 侵入性
AT 使用最多的模式,基于2PC协议,准备阶段生成undo日志,根据提交阶段提交/回滚,删除undo或使用undo回滚
XA 基于数据库的XA协议实现2PC,只有全局事务完成,数据才会真正更新到数据库表中,强调事务一致性时使用
TCC 自定义准备阶段,以及提交/回滚阶段的行为,可以针对不仅仅是数据库资源的分布式事务
Saga 单个事务失败后,可以补偿前面的成功者,具体补偿行为由自定义业务处理,针对长事务时使用

4.Seata安装

下载最新版本1.5.2:https://github.com/seata/seata/releases

4.1 修改配置

来到conf目录下:

1.5.2版本所有的配置都整合进了application.yml,1.4.2版本对registry.conf和file.conf的配置方式就不适用了,配置的例子可以看application.example.yml,我这边就是将application.example.yml内容复制进application.yml,并做了一些删减,主要是针对配置中心、服务注册中心、存储方式进行配置:

server:
  port: 7091
  
console: 
  user: 
    username: aruba
    password: aruba
  
spring:
  application:
    name: seata-server
    
logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  extend:
    logstash-appender:
      destination: 127.0.0.1:4560
    kafka-appender:
      bootstrap-servers: 127.0.0.1:9092
      topic: logback_to_logstash

seata:
  # 配置中心配置
  config:
    # support: nacos 、 consul 、 apollo 、 zk  、 etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace:
      group: SEATA_GROUP
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
      # 配置中心的对应配置的DataID
      data-id: seataServer.properties
  # 服务注册中心配置
  registry:
    # support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa
    type: nacos
    preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace:
      cluster: default
      username: nacos
      password: nacos
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key: ""
      #secret-key: ""
  # 对外服务配置
  server:
    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
    max-commit-retry-timeout: -1
    max-rollback-retry-timeout: -1
    rollback-retry-timeout-unlock-enable: false
    enable-check-auth: true
    enable-parallel-request-handle: true
    retry-dead-threshold: 130000
    xaer-nota-retry-timeout: 60000
    recovery:
      handle-all-session-period: 1000
    undo:
      log-save-days: 7
      log-delete-period: 86400000
    session:
      branch-async-queue-size: 5000 #branch async remove queue size
      enable-branch-async-remove: false #enable to asynchronous remove branchSession
  # 存储模式配置
  store:
    # support: file 、 db 、 redis
    mode: db
    session:
      mode: db
    lock:
      mode: db
    db:
      datasource: druid
      db-type: mysql
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      user: root
      password: root
      min-conn: 5
      max-conn: 100
      global-table: global_table
      branch-table: branch_table
      lock-table: lock_table
      distributed-lock-table: distributed_lock
      query-limit: 100
      max-wait: 5000
  metrics:
    enabled: false
    registry-type: compact
    exporter-list: prometheus
    exporter-prometheus-port: 9898
  transport:
    rpc-tc-request-timeout: 30000
    enable-tc-server-batch-send-response: false
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      boss-thread-size: 1
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login

配置完后,来到bin目录下,执行seata-server.bat批处理文件,此时可能会输出错误日志,原因是连接不到对应的数据库,后面我们会进行配置:

当Nacos种出现seata-server的服务,说明seata启动成功了:

4.2 存储方式

上面我们针对seata的存储方式进行了配置,使用的是db,数据库为mysql,现在需要创建相应的数据库和表

官方提供了建表语句:https://github.com/seata/seata/tree/develop/script/server/db

执行完后:

Seata默认使用的数据库驱动为mysql5版本,想要使用其他的数据库版本,需要自己手动导入jdbc的jar包,官方提供了mysql5和mysql8,我这边使用的是mysql8,所以只要删除mysql5的jdbc即可,来到lib/jdbc目录下:

此时启动seata后,不会报数据库连接异常了

4.3 配置中心

既然是SpringCloudAlibaba,推荐使用Nacos作为配置中心,此外Seata也支持其他的配置中心,下面是我们配置的Nacos作为配置中心的配置:

根据该配置在Nacos配置中心中,新建seataServer.properties配置,可配置的内容官网也提供了,其实配置项和上面yml中的配置项差不多:https://github.com/seata/seata/blob/develop/script/config-center/config.txt

根据提供内容进行简单的修改,主要针对jdbc配置:

#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction routing rules configuration, only for the client
#配置事务组,可以标识为机房地址
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=file
store.lock.mode=file
store.session.mode=file
#Used for password encryption
#store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
#jdbc的配置
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
#store.redis.sentinel.masterName=
#store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
#store.redis.password=
store.redis.queryLimit=100

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false

#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

创建完成,以后就可以通过Nacos配置中心动态的改变配置了:

5.Seata-AT

AT是使用最广泛的模式,Seata-AT模式会自动生成事务的二阶段提交和回滚操作,具体流程为:

  • 第一阶段
    1.TM发起准备事务,执行本地事务并远程调用RM方法
    2.TC拦截业务SQL,并解析SQL,在数据更新前,在TM/RM本地undo_log表中生成对应的Undo日志
    3.TM/RM执行本地事务,数据更新,并形成行锁,防止其他事务修改
  • 第二阶段
    根据第一阶段是否有本地事务执行失败,如果有,发起回滚,TM/RM根据Undo日志执行反向更新操作;如果都执行成功,发起提交,TM/RM删除Undo日志和释放资源
5.1 表设计

设计两张表:订单表(order_tbl)和库存表(stock_tbl)

order_tbl
stock_tbl

stock表中插入一条数据:

5.2 创建项目

分别创建order模块与stock模块:

order模块
stock模块
5.3 yml配置

两个模块只是端口和服务名不同,所以就只贴出一个:

server:
  port: 8201

spring:
  application:
    name: seata-order
  cloud:
    discovery:
      server-addr: 127.0.0.1:8848
    alibaba:
      seata:
        tx-service-group: default_tx_group
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

seata:
  tx-service-group: default_tx_group # 事务组名称,要和服务端对应
  service:
    vgroup-mapping:
      default_tx_group: default # key是事务组名称 value要和服务端的机房名称保持一致

management:
  endpoint:
    web:
      exposure:
        include:'*'

mybatis:
  mapper-locations: classpath:mybatis/*.xml #指定映射文件路径
  type-aliases-package: com.aruba.bean

需要注意的是,我们先前为seata-server配置的事务组,要与客户端yml配置对应上:

seata-server配置
客户端模块yml配置
5.4 @GlobalTransactional

其他的CURD操作就不展示了,在Service层相应方法上使用@GlobalTransactional注解开启分布式全局事务:

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private StockClient stockClient;

    @Override
    @GlobalTransactional// 开启分布式事务
    public void addOrder() {
        Order order = new Order(null, 1, "新增订单");
        orderMapper.addOrder(order);
        stockClient.reduceStock(1);
    }
}

StockClient是使用OpenFeign远程调用stock模块的方法,其方法中抛出一个异常:

@RestController
public class StockController {
    @Autowired
    private StockService stockService;

    @GetMapping("/reduceStock")
    public String reduceStock(@RequestParam("id") Integer id) {
        int i = 1 / 0;
        System.out.println("请求来了");
        stockService.reduceStock(id);
        return "减库存成功";
    }

}

启动服务后,经过测试,远程服务调用失败,数据库中的数据最终都回滚到了最初状态

其他模式

由于篇幅原因,其他模式暂时不做展示,另外三种模式使用的也不多,可以通过官方Demo进行参考:https://github.com/seata/seata-samples

项目地址:

https://gitee.com/aruba/spring-cloud-alibaba-study.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容