Reading Reminder:

  1. This article is for those who have a certain springboot foundation
  2. The Spring Cloud Hoxton RELEASE used for this tutorial
  3. This article relies on the previous project, please check the previous article for seamless connection, or directly download the source: github.com/WinterChenS…

Seata, a popular distributed transaction framework, is integrated. Distributed transactions appear because micro-services cause business branches in different services, and cannot use transactions like local transactions.

Before a summary

  • The first part of the SpringCloud tutorial series
  • Nacos of SpringCloud series (2) | August more challenges
  • SpringCloud series (3) of the Open Feign | August more challenges
  • SpringCloud series (4) the SpringCloud Gateway | August more challenges
  • Swagger Knife4J and Unified Login Permission Verification
  • SpringCloud uses sentinel as a fuse in part 6 of the SpringCloud series
  • Link tracing using SpringCloud Sleuth+Zipkin

What is Seata?

Seata is an open source distributed transaction solution dedicated to providing high performance and easy to use distributed transaction services. Seata will provide users with AT, TCC, SAGA and XA transaction modes to create a one-stop distributed solution for users.

For more information, see the official documentation: What is Seata

Download and install SEata-Server

download

Download address: Releases · Seata/Seata (github.com)

Download zip for Windows and tar.gz for Linux/MAC

Note: How to install NacOS see this: NacOS Quick Start. It is recommended to check out the previous article for slip-in play.

The installation

After unpacking, modify the registry. Conf configuration file (in this case, configure NACOS as the configuration center) :

