成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一站式分布式事務 Seata 方案

開發
本文主要對分布式事務Seata的使用、原理做了介紹,同時在選擇方案選擇上給出一些建議。

引言

上一篇《如何選擇分布式事務解決方案?》 綜合比較了各種分布式事務方案的優缺點,并在技術選型上給出合理化的建議。考慮到企業應用的普遍性和適用性,今天重點聊聊分布式Seata方案實踐和原理。

Seata 是什么?

Seata 是一款開源的分布式事務解決方案,致力于提供高性能和簡單易用的分布式事務服務。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務模式,為用戶打造一站式的分布式解決方案。默認AT模式。

英文官網:https://seata.io/zh-cn/

中文官網:https://seata.io/zh-cn/

看官網這輕松而有趣的解釋,心中默念:

為探究這個問題,我們嘗試從問題中來,到問題中去。試想:

  • 不使用分布式事務會產生怎樣的結果?
  • 為何選擇Seata方案?
  • 這種方案如何實現提交和回滾保證數據一致性的?

帶著這些問題,根據實際應用場景逐步探究。。。

業務場景

在電商交易系統中,最核心的業務場景:下訂單 --> 減庫存 --> 調支付,要求具備數據強一致性。

基本模型如下:

我們要求該場景應滿足以下幾個條件:

  • 訂單狀態正常(0-異常,1-正常)
  • 庫存不能出現負數或者下單了沒有扣庫存
  • 保證金額正常支付,不能出現扣款多或者扣款少的情況

業務實現

下面我們給出核心代碼描述業務過程。

訂單服務:

public interface OrderService {
    /**
    * 創建訂單
    */
   void create(Order order);
}

倉儲服務:

public interface StorageService {
    /**
     * 扣減庫存
     */
    void deduct(Long productId, Integer count);
}

帳戶服務:

public interface AccountService {

  /**
   * 扣減賬戶余額
   *
   * @param userId 用戶id
   * @param money  金額
   */
  void debit(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}

這里只給出核心業務邏輯說明問題。在訂單業務作為核心邏輯,遠程調用扣庫存和支付。

public class OrderServiceImpl implements OrderService
{
    @Resource
    private OrderDao orderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;

    /**
     * 創建訂單->調用庫存服務扣減庫存->調用賬戶服務扣減賬戶余額->修改訂單狀態
     * 簡單說:下訂單->扣庫存->減余額->改狀態
     */
    @Override
    //這里先注釋掉,用于比較實用分布式事務前后的效果
    //@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
    public void create(Order order)
    {
        //創建訂單
        orderDao.create(order);

        //2 扣減庫存
        storageService.deduct(order.getProductId(),order.getCount());

        //3 扣減賬戶金額
        accountService.debit(order.getUserId(),order.getMoney());

        //4 修改訂單狀態,從零到1,1代表已經完成
        orderDao.update(order.getUserId(),0);

    }
}

未使用分布式事務前的場景

初始狀態:

-- 庫存商品:100
mysql> select * from t_storage;
+----+------------+-------+------+---------+
| id | product_id | total | used | residue |
+----+------------+-------+------+---------+
|  1 |          1 |   100 |    0 |     100 |
+----+------------+-------+------+---------+
1 row in set (0.00 sec)
 
  
--賬戶余額:1000
mysql> select * from t_account;
+----+---------+-------+------+---------+
| id | user_id | total | used | residue |
+----+---------+-------+------+---------+
|  1 |       1 |  1000 |    0 |    1000 |
+----+---------+-------+------+---------+
1 row in set (0.00 sec)

  
-- 訂單為空 
mysql> select * from t_order;
Empty set (0.00 sec)

下面模擬一個賬戶扣減異常(因為OpenFeign的默認分別是連接超時時間10秒.這處設置20秒就是為了挑事兒),然后分別啟動訂單、庫存、賬戶服務。

/**
   * 扣減賬戶余額
   */
  @Override
  public void debit(Long userId, BigDecimal money) {
      LOGGER.info("------->account-service中扣減賬戶余額開始");
      //模擬超時異常,全局事務回滾
      //暫停幾秒鐘線程
      try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
      accountDao.debit(userId,money);
      LOGGER.info("------->account-service中扣減賬戶余額結束");
  }

模擬下訂單過程:

http://localhost:2001/order/create?userId=1&productId=1&count=10&mnotallow=100

頁面異常因為賬戶服務中扣減過程發生異常。我們再來觀察數據庫中的變化情況:

-- 庫存商品:100
mysql> select * from t_storage;
+----+------------+-------+------+---------+
| id | product_id | total | used | residue |
+----+------------+-------+------+---------+
|  1 |          1 |   100 |   10 |      90 |
+----+------------+-------+------+---------+
1 row in set (0.00 sec)
 
  
--賬戶余額:1000
mysql> select * from t_account;
+----+---------+-------+------+---------+
| id | user_id | total | used | residue |
+----+---------+-------+------+---------+
|  1 |       1 |  1000 |    0 |    1000 |
+----+---------+-------+------+---------+
1 row in set (0.00 sec)


  
-- 訂單為空 
mysql> select * from t_order;
+----+---------+------------+-------+-------+--------+
| id | user_id | product_id | count | money | status |
+----+---------+------------+-------+-------+--------+
|  3 |       1 |          1 |    10 |   100 |      0 |
+----+---------+------------+-------+-------+--------+
1 row in set (0.00 sec)

觀察分析,訂單狀態:0-異常。庫存較少,但是余額沒有扣減。商家容易哭死。這就使我們不得不做事務的控制。以達到數據一致性。

Seata方案引入

對此,我們使用分布式事務Seata的解決方案解決此問題。

圖片來源于官網

為方便測試使用,這里準備了Seata的一套環境。并給出相應業務測試用例SQL。

使用非常簡單。只需要在核心業務方法上加一個注解@GlobalTransactional即可。

@Override
    @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
    public void create(Order order)
    {
        //創建訂單
        orderDao.create(order);

        //2 扣減庫存
        storageService.deduct(order.getProductId(),order.getCount());

        //3 扣減賬戶金額
        accountService.debit(order.getUserId(),order.getMoney());

        //4 修改訂單狀態,從零到1,1代表已經完成
        orderDao.update(order.getUserId(),0);

    }

恢復數據初始狀態,再次啟動和重復上述測試步驟。觀察在發生異常的情況下,數據庫還是初始的狀態(為出現訂單異常和賬戶余額變動的問題)。顯然:這個注解幫助我們:在分布式環境下,當有異常發生時進行全局回滾,以維持數據的一致狀態。我們把這種全局意義上控制的事務成為全局事務。

Seata執行流程及原理

@GlobalTransactional用事實告訴我們:極簡是一種美!

是不是很好奇?

下面究其背后的原理做深層次的探究。

(1) 全局事務中的角色

