基于 Fescar 解決微服務架構下數據一致性的實踐

Fescar 是一款開源的分布式事務解決方案,提供高性能和簡單易用的分布式事務服務。

隨著業(yè)務的快速發(fā)展,應用單體架構暴露出代碼可維護性差,容錯率低,測試難度大,敏捷交付能力差等諸多問題,微服務應運而生。微服務的誕生一方面解決了上述問題,但是另一方面卻引入新的問題,其中主要問題之一就是如何保證微服務間的業(yè)務數據一致性。

本文將通過一個簡單的微服務架構的例子,說明業(yè)務如何step by step的使用 Fescar、Dubbo 和 Nacos 來保證業(yè)務數據的一致性。本文所述的例子中 Dubbo 和 Fescar 注冊配置服務中心均使用 Nacos。Fescar 0.2.1+ 開始支持 Nacos 注冊配置服務中心。

業(yè)務案例

用戶采購商品業(yè)務,整個業(yè)務包含3個微服務:

  • 庫存服務: 扣減給定商品的庫存數量。
  • 訂單服務: 根據采購請求生成訂單。
  • 賬戶服務: 用戶賬戶金額扣減。

業(yè)務結構圖

StorageService

public interface StorageService {
    /**
     * deduct storage count
     */
    void deduct(String commodityCode, int count);
}

OrderService

public interface OrderService {
    /**
     * create order
     */
    Order create(String userId, String commodityCode, int orderCount);
}

AccountService

public interface AccountService {
    /**
     * debit balance of user's account
     */
    void debit(String userId, int money);
}

說明: 以上三個微服務獨立部署。

Fescar、Dubbo和Nacos 集成

Step 1 初始化 MySQL 數據庫(需要InnoDB 存儲引擎)

resources/jdbc.properties 修改StorageService、OrderService、AccountService 對應的連接信息。

jdbc.account.url=jdbc:mysql://xxxx/xxxx
jdbc.account.username=xxxx
jdbc.account.password=xxxx
jdbc.account.driver=com.mysql.jdbc.Driver
# storage db config
jdbc.storage.url=jdbc:mysql://xxxx/xxxx
jdbc.storage.username=xxxx
jdbc.storage.password=xxxx
jdbc.storage.driver=com.mysql.jdbc.Driver
# order db config
jdbc.order.url=jdbc:mysql://xxxx/xxxx
jdbc.order.username=xxxx
jdbc.order.password=xxxx
jdbc.order.driver=com.mysql.jdbc.Driver

Step 2 創(chuàng)建 undo_log(用于Fescar AT 模式)表和相關業(yè)務表

相關建表腳本可在 resources/sql/ 下獲取,在相應數據庫中執(zhí)行 dubbo_biz.sql 中的業(yè)務建表腳本,在每個數據庫執(zhí)行 undo_log.sql 建表腳本。

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) 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`),
  KEY `idx_unionkey` (`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;

說明: 需要保證每個物理庫都包含 undo_log 表,此處可使用一個物理庫來表示上述三個微服務對應的獨立邏輯庫。

Step 3 引入 Fescar、Dubbo 和 Nacos 相關 POM 依賴

      <properties>
          <fescar.version>0.2.1</fescar.version>
          <dubbo.alibaba.version>2.6.5</dubbo.alibaba.version>
          <dubbo.registry.nacos.version>0.0.2</dubbo.registry.nacos.version>
       </properties>
        
       <dependency>
           <groupId>com.alibaba.fescar</groupId>
           <artifactId>fescar-spring</artifactId>
           <version>${fescar.version}</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba.fescar</groupId>
           <artifactId>fescar-dubbo-alibaba</artifactId>
           <version>${fescar.version}</version>
           <exclusions>
               <exclusion>
                   <artifactId>dubbo</artifactId>
                   <groupId>org.apache.dubbo</groupId>
               </exclusion>
           </exclusions>
       </dependency>
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>dubbo</artifactId>
           <version>${dubbo.alibaba.version}</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>dubbo-registry-nacos</artifactId>
           <version>${dubbo.registry.nacos.version}</version>
       </dependency>

說明: 由于當前 apache-dubbo 與 dubbo-registry-nacos jar存在兼容性問題,需要排除 fescar-dubbo 中的 apache.dubbo 依賴并手動引入 alibaba-dubbo,后續(xù) apache-dubbo(2.7.1+) 將兼容 dubbo-registry-nacos。在Fescar 中 fescar-dubbo jar 支持 apache.dubbo,fescar-dubbo-alibaba jar 支持 alibaba-dubbo。

Step 4 微服務 Provider Spring配置

分別在三個微服務Spring配置文件(dubbo-account-service.xml
dubbo-order-service
dubbo-storage-service.xml
)進行如下配置:

  • 配置 Fescar 代理數據源
    <bean id="accountDataSourceProxy" class="com.alibaba.fescar.rm.datasource.DataSourceProxy">
        <constructor-arg ref="accountDataSource"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="accountDataSourceProxy"/>
    </bean>

