隨著業務的快速發展,應用單體架構暴露出程式碼可維護性差、容錯率低、測試難度大和敏捷交付能力差等諸多問題,微服務應運而生。微服務的誕生一方面解決了上述問題,但是另一方面卻引入新的問題,其中主要問題之一就是:如何保證微服務間的業務資料一致性。
本文將通過一個商品採購的業務,來看看在Dubbo的微服務架構下,如何通過Fescar來保障業務的資料一致性。本文所述的例子中,Dubbo 和 Fescar 的註冊配置服務中心均使用 Nacos。Fescar 0.2.1+ 開始支援 Nacos 註冊配置服務中心。
業務描述
使用者採購商品的業務,包含3個微服務:
庫存服務: 扣減給定商品的庫存數量。
訂單服務: 根據採購請求生成訂單。
賬戶服務: 使用者賬戶金額扣減。
業務結構圖如下:
庫存服務(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);
}複製程式碼
說明: 以上三個微服務均是獨立部署。
8個步驟實現資料一致性
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:建立 undo_log(用於Fescar AT 模式)表和相關業務表
相關建表指令碼可在 resources/sql/ 下獲取,在相應資料庫中執行 dubbo_biz.sql 中的業務建表指令碼,在每個資料庫執行 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,後續 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 資料來源作為直接業務資料來源,DataSourceProxy 用於業務 SQL 的攔截解析並與 TC 互動協調事務操作狀態。
配置 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>複製程式碼
此處構造方法的第一個引數為業務自定義 applicationId,若在單機部署多微服務需要保證 applicationId 唯一。
構造方法的第二個引數為 Fescar 事務服務邏輯分組,此分組通過配置中心配置項 service.vgroup_mapping.my_test_tx_group 對映到相應的 Fescar-Server 叢集名稱,然後再根據叢集名稱.grouplist 獲取到可用服務列表。
Step 5:事務發起方配置
在 dubbo-business.xml 配置以下配置:
配置 Dubbo 註冊中心
同 Step 4
配置 Fescar GlobalTransactionScanner
同 Step 4
在事務發起方 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 複製程式碼
指令碼執行最後輸出 "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 環境下指定 Fescar-Server 註冊服務的IP
eg: sh fescar-server.sh 8091 /home/admin/fescar/data/
執行成功後可在 Nacos 控制檯看到 服務名 =serverAddr 服務註冊列表:
Step 8:啟動微服務並測試
修改業務客戶端發現註冊方式為 nacos
同Step 7 中[修改 Fescar-server 服務註冊方式為 nacos] 步驟
啟動 DubboAccountServiceStarter
啟動 DubboOrderServiceStarter
啟動 DubboStorageServiceStarter
啟動完成可在 Nacos 控制檯服務列表 看到啟動完成的三個 provider:
啟動 DubboBusinessTester 進行測試
注意: 在標註 @GlobalTransactional 註解方法內部顯示的丟擲異常才會進行事務的回滾。整個 Dubbo 服務呼叫鏈路只需要在事務最開始發起方的 service 方法標註註解即可。
通過以上8個步驟,我們實現了使用者採購商品的業務中庫存、訂單和賬戶3個獨立微服務之間的資料一致性。
大家如果覺得文章不錯的話,可以點個贊和關注下,以後會有跟多的精品文章分享給大家。
作者福利:
給大家推薦一個架構技術交流群:714827309 ,裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析
,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,相信對於已經工作
和遇到技術瓶頸的碼友,在這個群裡會有你需要的內容。
點選連結加入群聊【JAVA高階架構技術交流】:jq.qq.com/?_wv=1027&a…