阿里分布式事务中间件Seata

Seata 是阿里巴巴开源的分布式事务中间件,以高效并且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题。

项目说明

本项目演示如何使用Seata Starter完成Spring Cloud应用的分布式事务接入。

参考:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/seata-example

准备工作

在运行此示例之前,你需要先完成如下几步准备工作:

  • 配置数据库
  • 创建UNDO_LOG
  • 创建示例中业务所需要的数据库表
  • 启动Seata Server

配置数据库

首先,需要有一个支持InnoDB引擎的MySQL数据库。

注意:实际上,Seata支持不同的应用使用完全不相干的数据库,但是这里为了简单地演示一个原理,所以我们选择了只使用一个数据库。

account-serverorder-servicestorage-service这三个应用中的resources目录下的application.properties文件中的如下配置修改成实际运行环境中的实际配置。

mysql.server.ip=your mysql server ip address
mysql.server.port=your mysql server listening port
mysql.db.name=your database name for test

mysql.user.name=your mysql server username
mysql.user.password=your mysql server password

创建 undo_log 表

Seata AT模式 需要使用到undo_log表。

-- 注意此处0.3.0+ 增加唯一索引 ux_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;

创建示例中业务所需要的数据库表

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;

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 `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;

启动 Seata Server

打开页面:https://github.com/seata/seata/releases ,下载最新版本的Seata Server

进入解压之后的bin目录,执行如下命令来启动,所有启动参数为可选项

sh seata-server.sh -p $LISTEN_PORT -m $MODE(file or db) -h $HOST -e $ENV
  • -p seata-server监听服务端口号
  • -m 存储模式,可选值:filedbfile用于单点模式,db用于ha模式,当使用db存储模式,需要修改配置中store配置节点的数据库配置,同时在数据库中初始化global_table、branch_table和lock_table
  • -h 用于解决seata-server和业务侧跨网络问题,其配置的host值直接显示到注册中心的服务可用地址host,当跨网络时这里需要配置为公网IPNATIP,若都在同一局域网则无需配置
  • -e 用于解决多环境配置中心隔离问题

在这个示例中,采用如下命令来启动 Seata Server

sh seata-server.sh -p 8091 -m file

注意:如果修改了endpoint且注册中心使用默认file类型,那么记得需要在各个示例工程中的file.conf文件中,修改grouplist的值(当registry.confregistry.typeconfig.typefile时会读取内部的file节点中的文件名,若type不为file将直接从配置类型的对应元数据的注册配置中心读取数据),推荐使用Nacos作为配置注册中心。

运行示例

分别运行account-serverorder-servicestorage-servicebusiness-service这四个应用的Main函数,启动示例。

启动示例后,通过HTTPGET方法访问如下两个URL,可以分别验证在business-service中 通过RestTemplateFeignClient调用其他服务的场景。

http://127.0.0.1:18081/seata/feign

http://127.0.0.1:18081/seata/rest

如何验证分布式事务成功

Xid 信息是否成功传递

account-serverorder-servicestorage-service三个服务的Controller中,第一个执行的逻辑都是输出RootContext中的Xid信息,如果看到都输出了正确的Xid信息,即每次都发生变化,且同一次调用中所有服务的Xid都一致。则表明SeataXid的传递和还原是正常的。

数据库中数据是否一致

在本示例中,我们模拟了一个用户购买货物的场景,StorageService负责扣减库存数量,OrderService负责保存订单,AccountService负责扣减用户账户余额。

为了演示样例,我们在OrderServiceAccountService中 使用Random.nextBoolean()的方式来随机抛出异常,模拟了在服务调用时随机发生异常的场景。

如果分布式事务生效的话,那么以下等式应该成立

  • 用户原始金额(1000) = 用户现存的金额 + 货物单价 (2) 订单数量 每单的货物数量(2)
  • 货物的初始数量(100) = 货物的现存数量 + 订单数量 * 每单的货物数量(2)

对 Spring Cloud 支持点

  • 通过Spring MVC提供服务的服务提供者,在收到header中含有Seata信息的HTTP请求时,可以自动还原Seata上下文
  • 支持服务调用者通过RestTemplate调用时,自动传递Seata上下文
  • 支持服务调用者通过FeignClient调用时,自动传递Seata上下文
  • 支持SeataClientHystrix同时使用的场景
  • 支持SeataClientSentinel同时使用的场景

account-service

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
    </dependency>
</dependencies>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AccountApplication {

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

}
import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration
public class DatabaseConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource")
    public DataSource storageDataSource() {
        return new DruidDataSource();
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

        jdbcTemplate.update("delete from account_tbl where user_id = 'U100001'");
        jdbcTemplate.update(
                "insert into account_tbl(user_id, money) values ('U100001', 10000)");

        return jdbcTemplate;
    }
}
import java.util.Random;

import io.seata.core.context.RootContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AccountController {

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

    private static final String SUCCESS = "SUCCESS";

    private static final String FAIL = "FAIL";

    private final JdbcTemplate jdbcTemplate;

    private Random random;

    public AccountController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.random = new Random();
    }

    @PostMapping(value = "/account", produces = "application/json")
    public String account(String userId, int money) {
        LOGGER.info("Account Service ... xid: " + RootContext.getXID());

        if (random.nextBoolean()) {
            throw new RuntimeException("this is a mock Exception");
        }

        int result = jdbcTemplate.update(
                "update account_tbl set money = money - ? where user_id = ?",
                new Object[] { money, userId });
        LOGGER.info("Account Service End ... ");
        if (result == 1) {
            return SUCCESS;
        }
        return FAIL;
    }
}
spring.application.name=account-service
server.port=18084
spring.cloud.nacos.discovery.server-addr=localhost:8848

spring.datasource.name="accountDataSource"
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://xxx:3306/seata?useSSL=false&serverTimezone=UTC
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=2
spring.datasource.druid.initial-size=2

seata.enabled=true
spring.cloud.alibaba.seata.tx-service-group=account-service
seata.service.vgroup-mapping.account-service=default
seata.service.grouplist.default=127.0.0.1:8091
seata.service.disable-global-transaction=false

## if use registry center
#seata.registry.type=nacos
#seata.registry.nacos.cluster=default
#seata.registry.nacos.server-addr=localhost
#
## if use config center
#seata.config.type=apollo
#seata.config.apollo.apollo-meta=http://192.168.1.204:8801
#seata.config.apollo.app-id=seata-server

order-service

import java.io.Serializable;

public class Order implements Serializable {

    /**
     * id.
     */
    public long id;

    /**
     * user id.
     */
    public String userId;

    /**
     * commodity code.
     */
    public String commodityCode;

    /**
     * count.
     */
    public int count;

    /**
     * money.
     */
    public int money;

    @Override
    public String toString() {
        return "Order{" + "id=" + id + ", userId='" + userId + '\'' + ", commodityCode='"
                + commodityCode + '\'' + ", count=" + count + ", money=" + money + '}';
    }
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class OderApplication {

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

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Random;

import io.seata.core.context.RootContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class OrderController {

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

    private static final String SUCCESS = "SUCCESS";

    private static final String FAIL = "FAIL";

    private static final String USER_ID = "U100001";

    private static final String COMMODITY_CODE = "C00321";

    private final JdbcTemplate jdbcTemplate;

    private final RestTemplate restTemplate;

    private Random random;

    public OrderController(JdbcTemplate jdbcTemplate, RestTemplate restTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.restTemplate = restTemplate;
        this.random = new Random();
    }

    @PostMapping(value = "/order", produces = "application/json")
    public String order(String userId, String commodityCode, int orderCount) {
        LOGGER.info("Order Service Begin ... xid: " + RootContext.getXID());

        int orderMoney = calculate(commodityCode, orderCount);

        invokerAccountService(orderMoney);

        final Order order = new Order();
        order.userId = userId;
        order.commodityCode = commodityCode;
        order.count = orderCount;
        order.money = orderMoney;

        KeyHolder keyHolder = new GeneratedKeyHolder();

        int result = jdbcTemplate.update(new PreparedStatementCreator() {

            @Override
            public PreparedStatement createPreparedStatement(Connection con)
                    throws SQLException {
                PreparedStatement pst = con.prepareStatement(
                        "insert into order_tbl (user_id, commodity_code, count, money) values (?, ?, ?, ?)",
                        PreparedStatement.RETURN_GENERATED_KEYS);
                pst.setObject(1, order.userId);
                pst.setObject(2, order.commodityCode);
                pst.setObject(3, order.count);
                pst.setObject(4, order.money);
                return pst;
            }
        }, keyHolder);

        order.id = keyHolder.getKey().longValue();

        if (random.nextBoolean()) {
            throw new RuntimeException("this is a mock Exception");
        }

        LOGGER.info("Order Service End ... Created " + order);

        if (result == 1) {
            return SUCCESS;
        }
        return FAIL;
    }

    private int calculate(String commodityId, int orderCount) {
        return 2 * orderCount;
    }

    private void invokerAccountService(int orderMoney) {
        String url = "http://127.0.0.1:18084/account";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();

        map.add("userId", USER_ID);
        map.add("money", orderMoney + "");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(
                map, headers);

        ResponseEntity<String> response = restTemplate.postForEntity(url, request,
                String.class);
    }

}
@SpringBootApplication
public class OderApplication {

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

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
spring.application.name=order-service
server.port=18083
spring.cloud.nacos.discovery.server-addr=localhost:8848

spring.datasource.name="orderDataSource"
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://xxx:3306/seata?useSSL=false&serverTimezone=UTC
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=2
spring.datasource.druid.initial-size=2

seata.enabled=true
spring.cloud.alibaba.seata.tx-service-group=business-service
seata.service.vgroup-mapping.business-service=default
seata.service.grouplist.default=127.0.0.1:8091
seata.service.disable-global-transaction=false

## if use registry center
#seata.registry.type=nacos
#seata.registry.nacos.cluster=default
#seata.registry.nacos.server-addr=localhost
#
## if use config center
#seata.config.type=apollo
#seata.config.apollo.apollo-meta=http://192.168.1.204:8801
#seata.config.apollo.app-id=seata-server

storage-service

import io.seata.core.context.RootContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StorageController {

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

    private static final String SUCCESS = "SUCCESS";

    private static final String FAIL = "FAIL";

    private final JdbcTemplate jdbcTemplate;

    public StorageController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @GetMapping(value = "/storage/{commodityCode}/{count}", produces = "application/json")
    public String echo(@PathVariable String commodityCode, @PathVariable int count) {
        LOGGER.info("Storage Service Begin ... xid: " + RootContext.getXID());
        int result = jdbcTemplate.update(
                "update storage_tbl set count = count - ? where commodity_code = ?",
                new Object[] { count, commodityCode });
        LOGGER.info("Storage Service End ... ");
        if (result == 1) {
            return SUCCESS;
        }
        return FAIL;
    }

}
import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration
public class DatabaseConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource")
    public DataSource storageDataSource() {
        return new DruidDataSource();
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {

        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

        jdbcTemplate.update("delete from storage_tbl where commodity_code = 'C00321'");
        jdbcTemplate.update(
                "insert into storage_tbl(commodity_code, count) values ('C00321', 100)");

        return jdbcTemplate;
    }
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StorageApplication {

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

}
spring.application.name=storage-service
server.port=18082
spring.cloud.nacos.discovery.server-addr=localhost:8848

spring.datasource.name="storageDataSource"
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://xxx:3306/seata?useSSL=false&serverTimezone=UTC
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=2
spring.datasource.druid.initial-size=2

seata.enabled=true
spring.cloud.alibaba.seata.tx-service-group=business-service
seata.service.vgroup-mapping.business-service=default
seata.service.grouplist.default=127.0.0.1:8091
seata.service.disable-global-transaction=false

## if use registry center
#seata.registry.type=nacos
#seata.registry.nacos.cluster=default
#seata.registry.nacos.server-addr=localhost
#
## if use config center
#seata.config.type=apollo
#seata.config.apollo.apollo-meta=http://192.168.1.204:8801
#seata.config.apollo.app-id=seata-server

business-service

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</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>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
</dependencies>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient(autoRegister = false)
public class BusinessApplication {

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

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @FeignClient("storage-service")
    public interface StorageService {

        @GetMapping(path = "/storage/{commodityCode}/{count}")
        String storage(@PathVariable("commodityCode") String commodityCode,
                @PathVariable("count") int count);

    }

    @FeignClient("order-service")
    public interface OrderService {

        @PostMapping(path = "/order")
        String order(@RequestParam("userId") String userId,
                @RequestParam("commodityCode") String commodityCode,
                @RequestParam("orderCount") int orderCount);

    }
}
import com.alibaba.cloud.examples.BusinessApplication.OrderService;
import com.alibaba.cloud.examples.BusinessApplication.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class HomeController {

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

    private static final String SUCCESS = "SUCCESS";

    private static final String FAIL = "FAIL";

    private static final String USER_ID = "U100001";

    private static final String COMMODITY_CODE = "C00321";

    private static final int ORDER_COUNT = 2;

    private final RestTemplate restTemplate;

    private final OrderService orderService;

    private final StorageService storageService;

    public HomeController(RestTemplate restTemplate, OrderService orderService,
            StorageService storageService) {
        this.restTemplate = restTemplate;
        this.orderService = orderService;
        this.storageService = storageService;
    }

    @GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx")
    @GetMapping(value = "/seata/rest", produces = "application/json")
    public String rest() {

        String result = restTemplate.getForObject(
                "http://127.0.0.1:18082/storage/" + COMMODITY_CODE + "/" + ORDER_COUNT,
                String.class);

        if (!SUCCESS.equals(result)) {
            throw new RuntimeException();
        }

        String url = "http://127.0.0.1:18083/order";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        map.add("userId", USER_ID);
        map.add("commodityCode", COMMODITY_CODE);
        map.add("orderCount", ORDER_COUNT + "");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(
                map, headers);

        ResponseEntity<String> response;
        try {
            response = restTemplate.postForEntity(url, request, String.class);
        }
        catch (Exception exx) {
            throw new RuntimeException("mock error");
        }
        result = response.getBody();
        if (!SUCCESS.equals(result)) {
            throw new RuntimeException();
        }

        return SUCCESS;
    }

    @GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx")
    @GetMapping(value = "/seata/feign", produces = "application/json")
    public String feign() {

        String result = storageService.storage(COMMODITY_CODE, ORDER_COUNT);

        if (!SUCCESS.equals(result)) {
            throw new RuntimeException();
        }

        result = orderService.order(USER_ID, COMMODITY_CODE, ORDER_COUNT);

        if (!SUCCESS.equals(result)) {
            throw new RuntimeException();
        }

        return SUCCESS;
    }

}
import java.io.Serializable;

public class Order implements Serializable {

    /**
     * order id.
     */
    public long id;

    /**
     * user id.
     */
    public String userId;

    /**
     * commodity code.
     */
    public String commodityCode;

    /**
     * count.
     */
    public int count;

    /**
     * money.
     */
    public int money;

    @Override
    public String toString() {
        return "Order{" + "id=" + id + ", userId='" + userId + '\'' + ", commodityCode='"
                + commodityCode + '\'' + ", count=" + count + ", money=" + money + '}';
    }
}
server.port=18081
spring.application.name=business-service
spring.cloud.nacos.discovery.server-addr=localhost:8848
# The following configuration can be omitted.

#feign.hystrix.enabled=true
#feign.sentinel.enabled=true

logging.level.io.seata=debug

seata.enabled=true
spring.cloud.alibaba.seata.tx-service-group=business-service
seata.service.vgroup-mapping.business-service=default
seata.service.grouplist.default=127.0.0.1:8091
seata.service.disable-global-transaction=false

spring.cloud.loadbalancer.ribbon.enabled=true

## if use registry center
#seata.registry.type=nacos
#seata.registry.nacos.cluster=default
#seata.registry.nacos.server-addr=localhost
#
## if use config center
#seata.config.type=apollo
#seata.config.apollo.apollo-meta=http://192.168.1.204:8801
#seata.config.apollo.app-id=seata-server

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/26/alibaba-distributed-transaction-middleware-seata/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
阿里分布式事务中间件Seata
Seata 是阿里巴巴开源的分布式事务中间件,以高效并且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题。 项目说明 本项目演示如何使用Seata Starter……
<<上一篇
下一篇>>
文章目录
关闭
目 录