此處需要使用 com.alibaba.fescar.rm.datasource.DataSourceProxy 包裝 Druid 數據源作為直接業(yè)務數據源,DataSourceProxy 用于業(yè)務 sql 的攔截解析并與 TC 交互協調事務操作狀態(tài)。

  • 配置 Dubbo 注冊中心
    <dubbo:registry address="nacos://${nacos-server-ip}:8848"/>
  • 配置 Fescar GlobalTransactionScanner
    <bean class="com.alibaba.fescar.spring.annotation.GlobalTransactionScanner">
        <constructor-arg value="dubbo-demo-account-service"/>
        <constructor-arg value="my_test_tx_group"/>
    </bean>

此處構造方法的第一個參數為業(yè)務自定義 applicationId,若在單機部署多微服務需要保證 applicationId 唯一。
構造方法的第二個參數為 Fescar 事務服務邏輯分組,此分組通過配置中心配置項 service.vgroup_mapping.my_test_tx_group 映射到相應的 Fescar-Server 集群名稱,然后再根據集群名稱.grouplist 獲取到可用服務列表。

Step 5 事務發(fā)起方配置

dubbo-business.xml 配置以下配置:

  • 配置 Dubbo 注冊中心

同 Step 4

  • 配置 Fescar GlobalTransactionScanner

同 Step 4

  • 在事務發(fā)起方 service 方法上添加 @GlobalTransactional 注解
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")

timeoutMills 為事務的總體超時時間默認60s,name 為事務方法簽名的別名,默認為空。注解內參數均可省略。

Step 6 啟動 Nacos-Server

  • 下載 Nacos-Server 最新 release 包并解壓

  • 運行 Nacos-server

Linux/Unix/Mac

sh startup.sh -m standalone

Windows

cmd startup.cmd -m standalone

訪問 Nacos 控制臺:http://localhost:8848/nacos/index.html#/configurationManagement?dataId=&group=&appName=&namespace=

若訪問成功說明 Nacos-Server 服務運行成功(默認賬號/密碼: nacos/nacos)

Step 7 啟動 Fescar-Server

  • 下載 Fescar-Server 最新 release 包并解壓

  • 初始化 Fescar 配置

進入到 Fescar-Server 解壓目錄 conf 文件夾下,確認 nacos-config.txt 的配置值(一般不需要修改),確認完成后運行 nacos-config.sh 腳本初始化配置。

sh nacos-config.sh $Nacos-Server-IP

eg:

sh nacos-config.sh localhost 

腳本執(zhí)行最后輸出 "init nacos config finished, please start fescar-server." 說明推送配置成功。若想進一步確認可登陸Nacos 控制臺 配置列表 篩選 Group=FESCAR_GROUP 的配置項。

  • 修改 Fescar-server 服務注冊方式為 nacos

進入到 Fescar-Server 解壓目錄 conf 文件夾下 registry.conf 修改 type="nacos" 并配置 Nacos 的相關屬性。

registry {
  # file nacos
  type = "nacos"

  nacos {
    serverAddr = "localhost"
    namespace = "public"
    cluster = "default"
  }
  file {
    name = "file.conf"
  }
}

type: 可配置為 nacos 和 file,配置為 file 時無服務注冊功能
nacos.serverAddr: Nacos-Sever 服務地址(不含端口號)
nacos.namespace: Nacos 注冊和配置隔離 namespace
nacos.cluster: 注冊服務的集群名稱
file.name: type = "file" classpath 下配置文件名

  • 運行 Fescar-server

Linux/Unix/Mac

sh fescar-server.sh $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA $IP(此參數可選)

Windows

cmd fescar-server.bat $LISTEN_PORT $PATH_FOR_PERSISTENT_DATA $IP(此參數可選)
$LISTEN_PORT: Fescar-Server 服務端口 .            
$PATH_FOR_PERSISTENT_DATA: 事務操作記錄文件存儲路徑(已存在路徑) .    
$IP(可選參數): 用于多 IP 環(huán)境下指定 Fescar-Server 注冊服務的IP 

eg:
sh fescar-server.sh 8091 /home/admin/fescar/data/

運行成功后可在 Nacos 控制臺看到 服務名 =serverAddr 服務注冊列表:

Step 8 啟動微服務并測試

啟動完成可在 Nacos 控制臺服務列表 看到啟動完成的三個 provider

注意: 在標注 @GlobalTransactional 注解方法內部顯示的拋出異常才會進行事務的回滾。整個 Dubbo 服務調用鏈路只需要在事務最開始發(fā)起方的 service 方法標注注解即可。

相關鏈接:

本文 sample 地址: https://github.com/fescar-group/fescar-samples/tree/master/nacos
Fescar: https://github.com/alibaba/fescar
Dubbo: https://github.com/apache/incubator-dubbo
Nacos: https://github.com/alibaba/nacos

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容