マイクロサービス環境では、データベースやサービスが分割されることで、従来の単一DBトランザクションでは対応できない「複数サービス・複数DBにまたがる一貫性」の問題が生じます。この課題を解決するための代表的なフレームワークがSeataです。
分散トランザクションの基本戦略
- 強一貫性:全ノードが成功か失敗かを同期的に保証。性能は犠牲になるが、整合性は確実。
- 最終一貫性:一時的な不整合を許容し、時間経過で整合性を回復。可用性とスケーラビリティに優れる。
Seataが提供する4つのモード
1. XAモード(強一貫性)
データベースのXAプロトコルを利用。第一段階でロックを保持するため性能は劣るが、ACIDを厳密に保証。
// グローバルトランザクション開始
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx.begin();
try {
// 分岐トランザクション実行
branchService.execute();
tx.commit(); // 全分岐成功 → 全体コミット
} catch (Exception e) {
tx.rollback(); // いずれか失敗 → 全体ロールバック
}
2. ATモード(最終一貫性・デフォルト)
ローカルトランザクション+undoログによる補償型。第一段階で即時コミットし、失敗時にundoログでロールバック。
グローバルロックによる同時実行制御:
他のトランザクションが同じレコードを更新しようとした場合、グローバルロックを取得できなければ一定時間リトライ後失敗。これにより「ダーティライト」を回避。
3. TCCモード(高性能・コード侵入あり)
ビジネスロジックをTry/Confirm/Cancelの3段階に分割。リソース予約→確定or取消の明示的制御。
@TwoPhaseBusinessAction(name = "transfer", commitMethod = "confirm", rollbackMethod = "cancel")
public boolean tryTransfer(@BusinessActionContextParameter(paramName = "amount") int amount) {
// Try: 残高チェック & 仮凍結
return accountService.reserve(amount);
}
public boolean confirm(BusinessActionContext context) {
// Confirm: 実際の出金処理
return accountService.deduct((int)context.getActionContext("amount"));
}
public boolean cancel(BusinessActionContext context) {
// Cancel: 凍結解除
return accountService.release((int)context.getActionContext("amount"));
}
4. Sagaモード(長時間トランザクション向け)
各ステップがローカルコミットを行い、失敗時はそれまで成功したステップを逆順で補償。非同期処理や外部API呼び出しに適す。
環境構築手順(Spring Cloud + Nacos + Docker)
Docker Compose設定
version: '3'
services:
seata-server:
image: seataio/seata-server:1.5.2
ports:
- "8091:8091"
- "7091:7091"
environment:
SEATA_CONFIG_NAME: file:/seata-config/registry
volumes:
- ./config:/seata-config
- ./logs:/root/logs
Nacos設定例(seataServer.properties)
store.mode=db
store.db.url=jdbc:mysql://db-host:3306/seata_db
store.db.user=seata_user
store.db.password=secure_password
service.vgroupMapping.my_tx_group=default
client.rm.reportSuccessEnable=true
必要テーブル作成(MySQL)
-- 各業務DBに配置
CREATE TABLE undo_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
xid VARCHAR(100) NOT NULL,
branch_id BIGINT NOT NULL,
rollback_info LONGBLOB NOT NULL,
UNIQUE KEY uk_xid_branch(xid, branch_id)
);
-- Seata管理用DB
CREATE TABLE global_table (...);
CREATE TABLE branch_table (...);
CREATE TABLE lock_table (...);
アプリケーション統合
Maven依存
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
application.yml設定
seata:
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: seata-server:8091
registry:
type: nacos
nacos:
server-addr: nacos-server:8848
namespace: seata-ns-id
トランザクション制御
@GlobalTransactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
// ローカル更新
accountMapper.debit(from.getId(), amount);
// 遠隔サービス呼び出し(失敗時は全体ロールバック)
remoteAccountService.credit(to.getId(), amount);
}
よくあるトラブルと対策
問題:Feignのfallbackで例外を握りつぶし、トランザクションがロールバックされない。
解決策:AOPでfallbackメソッドを監視し、手動ロールバックを実行。
@Aspect
@Component
public class SeataFallbackAspect {
@After("execution(* *..*Fallback.*(..))")
public void handleFallback(JoinPoint jp) {
String xid = RootContext.getXID();
if (xid != null) {
GlobalTransaction tx = GlobalTransactionContext.reload(xid);
tx.rollback(); // 手動ロールバック
}
}
}