This article is part 7 of SpringCloud Alibaba microservices Practice series, which focuses on using Seata to solve distributed transaction problems. A series of articles, welcome to continue to pay attention.

The scene that

Order service order-service provides an interface for creating an order. The service logic for creating an order is as follows:

Call the local orderService to save the order operation, then call the remote Accout-Service through Feign to deduct the account balance, and finally call the remote Product-Service through Feign to deduct the inventory operation.

The key logic code is as follows:

  • OrderControllerProvides an interface for creating orders externally
@PostMapping("/order/create")
public ResultData<OrderDTO> create(@RequestBody OrderDTO orderDTO){
    log.info("create order:{}",orderDTO);
    orderDTO.setOrderNo(UUID.randomUUID().toString());
    orderDTO.setAmount(orderDTO.getPrice().multiply(new BigDecimal(orderDTO.getCount())));
    orderService.createOrder(orderDTO);
    return ResultData.success("create order success");
}Copy the code

  • OrderServiceImplHandles the business logic for creating orders
@Transactional(rollbackFor = RuntimeException.class) @Override public void createOrder(OrderDTO orderDTO) { Order order = new Order(); BeanUtils.copyProperties(orderDTO,order); // local store Order this.saveOrder(Order); // productFeign.deduct(orderTo.getProductCode (),order.getCount()); Accountfeign.reduce (orderTo.getAccountCode (), orderTo.getAmount ())); } @Transactional(rollbackFor = RuntimeException.class) void saveOrder(Order order) { orderMapper.insert(order); }Copy the code

Local saves, then invokes two remote services to do the deduction.

  • AccountServiceImplDeduct the account balance
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void reduceAccount(String accountCode, BigDecimal amount) {
    Account account = accountMapper.selectByCode(accountCode);
    if(null == account){
        throw new RuntimeException("can't reduce amount,account is null");
    }
    BigDecimal subAmount = account.getAmount().subtract(amount);
    if(subAmount.compareTo(BigDecimal.ZERO) < 0){
        throw new RuntimeException("can't reduce amount,account'amount is less than reduce amount");
    }
    account.setAmount(subAmount);
    accountMapper.updateById(account);
}Copy the code

Do some simple checks. No deductions are allowed when the account balance is insufficient.

  • ProductServiceImplDeduct product inventory
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void deduct(String productCode, Integer deductCount) {
    Product product = productMapper.selectByCode(productCode);
    if(null == product){
        throw new RuntimeException("can't deduct product,product is null");
    }
    int surplus = product.getCount() - deductCount;
    if(surplus < 0){
        throw new RuntimeException("can't deduct product,product's count is less than deduct count");
    }
    product.setCount(surplus);
    productMapper.updateById(product);
}Copy the code

Do some simple checks and do not allow deductions when the product is out of stock.

Order-service, product-service, and account-service belong to different services. When one of them throws an exception and cannot be submitted, distributed transactions will result. For example, when account-service is called by Feign to perform account balance deduction, If the account-service verifies the account balance is insufficient, an exception is thrown, but the saving operation of the order-service will not be rolled back. Or the first two steps are successful, but the product-service check does not pass the previous operation and will not be rolled back, which leads to data inconsistency, that is, distributed transaction problem!

Seata solutions

Using Seata as a distributed transaction solution in Springcloud Alibaba, you can visit the Seata website to learn more. This time we will use Seata’s file configuration to solve the above problem, and then modify it later.

Download and install Seata Server.

  • Download Seata Server from the Release page
  • After the download is complete, start the Server.

    Under the Linux/Mac

    $ sh ./bin/seata-server.sh

    Under the Windows

    binseata-server.bat

Introduce the SEATA component

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>Copy the code

Add the SEATA configuration to the configuration file

spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: ${spring.application.name}-seataCopy the code

The Seata Client configuration is modified

  • The Seata Server configuration directoryregistry.conf,file.confCopy two files to the Resources folder in the microservice

  • Modify the copied registry
registry{
  type = "file"

  file {
    name = "file.conf"
  }
}
config{
  type = "file"

  file {
    name = "file.conf"
  }
}Copy the code
  • Modify the file. The conf



    The main modifications are as follows:

    service.vgroup_mapping.Modify the following values to configuration filesspring.cloud.alibaba.seata.tx-service-groupThe properties of the

    service.default.grouplist=Change the value to Seata Server IP address: port

    support.spring.datasource.autoproxyChange the value of true to enabledatasourceAutomatic proxy

Generate undo_log table

Run the following statement in the microservice business library to generate the undo_log table

This script must be initialized in your current business database for AT-mode XID records. Drop table '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;Copy the code

Enable global transactions

Add the @GlobalTransactional annotation to the distributed transaction method entry. You only need to add this annotation to the createOrder method!

@GlobalTransactional(name = "TX_ORDER_CREATE") @Override public void createOrder(OrderDTO orderDTO) { Order order = new Order(); BeanUtils.copyProperties(orderDTO,order); // local store Order this.saveOrder(Order); log.info("ORDER XID is: {}", RootContext.getXID()); Accountfeign.reduce (orderTo.getAccountCode (), orderTo.getAmount ())); // productFein.deduct (orderTo.getProductCode (), orderTo.getCount ()); }Copy the code

You can use rootContext.getxId () in your code to get the global XID

Start the service

After the service starts normally, you can see the registration information on the Seata Server console

The interface test

After the transformation, test the interface. If other services throw exceptions, the following error logs will be displayed. Then combine the database data to check whether the rollback is normal

Debug can be used to detect the execution processundo_logTables are constantly inserting data and then being deleted after execution.

Through the above steps, we implemented distributed transactions using Seata to ensure data consistency. Finally, WE say Seata is delicious, you want to feel it.

So far this period of “SpringCloud Alibaba micro-service actual combat seven – distributed transactions” is the end of the chapter, we will see you next time!

Let me ask for a wave of attention before goodbye, O(∩_∩)O ha ha ~!

series

  • SpringCloud Alibaba Micro Service Practice 6 – Configuration isolation
  • SpringCloud Alibaba micro-service practice five – current limit fuse
  • SpringCloud Alibaba Micro service practice 4 – version management
  • SpringCloud Alibaba micro-service practice 3 – service invocation
  • SpringCloud Alibaba micro-service practice ii – service registration
  • SpringCloud Alibaba micro-service practice I – basic environment preparation

Please scan the code to follow the wechat public account or personal blog