  • TM 事務發起方,是業務方法上帶有@GlobalTransactional注解的服務,如:本案例中訂單服務。
  • TC 事務協調者,可以理解為一個隱形的中間人,負責管理事務。
  • TR 事務參與者:本案例當中:訂單、庫存、賬戶都是還是事務參與者

(2) 全局成功提交流程

(3) 全局失敗回滾流程

下面我們用代碼驗證此過程:

  • 啟動nacos;
  • 啟動seata;
  • debug啟動訂單、庫存、賬戶服務,斷點跟蹤

查看數據庫變化情況(取關鍵字段信息):

-- 全局事務
mysql> select xid,transaction_id,application_id,transaction_service_group,transaction_name from global_table ;
+-------------------------------+----------------+---------------------+---------------------------+------------------+
| xid                           | transaction_id | application_id      | transaction_service_group | transaction_name |
+-------------------------------+----------------+---------------------+---------------------------+------------------+
| 192.168.0.101:8091:2155601919 |     2155601919 | seata-order-service | fsp_tx_group              | fsp-create-order |
+-------------------------------+----------------+---------------------+---------------------------+------------------+
1 row in set (0.01 sec)

  
-- 分支事務
mysql> select  xid,transaction_id,branch_id,client_id,branch_type from  branch_table;
+-------------------------------+----------------+------------+-------------------------------------------+-------------+
| xid                           | transaction_id | branch_id  | client_id                                 | branch_type |
+-------------------------------+----------------+------------+-------------------------------------------+-------------+
| 192.168.0.101:8091:2155601919 |     2155601919 | 2155601922 | seata-order-service:192.168.0.101:51952   | AT          |
| 192.168.0.101:8091:2155601919 |     2155601919 | 2155601927 | seata-storage-service:192.168.0.101:52459 | AT          |
| 192.168.0.101:8091:2155601919 |     2155601919 | 2155601930 | seata-account-service:192.168.0.101:52498 | AT          |
+-------------------------------+----------------+------------+-------------------------------------------+-------------+
3 rows in set (0.00 sec)

  
-- 鎖
mysql> select xid,transaction_id,branch_id,table_name,resource_id  from lock_table;
+-------------------------------+----------------+------------+------------+-----------------------------------------------+
| xid                           | transaction_id | branch_id  | table_name | resource_id                                   |
+-------------------------------+----------------+------------+------------+-----------------------------------------------+
| 192.168.0.101:8091:2155601919 | 2155601919     | 2155601930 | t_account  | jdbc:mysql://114.116.10.56:3306/seata_account |
| 192.168.0.101:8091:2155601919 | 2155601919     | 2155601922 | t_order    | jdbc:mysql://114.116.10.56:3306/seata_order   |
| 192.168.0.101:8091:2155601919 | 2155601919     | 2155601927 | t_storage  | jdbc:mysql://114.116.10.56:3306/seata_storage |
+-------------------------------+----------------+------------+------------+-----------------------------------------------+
3 rows in set (0.00 sec)

注意:全局事務xid 和分支事務branch_id 之間的對應關系,比對上圖中成功提交流程.

我們觀察業務庫中的undo_log日志情況:(重點關注rollback_info字段信息)

格式化一下rollback_info信息:

當然,讀者感興趣可以查看下seata_account庫和 seata_storage庫中undo_log的情況,與此類似。

所以我們可以將Seata的執行原理歸納為:

(1) 在一階段,Seata 會攔截“業務 SQL”,

