一、如何理解分布式事務(wù)
在傳統(tǒng)的關(guān)系型數(shù)據(jù)庫中,事務(wù)是一個(gè)標(biāo)準(zhǔn)組件,幾乎所有成熟的關(guān)系型數(shù)據(jù)庫都提供了對(duì)本地事務(wù)的原生支持。本地事務(wù)提供了 ACID 事務(wù)特性?;诒镜厥聞?wù),為了保證數(shù)據(jù)的一致性,我們先開啟一個(gè)事務(wù)后,才可以執(zhí)行數(shù)據(jù)操作,最后提交或回滾就可以了。更進(jìn)一步,借助于 Spring 等集成化框架,開發(fā)人員只需關(guān)注引起數(shù)據(jù)改變的業(yè)務(wù)即可。
但在分布式環(huán)境下,事情就會(huì)變得比較復(fù)雜。假設(shè)系統(tǒng)中存在多個(gè)獨(dú)立的數(shù)據(jù)庫,為了確保數(shù)據(jù)在這些獨(dú)立的數(shù)據(jù)庫中保持一致,我們需要把這些數(shù)據(jù)庫納入同一個(gè)事務(wù)中。這時(shí)本地事務(wù)就無能為力了,我們需要使用分布式事務(wù)。
業(yè)界關(guān)于如何實(shí)現(xiàn)分布式事務(wù)也有一些通用的實(shí)現(xiàn)機(jī)制,例如支持兩階段提交的 XA 協(xié)議以及以 Saga 為代表的柔性事務(wù)。針對(duì)不同的實(shí)現(xiàn)機(jī)制,也存在一些供應(yīng)商和開發(fā)工具。因?yàn)檫@些開發(fā)工具在使用方式上和實(shí)現(xiàn)原理上都有較大的差異性,所以開發(fā)人員的一大訴求在于,希望能有一套統(tǒng)一的解決方案能夠屏蔽這些差異。同時(shí),我們也希望這種解決方案能夠提供友好的系統(tǒng)集成性。
ShardingSphere 作為一款分布式數(shù)據(jù)庫中間件,勢(shì)必要考慮分布式事務(wù)的實(shí)現(xiàn)方案。而在設(shè)計(jì)上,ShardingSphere 從一開始就充分考慮到了開發(fā)人員的這些訴求。
二、ShardingJdbc 分布式事務(wù)
sharding-jdbc分布式事務(wù)支持:官網(wǎng)https://shardingsphere.apache.org/document/current/cn/features/transaction/
ShardingSphere 中的分布式事務(wù)
在 ShardingSphere 中,除本地事務(wù)之外,還提供針對(duì)分布式事務(wù)的兩種實(shí)現(xiàn)方案,分別是 XA 事務(wù)和柔性事務(wù)。這點(diǎn)可以從事務(wù)類型枚舉值 TransactionType 中得到驗(yàn)證:
public enum TransactionType {
LOCAL, XA, BASE
}
1、本地事務(wù)
在不開啟任何分布式事務(wù)管理器的前提下,讓每個(gè)數(shù)據(jù)節(jié)點(diǎn)各自管理自己的事務(wù)。 它們之間沒有協(xié)調(diào)以及通信的能力,也并不互相知曉其他數(shù)據(jù)節(jié)點(diǎn)事務(wù)的成功與否。 本地事務(wù)在性能方面無任何損耗,但在強(qiáng)一致性以及最終一致性方面則力不從心。
2、兩階段提交:
XA協(xié)議最早的分布式事務(wù)模型是由 X/Open 國(guó)際聯(lián)盟提出的 X/Open Distributed Transaction Processing (DTP) 模型,簡(jiǎn)稱 XA 協(xié)議。
基于XA協(xié)議實(shí)現(xiàn)的分布式事務(wù)對(duì)業(yè)務(wù)侵入很小。 它最大的優(yōu)勢(shì)就是對(duì)使用方透明,用戶可以像使用本地事務(wù)一樣使用基于XA協(xié)議的分布式事務(wù)。 XA協(xié)議能夠嚴(yán)格保障事務(wù) ACID 特性。
嚴(yán)格保障事務(wù) ACID 特性是一把雙刃劍。 事務(wù)執(zhí)行在過程中需要將所需資源全部鎖定,它更加適用于執(zhí)行時(shí)間確定的短事務(wù)。 對(duì)于長(zhǎng)事務(wù)來說,整個(gè)事務(wù)進(jìn)行期間對(duì)數(shù)據(jù)的獨(dú)占,將導(dǎo)致對(duì)熱點(diǎn)數(shù)據(jù)依賴的業(yè)務(wù)系統(tǒng)并發(fā)性能衰退明顯。 因此,在高并發(fā)的性能至上場(chǎng)景中,基于XA協(xié)議的分布式事務(wù)并不是最佳選擇。
3、柔性事務(wù)
如果將實(shí)現(xiàn)了 ACID 的事務(wù)要素的事務(wù)稱為剛性事務(wù)的話,那么基于 BASE 事務(wù)要素的事務(wù)則稱為柔性事務(wù)。 BASE 是基本可用、柔性狀態(tài)和最終一致性這三個(gè)要素的縮寫。
基本可用(Basically Available):允許響應(yīng)時(shí)間拉長(zhǎng),允許功能上的損失,允許降級(jí)頁面(系統(tǒng)繁忙,稍后重試等),即分布式系統(tǒng)在出現(xiàn)故障時(shí),允許損失部分可用功能,保證核心功能可用。如,電商網(wǎng)站交易付款出現(xiàn)問題了,商品依然可以正常瀏覽。
柔性狀態(tài)(Soft state):是指允許系統(tǒng)中的數(shù)據(jù)存在中間狀態(tài),并認(rèn)為該中間狀態(tài)的存在不會(huì)影響系統(tǒng)的整體可用性。如訂單的"支付中"、“數(shù)據(jù)同步中”等狀態(tài),待數(shù)據(jù)最終一致后狀態(tài)改為“成功”狀態(tài)。
最終一致性(Eventually consistent):本質(zhì)就是需要保證最終數(shù)據(jù)能夠達(dá)到一致性,而不需要實(shí)時(shí)保證系統(tǒng)數(shù)據(jù)的強(qiáng)一致性。如訂單的"支付中"狀態(tài),最終會(huì)變?yōu)椤爸Ц冻晒Α被蛘?支付失敗",使訂單狀態(tài)與實(shí)際交易結(jié)果達(dá)成一致,但需要一定時(shí)間的延遲、等待。
在 ACID 事務(wù)中對(duì)隔離性的要求很高,在事務(wù)執(zhí)行過程中,必須將所有的資源鎖定。 柔性事務(wù)的理念則是通過業(yè)務(wù)邏輯將互斥鎖操作從資源層面上移至業(yè)務(wù)層面。通過放寬對(duì)強(qiáng)一致性要求,來換取系統(tǒng)吞吐量的提升。
三、ShardingJdbc 分布式事務(wù)使用
3.1 不使用Spring
3.1.1 引入Maven依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<!-- 使用XA事務(wù)時(shí),需要引入此模塊 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<!-- 使用BASE事務(wù)時(shí),需要引入此模塊 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
3.1.2 基于Java編碼方式使用分布式事務(wù)
TransactionTypeHolder.set(TransactionType.XA); // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE
try (Connection connection = dataSource.getConnection()) { // dataSource的類型為ShardingDataSource
connection.setAutoCommit(false);
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO t_order (user_id, status) VALUES (?, ?)");
preparedStatement.setObject(1, i);
preparedStatement.setObject(2, "init");
preparedStatement.executeUpdate();
connection.commit();
}
3.2 使用Spring-namespace
3.2.1 引入Maven依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<!-- 使用XA事務(wù)時(shí),需要引入此模塊 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<!-- 使用BASE事務(wù)時(shí),需要引入此模塊 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
3.2.2 配置spring-namespace的事務(wù)管理器
<!-- 進(jìn)行ShardingDataSource的相關(guān)配置 -->
...
<!-- 開啟自動(dòng)掃描@ShardingTransactionType注解,使用Spring原生的AOP在類和方法上進(jìn)行增強(qiáng) -->
<sharding:tx-type-annotation-driven />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="shardingDataSource" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="shardingDataSource" />
</bean>
<tx:annotation-driven />
3.2.3 業(yè)務(wù)代碼中使用分布式事務(wù)
@Transactional
@ShardingTransactionType(TransactionType.XA) // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE
public void insert() {
jdbcTemplate.execute("INSERT INTO t_order (user_id, status) VALUES (?, ?)", (PreparedStatementCallback<Object>) preparedStatement -> {
preparedStatement.setObject(1, i);
preparedStatement.setObject(2, "init");
preparedStatement.executeUpdate();
});
}
3.3 使用Spring-boot
3.3.1 引入Maven依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<!-- 使用XA事務(wù)時(shí),需要引入此模塊 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<!-- 使用BASE事務(wù)時(shí),需要引入此模塊 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
3.3.2 配置spring-boot的事務(wù)管理器
@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {
@Bean
public PlatformTransactionManager txManager(final DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
3.3.3 業(yè)務(wù)代碼中使用分布式事務(wù)
@Transactional
@ShardingTransactionType(TransactionType.XA) // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE
public void insert() {
jdbcTemplate.execute("INSERT INTO t_order (user_id, status) VALUES (?, ?)", (PreparedStatementCallback<Object>) preparedStatement -> {
preparedStatement.setObject(1, i);
preparedStatement.setObject(2, "init");
preparedStatement.executeUpdate();
});
}
分布式事務(wù)管理器的特有配置
XA事務(wù)管理器參數(shù)配置(可選)
ShardingSphere默認(rèn)的XA事務(wù)管理器為Atomikos,在項(xiàng)目的logs目錄中會(huì)生成xa_tx.log, 這是XA崩潰恢復(fù)時(shí)所需的日志,請(qǐng)勿刪除。
也可以通過在項(xiàng)目的classpath中添加jta.properties來定制化Atomikos配置項(xiàng)。具體的配置規(guī)則請(qǐng)參考Atomikos的官方文檔。
BASE柔性事務(wù)管理器(SEATA-AT配置)
1、按照seata-work-shop中的步驟,下載并啟動(dòng)seata server,參考 Step6 和 Step7即可。
2、在每一個(gè)分片數(shù)據(jù)庫實(shí)例中執(zhí)創(chuàng)建undo_log表(以MySQL為例)
CREATE TABLE IF NOT EXISTS `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME NOT NULL COMMENT 'modify datetime',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
- 3、在classpath中增加seata.conf
client {
application.id = example ## 應(yīng)用唯一id
transaction.service.group = my_test_tx_group ## 所屬事務(wù)組
}
- 4、根據(jù)實(shí)際場(chǎng)景修改seata的file.conf和registry.conf文件
注意:
shardingjdbc對(duì)數(shù)據(jù)庫的訪問有一些限制:
官方站的說明:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/unsupported-items/
四、 ShardingSphere集成Nacos配置中心
4.1 集成配置中心
為了集成配置中心,第一步需要引入 ShardingSphere 中與編排治理相關(guān)的依賴包。在 Spring Boot 環(huán)境中,這個(gè)依賴包是 sharding-jdbc-orchestration-spring-boot-starter:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-orchestration-spring-boot-starter</artifactId>
</dependency>
引入Nacos配置相關(guān)依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-orchestration-reg-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
如果是ZooKeeeper作為配置中心的話,需引入相關(guān)依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-orchestration-reg-zookeeper-curator</artifactId>
</dependency>
4.2 掌握配置項(xiàng)
針對(duì)配置中心,ShardingSphere 提供了一系列的 DataSource,包括用于數(shù)據(jù)分片的 OrchestrationShardingDataSource、用于讀寫分離的 OrchestrationMasterSlaveDataSource 以及用于數(shù)據(jù)脫敏的 OrchestrationEncryptDataSource。圍繞這些 DataSource,也存在對(duì)應(yīng)的 DataSourceFactory 工廠類。這里以 OrchestrationMasterSlaveDataSourceFactory 為例來看創(chuàng)建 DataSource 所需要的配置類:
public final class OrchestrationMasterSlaveDataSourceFactory {
public static DataSource createDataSource(final Map<String, DataSource> dataSourceMap, final MasterSlaveRuleConfiguration masterSlaveRuleConfig,
final Properties props, final OrchestrationConfiguration orchestrationConfig) throws SQLException {
if (null == masterSlaveRuleConfig || null == masterSlaveRuleConfig.getMasterDataSourceName()) {
return createDataSource(orchestrationConfig);
}
MasterSlaveDataSource masterSlaveDataSource = new MasterSlaveDataSource(dataSourceMap, new MasterSlaveRule(masterSlaveRuleConfig), props);
return new OrchestrationMasterSlaveDataSource(masterSlaveDataSource, orchestrationConfig);
}
…
}
可以看到,這里存在一個(gè)治理規(guī)則配置類 OrchestrationConfiguration,而在其他的 DataSourceFactory 中所使用的也是這個(gè)配置類:
public final class OrchestrationConfiguration {
//治理規(guī)則名稱
private final String name;
//注冊(cè)(配置)中心配置類
private final RegistryCenterConfiguration regCenterConfig;
//本地配置是否覆寫服務(wù)器配置標(biāo)志位
private final boolean overwrite;
}
在 OrchestrationConfiguration 中我們看到了用于指定本地配置是否覆寫服務(wù)器配置的 overwrite 標(biāo)志位,也看到了一個(gè)注冊(cè)中心的配置子類 RegistryCenterConfiguration。RegistryCenterConfiguration 包的含內(nèi)容比較多,我們截取最常見最通用的部分配置項(xiàng):
public final class RegistryCenterConfiguration extends TypeBasedSPIConfiguration {
//配置中心服務(wù)器列表
private String serverLists;
//命名空間
private String namespace;
…
}
這里包含了配置中心服務(wù)器列表 serverLists 以及用于標(biāo)識(shí)唯一性的命名空間 namespace。因?yàn)?RegistryCenterConfiguration 繼承了 TypeBasedSPIConfiguration,所以也就自動(dòng)帶有 type 和 properties 這兩個(gè)配置項(xiàng)。
4.3 在Nacos創(chuàng)建配置
spring.shardingsphere.datasource.names=dsmaster,dsslave0,dsslave1
spring.shardingsphere.datasource.dsmaster.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.dsmaster.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsmaster.jdbc-url=jdbc:mysql://localhost:3306/dsmaster
spring.shardingsphere.datasource.dsmaster.username=root
spring.shardingsphere.datasource.dsmaster.password=root
spring.shardingsphere.datasource.dsslave0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.dsslave0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsslave0.jdbc-url=jdbc:mysql://localhost:3306/dsslave0
spring.shardingsphere.datasource.dsslave0.username=root
spring.shardingsphere.datasource.dsslave0.password=root
spring.shardingsphere.datasource.dsslave1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.dsslave1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsslave1.jdbc-url=jdbc:mysql://localhost:3306/dsslave1
spring.shardingsphere.datasource.dsslave1.username=root
spring.shardingsphere.datasource.dsslave1.password=root
spring.shardingsphere.masterslave.load-balance-algorithm-type=random
spring.shardingsphere.masterslave.name=health_ms
spring.shardingsphere.masterslave.master-data-source-name=dsmaster
spring.shardingsphere.masterslave.slave-data-source-names=dsslave0,dsslave1
spring.shardingsphere.props.sql.show=true
4.4 ShardingSphere配置開關(guān)
- spring.shardingsphere.orchestration.overwrite=true:采用本地配置。
- spring.shardingsphere.orchestration.overwrite=false:只從配置中心讀取配置。
選擇使用阿里巴巴的 Nacos來構(gòu)建配置中心服務(wù)器
spring.shardingsphere.orchestration.name=health_ms
spring.shardingsphere.orchestration.overwrite=false
spring.shardingsphere.orchestration.registry.type=nacos
spring.shardingsphere.orchestration.registry.server-lists=localhost:8848
spring.shardingsphere.orchestration.registry.namespace=
如果采用其他配置配置中心需修改spring.shardingsphere.orchestration.registry.type并提供對(duì)應(yīng)的 server-lists即可。
參考:
https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/transaction/
https://segmentfault.com/a/1190000023379017