config {
  type = "nacos"Change this to nacos and change the corresponding configuration nacos {serverAddr ="127.0.0.1:8848"
    namespace = "public"
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"}}Copy the code

Then continue to modify the registry (NACOS as the registry) :

registry {
 
  type = "nacos"

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "DEFAULT_GROUP"# heregroupNeed to work with business services in onegroupWithin the namespace ="public"
    cluster = "default"
    username = "nacos"
    password = "nacos"}}Copy the code

Nacos is used as the registry and configuration center for this article, but you can check the official documentation if you need something else.

Upload the configuration

Download the configuration file template before uploading it

  1. Seata /script/config-center at develop · seata/seata (github.com) See illustration 1
  2. Then locate the shell script in the corresponding configuration center directory, using nacos-config.shScript /config-center/nacos at develop · seata/seata (github.com)And put it into${SEATA_DIR}/script/config-center/nacos/nacos-config.shNote:${SEATA_DIR}Is the root of seata; See illustration 2
  3. Run it in the unzip root of SeataSh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -u nacos -w nacosNote: nacos-config.sh is the script file downloaded in step 2, as shown in legend 3

Illustrations 1:

Legend 2:

Legend 3:

Legend 4:

On the NACOS console (localhost:8848/ nacOS, account secret: nacOS/nacOS) :

You can see that the configuration has been uploaded successfully

For details, see seata/config. TXT at develop · seata/seata (github.com)

Configuration parameter official comparison table: Seata parameter configuration

Caution Modify the configuration of the corresponding database. You can directly modify the configuration in NACOS.

One of the key points, and one of the things that can go wrong, is a configuration parameter,

Service. VgroupMapping.< your service name >-group=default This group needs to be consistent between seata-server and client. My_test_tx_group =default if the default configuration file is service.vgroupMapping.my_test_tx_group=default then we need to configure the group in the application.

seata:
  tx-service-group: my_test_tx_group
Copy the code

Start the seata – server:

Windows: Open the console:./seata-server.bat -h 127.0.0.1

Linux/MacOS: sh [seata-server.sh](http://seata-server.sh) -h 127.0.0.1

Successful startup:

Nacos Registry:

Multiple deployment modes:

  • Deploy Seata Server using Docker
  • Deploy Seata Server using Kubernetes
  • Seata Server is deployed using the Helm
  • Seata high availability deployment

New seATA data table:

· seata/seata (github.com)

After downloading, create a new library in the database: seata, and import the library build script.

engineered

1. Copy engineering

Copy the project: Spring-Cloud-nacos-consumer and change it to: order-server

Copy the project: Spring-Cloud-nacos-provider and change it to: stock-server

Note: Part of the POM configuration needs to be modified after replication (to the corresponding name) :

<artifactId>order-server</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <name>order-server</name>
    <description>Demo project for Spring Boot</description>
Copy the code

And add to the parent POM:

<modules>.<module>stock-server</module>
    <module>order-server</module>
</modules>
Copy the code

Then re-import the dependency

If exceptions still exist, delete the. Imi file

2. Increase dependency

Add dependencies to the POM of two engineering modules:

<! -- seata -->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
Copy the code

3. Modify the application. Yml configuration

order-server

logging:
  level:
    io:
      seata: debug
seata:
  tx-service-group: my_test_tx_group
Copy the code

stock-server

logging:
  level:
    io:
      seata: debug
seata:
  tx-service-group: my_test_tx_group
Copy the code

4. Initialize the database

Create order library, business table, undo_log table
create database seata_order;
use seata_order;

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;

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 stock, business table, undo_log table
create database seata_stock;
use seata_stock;

DROP TABLE IF EXISTS `stock_tbl`;
CREATE TABLE `stock_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;

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;

Initialize inventory simulation data
INSERT INTO seata_stock.stock_tbl (id, commodity_code, count) VALUES (1.'product-1'.9999999);
INSERT INTO seata_stock.stock_tbl (id, commodity_code, count) VALUES (2.'product-2'.0);
Copy the code

5. Introduce MyBatis Plus

Parent POM introduces dependencies:

<! -- mybatis plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3.4</version>
</dependency>

<! Mysql driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
Copy the code

Two projects introduce dependencies respectively:

<! -- mybatis plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
Copy the code

6. Modify the order-server service

New Partial class

@Data
@TableName("order_tbl")
@Accessors(chain = true)
@Builder
public class Order implements Serializable {

   private static final longserialVersionUID= 1L;

    @JsonFormat(shape = JsonFormat.Shape.STRING)
    @TableId(value="id" ,type = IdType.AUTO)
/ * * * /
@TableField("id")
    private Integer id;

/ * * * /
@TableField("user_id")
    private String userId;

/ * * * /
@TableField("commodity_code")
    private String commodityCode;

/ * * * /
@TableField("count")
    private Integer count;

/ * * * /
@TableField("money")
    private BigDecimal money;

    @Tolerate
    public Order(a){}}Copy the code
@Mapper
public interface OrderTblMapper extends BaseMapper<Order> {}Copy the code

mapperxml:


      
<! DOCTYPEmapper PUBLIC "- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.winterchen.nacos.mapper.OrderTblMapper">

</mapper>
Copy the code

service:

public interface OrderTblService extends IService<Order> {

    void placeOrder(String userId, String commodityCode, Integer count);

}

Copy the code
@Service
public class OrderTblServiceImpl extends ServiceImpl<OrderTblMapper.Order> implements OrderTblService {

    @Autowired
    private StockFeignClient stockFeignClient;

    /** * place an order: create an order, reduce inventory, which involves two services **@param userId
     * @param commodityCode
     * @param count
     */
    @GlobalTransactional
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void placeOrder(String userId, String commodityCode, Integer count) {
        BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
        Order order = newOrder().setUserId(userId).setCommodityCode(commodityCode).setCount(count).setMoney(orderMoney); baseMapper.insert(order); stockFeignClient.deduct(commodityCode, count); }}Copy the code

StockFeignClient

@FeignClient(name = "stock-server")
public interface StockFeignClient {

    @PostMapping("/api/stock/deduct")
    Boolean deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);

}
Copy the code
@ Api (tags = "order Api")
@RestController
@RequestMapping("/api/order")
public class OrderTblController {

    @Autowired
    private OrderTblService orderTblService;

    /** * : insert order table, subtract inventory, simulate rollback **@return* /
    @PostMapping("/placeOrder/commit")
    public Boolean placeOrderCommit(a) {

        orderTblService.placeOrder("1"."product-1".1);
        return true;

    }

    /** * : insert order table, subtract inventory, simulate rollback **@return* /
    @PostMapping("/placeOrder/rollback")
    public Boolean placeOrderRollback(a) {
        // Product -2 ()
        orderTblService.placeOrder("1"."product-2".1);
        return true;
    }

    @PostMapping("/placeOrder")
    public Boolean placeOrder(String userId, String commodityCode, Integer count) {
        orderTblService.placeOrder(userId, commodityCode, count);
        return true; }}Copy the code

7. Stock -server service modification

entity:

@Data
@TableName("stock_tbl")
@Accessors(chain = true)
@Builder
public class Stock implements Serializable {
	
	private static final long serialVersionUID = 1L;
		
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    @TableId(value="id" ,type = IdType.AUTO)
    / * * * /
    @TableField("id")
    private Integer id;

    / * * * /
    @TableField("commodity_code")
    private String commodityCode;

    / * * * /
    @TableField("count")
    private Integer count;

    @Tolerate
    public Stock(a){}}Copy the code

mapper:

@Mapper
public interface StockTblMapper extends BaseMapper<Stock> {}Copy the code

mapperxml:

<? xml version="1.0" encoding="UTF-8"? > <! DOCTYPE mapper PUBLIC"- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.winterchen.nacos.mapper.StockTblMapper">

</mapper>
Copy the code

service:

public interface StockTblService extends IService<Stock> {

    void deduct(String commodityCode, int count);

}
Copy the code
@Service
public class StockTblServiceImpl extends ServiceImpl<StockTblMapper.Stock> implements StockTblService {

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deduct(String commodityCode, int count) {
        if (commodityCode.equals("product-2")) {
            throw new RuntimeException("Exception: Simulated business exception: Stock Branch Exception");
        }

        QueryWrapper<Stock> wrapper = new QueryWrapper<>();
        wrapper.setEntity(newStock().setCommodityCode(commodityCode)); Stock stock = baseMapper.selectOne(wrapper); stock.setCount(stock.getCount() - count); baseMapper.updateById(stock); }}Copy the code

Controller:

@ Api (tags = "inventory Api")
@RestController
@RequestMapping("/api/stock")
public class StockTblController {

    @Autowired
    private StockTblService stockTblService;

    /** ** reduce inventory **@paramCommodityCode *@paramCount the number of *@return* /
    @PostMapping(path = "/deduct")
    public Boolean deduct(String commodityCode, Integer count) {
        stockTblService.deduct(commodityCode, count);
        return true; }}Copy the code

8. Modify gateway:

Modify GatewayConfiguration

InitCustomizedApis Added initialization for order-server and stock-server

ApiDefinition api3 = new ApiDefinition("order")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{

                    add(new ApiPathPredicateItem().setPattern("/order/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
        ApiDefinition api4 = new ApiDefinition("stock")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem().setPattern("/stock/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});
  definitions.add(api3);
  definitions.add(api4);
Copy the code

InitGatewayRules Adds a rule

rules.add(new GatewayFlowRule("order")
                .setCount(10)
                .setIntervalSec(1)); rules.add(new GatewayFlowRule("order")
                .setCount(2)
                .setIntervalSec(2)
                .setBurst(2)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
                )
        );

rules.add(new GatewayFlowRule("stock")
                .setCount(10)
                .setIntervalSec(1)
                .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
                .setMaxQueueingTimeoutMs(600)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
                        .setFieldName("X-Sentinel-Flag"))); rules.add(new GatewayFlowRule("stock")
                .setCount(1)
                .setIntervalSec(1)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                        .setFieldName("pa"))); rules.add(new GatewayFlowRule("stock")
                .setCount(2)
                .setIntervalSec(30)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                        .setFieldName("type")
                        .setPattern("warn")
                        .setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS)
                )
        );

        rules.add(new GatewayFlowRule("stock")
                .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
                .setCount(5)
                .setIntervalSec(1)
                .setParamItem(new GatewayParamFlowItem()
                        .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
                        .setFieldName("pn")));Copy the code

Appilcation. Yml Added route configuration for order-server and stock-server:


spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0. 01.: 8848
    gateway:
      discovery:
        locator:
          enabled: false
          lowerCaseServiceId: true
      routes:
        - id: provider
          uri: lb://winter-nacos-provider
          predicates:
            - Path=/provider/**
          filters:
            - StripPrefix=1 # StripPrefix = 1 means capture path number is 1, such as front-end to request/test/good / 1 / view, after the success of the match, routed to the backend request path will become http://localhost:8888/good/1/view
        - id: consumer
          uri: lb://winter-nacos-consumer
          predicates:
            - Path=/consumer/**
          filters:
            - StripPrefix=1
        - id: auth
          uri: lb://auth
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
        - id: order-server   ------- Added routing rules for order-server
          uri: lb://order-server
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1
        - id: stock-server  --------- The routing rule of stock-server is added
          uri: lb://stock-server
          predicates:
            - Path=/stock/**
          filters:
            - StripPrefix=1
Copy the code

Testing:

Start the corresponding service first:

  • spring-cloud-gateway
  • spring-cloud-auth
  • order-server
  • stock-server

Then open up swagger to test: Consumer Services

Test submission:

Order created successfully:

Inventory deduction successful:

Test rollback:

At this point, the database is normally rolled back.

conclusion

Above is all there is in this tutorial, seata distributed transaction is a very useful framework, provides a simpler API, developers seata default is used AT mode of transaction, of course, can be combined with their own business to choose more appropriate mode of distributed transactions, specific configuration can be reference for the official documentation.

WinterChenS/ Spring-Cloud-Hoxton-Study: Spring Cloud Hoxton Release Study (github.com)

References:

What is the Seata

Seata (Fescar) Distributed transaction integration Spring Cloud