  • 解析 SQL 語義,找到“業務 SQL”要更新的業務數據,在業務數據被更新前,將其保存成“before image”;
  • 執行“業務 SQL”更新業務數據;
  • 在業務數據更新之后,其保存成“after image”,最后生成行鎖。

以上操作全部在一個數據庫事務內完成,這樣保證了一階段操作的原子性。

圖片來源于學習筆記

(2) 二階段提交:

天空不留下鳥兒的痕跡,但它已經飛過),完成數據清理即可。

圖片來源于學習筆記

(3) 二階段回滾:

二階段如果是回滾的話,Seata 就需要回滾一階段已經執行的“業務 SQL”,還原業務數據。回滾方式便是用“before image”還原業務數據。

  • 還原前要首先要校驗臟寫;
  • 對比“數據庫當前業務數據”和 “after image”
  • 如果兩份數據完全一致就說明沒有臟寫,可以還原業務數據,如果不一致就說明有臟寫,出現臟寫就需要轉人工處理。

圖片來源于學習筆記

總結

本文主要對分布式事務Seata的使用、原理做了介紹,同時在選擇方案選擇上給出如下建議:

  • 非必要不引入分布式事務的處理;
  • 分布式事務在分布式環境下使用,單體應用不必考慮;
  • 一般多用在遠程調用三方外部平臺之間,內部系統服務之間建議使用Spring事務
  • Seata分布式事務方案默認AT模式,代碼無入侵,使用簡單,在數據一致性要求比較高的系統中,是很好的分布式事務解決方案。常用于電商支付、金融轉賬類業務。
  • Seata配置繁瑣,引入子系統會產生很多其它問題。應根據實際場景合理選擇。
責任編輯:趙寧寧 來源: 碼易有道
相關推薦

2022-06-27 08:21:05

Seata分布式事務微服務

2023-11-06 13:15:32

分布式事務Seata

2022-06-21 08:27:22

Seata分布式事務

2017-05-04 21:30:32

前端異常監控捕獲方案

2022-03-24 07:51:27

seata分布式事務Java

2023-01-06 09:19:12

Seata分布式事務

2025-04-28 00:44:04

2010-05-06 16:02:26

2023-02-04 18:24:10

SeataJava業務

2022-07-10 20:24:48

Seata分布式事務

2025-05-07 00:10:00

分布式事務TCC模式

2013-06-14 09:30:52

2013-10-20 13:30:07

華為一站式BYOD敏捷辦公

2021-04-23 08:15:51

Seata XA AT

2025-04-30 10:44:02

2024-10-09 14:14:07

2009-07-30 21:16:29

布線服務電纜架設

2022-09-16 11:27:46

建設微服務

2009-10-23 09:42:24

2015-04-19 16:36:10

騰訊云
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品一区二区三区在线观看国产 | 一级欧美黄色片 | 国产免费一区二区三区 | 久久久久久久av麻豆果冻 | 精品久久久久久中文字幕 | 欧美日韩成人在线 | 99精品国产一区二区青青牛奶 | 激情网五月天 | 99精品欧美一区二区蜜桃免费 | 在线观看日韩av | 国产精品久久久久久av公交车 | 在线免费毛片 | 久久久一区二区三区 | 一区二区三区在线免费观看 | 天天操夜夜操免费视频 | 精品九九在线 | 超碰在线亚洲 | 色免费看| 国产在线精品一区二区三区 | 一区二区视频在线观看 | 亚洲国产精品成人 | 亚洲欧美日本在线 | 日韩久久久久 | 天堂av中文在线 | 亚洲精品欧美 | 日韩一区二区三区四区五区六区 | 黄视频免费观看 | 91毛片在线看 | 午夜爱爱毛片xxxx视频免费看 | 中文字幕91 | 草比网站 | 日韩中文字幕网 | re久久| 国产精品亚洲精品 | 永久网站 | 五月婷婷亚洲 | 国产一级电影在线 | 91大神在线看 | 日韩av在线免费 | 国产精品久久久久久52avav | 欧美日韩免费一区二区三区 |