SpringCloud Alibaba微服務實戰七 - 分佈式事務

導讀:本篇做爲SpringCloud Alibaba微服務實戰系列的第七篇,主要內容是使用Seata解決分佈式事務問題。系列文章,歡迎持續關注。html

場景說明

訂單服務order-service須要對外提供建立訂單的接口,建立訂單的業務邏輯以下:java

先調用本地的orderService保存訂單操做,而後經過feign調用遠程的accout-service進行帳戶餘額扣減,最後再經過feign調用遠程的product-service進行庫存扣減操做。git

關鍵的邏輯代碼以下:github

  • OrderController對外提供建立訂單的接口
@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");
}
  • OrderServiceImpl負責處理建立訂單的業務邏輯
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void createOrder(OrderDTO orderDTO) {
    Order order = new Order();
    BeanUtils.copyProperties(orderDTO,order);
    //本地存儲Order
    this.saveOrder(order);
    //庫存扣減
    productFeign.deduct(orderDTO.getProductCode(),order.getCount());
    //帳戶餘額扣減
    accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount());
}
@Transactional(rollbackFor = RuntimeException.class)
void saveOrder(Order order) {
    orderMapper.insert(order);
}

本地先保存,而後調用兩個遠程服務進行扣減操做。spring

  • AccountServiceImpl扣減帳戶餘額
@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);
}

作些簡單的校驗,當帳戶餘額不足的時候不容許扣減操做。sql

  • ProductServiceImpl扣減產品庫存
@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);
}

作些簡單的校驗,當產品庫存不足時不容許扣減操做。數據庫

order-serviceproduct-serviceaccount-service分屬不一樣的服務,當其中一個服務拋出異常沒法提交時就會致使分佈式事務,如當使用feign調用account-service執行扣減帳戶餘額時,account-service校驗帳戶餘額不足拋出異常,可是order-service的保存操做不會回滾;或者是前兩步執行成功可是product-service校驗不經過前面的操做也不會回滾,這就致使了數據不一致,也就是分佈式事務問題!segmentfault

Seata解決方案

在Springcloud Alibaba體系中使用Seata做爲分佈式事務解決方案,你們能夠訪問seata官網去了解詳情。
此次咱們先使用Seata的file配置解決上面出現的問題,後面再來對其改造。微信

下載安裝Seata Server。

  • Release 頁面下載Seata Server
  • 下載完成後直接啓動Server端服務。
    在Linux/Mac下
    $ sh ./bin/seata-server.sh

在Windows下
bin\seata-server.batapp

### 引入seata組件

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

在配置文件中增長seata配置

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

Seata Client 配置修改

  • 將Seata Server 配置目錄下的registry.conffile.conf 2個文件拷貝到微服務中的resources文件夾下

image.png

  • 修改拷貝後的registry.conf
registry{
  type = "file"

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

  file {
    name = "file.conf"
  }
}
  • 修改file.conf

image.png
主要修改以下三處:
service.vgroup_mapping.後面的值修改成配置文件spring.cloud.alibaba.seata.tx-service-group的屬性
service.default.grouplist=修改成Seata Server的ip:端口
support.spring.datasource.autoproxy的值修改成true,開啓datasource自動代理

生成undo_log表

在微服務的業務庫下執行以下語句,生成undo_log表

-- 此腳本必須初始化在你當前的業務數據庫中,用於AT 模式XID記錄。與server端無關(注:業務數據庫)
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;

開啓全局事務

在分佈式事務方法入口添加註解@GlobalTransactional,這裏只須要在createOrder方法上添加此註解便可!

@GlobalTransactional(name = "TX_ORDER_CREATE")
@Override
public void createOrder(OrderDTO orderDTO) {
    Order order = new Order();
    BeanUtils.copyProperties(orderDTO,order);
    //本地存儲Order
    this.saveOrder(order);
    log.info("ORDER XID is: {}", RootContext.getXID());
    //帳戶餘額扣減
    accountFeign.reduce(orderDTO.getAccountCode(), orderDTO.getAmount());
    //庫存扣減
    productFeign.deduct(orderDTO.getProductCode(),orderDTO.getCount());
}

在代碼中可使用RootContext.getXID()獲取全局xid

啓動服務

服務正常啓動後在Seata Server控制檯能夠看到註冊信息
image.png

接口測試

改造完成後對接口進行測試,若是其餘服務拋出異常會看到以下錯誤日誌,再結合數據庫數據觀察是否正常回滾
image.png

執行過程當中咱們經過debug能夠發現undo_log表會不斷插入數據,在執行後又會被刪除。
image.png

經過上面幾步咱們使用Seata實現了分佈式事務,保證了數據的一致性,最後說一句Seata真香,大家要不要感覺一下。
至此本期的「SpringCloud Alibaba微服務實戰七 - 分佈式事務」篇也就該結束啦,我們下期有緣再見!
image.png
再見以前讓我在求一波關注吧,O(∩_∩)O哈哈~!
image.png

系列文章
歡迎掃碼關注微信公衆號或 我的博客
相關文章
相關標籤/搜索