溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法

發(fā)布時(shí)間:2021-09-26 13:44:28 來(lái)源:億速云 閱讀:162 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

隨著業(yè)務(wù)的逐漸增大,原有保存在單表的數(shù)據(jù)量也日益增強(qiáng)。數(shù)據(jù)庫(kù)數(shù)據(jù)會(huì)隨著業(yè)務(wù)的發(fā)展而不斷增多,因此數(shù)據(jù)操作,如增刪改查的開(kāi)銷也會(huì)越來(lái)越大。再加上物理服務(wù)器的資源有限(CPU、磁盤、內(nèi)存、IO 等)。最終數(shù)據(jù)庫(kù)所能承載的數(shù)據(jù)量、數(shù)據(jù)處理能力都將遭遇瓶頸。換句話說(shuō)需要合理的數(shù)據(jù)庫(kù)架構(gòu)來(lái)存放不斷增長(zhǎng)的數(shù)據(jù),這個(gè)就是分庫(kù)分表的設(shè)計(jì)初衷。目的就是為了緩解數(shù)據(jù)庫(kù)的壓力,大限度提高數(shù)據(jù)操作的效率。

數(shù)據(jù)庫(kù)分庫(kù)分表中間件是采用的 apache sharding。

1、Sharing JDBC 簡(jiǎn)介

ShardingSphere是一套開(kāi)源的分布式數(shù)據(jù)庫(kù)中間件解決方案組成的生態(tài)圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(計(jì)劃中)這3款相互獨(dú)立的產(chǎn)品組成。 他們均提供標(biāo)準(zhǔn)化的數(shù)據(jù)分片、分布式事務(wù)和數(shù)據(jù)庫(kù)治理功能,可適用于如Java同構(gòu)、異構(gòu)語(yǔ)言、云原生等各種多樣化的應(yīng)用場(chǎng)景。

ShardingSphere定位為關(guān)系型數(shù)據(jù)庫(kù)中間件,旨在充分合理地在分布式的場(chǎng)景下利用關(guān)系型數(shù)據(jù)庫(kù)的計(jì)算和存儲(chǔ)能力,而并非實(shí)現(xiàn)一個(gè)全新的關(guān)系型數(shù)據(jù)庫(kù)。 它與NoSQL和NewSQL是并存而非互斥的關(guān)系。NoSQL和NewSQL作為新技術(shù)探索的前沿,放眼未來(lái),擁抱變化,是非常值得推薦的。反之,也可以用另一種思路看待問(wèn)題,放眼未來(lái),關(guān)注不變的東西,進(jìn)而抓住事物本質(zhì)。 關(guān)系型數(shù)據(jù)庫(kù)當(dāng)今依然占有巨大市場(chǎng),是各個(gè)公司核心業(yè)務(wù)的基石,未來(lái)也難于撼動(dòng),我們目前階段更加關(guān)注在原有基礎(chǔ)上的增量,而非顛覆。

ShardingSphere已經(jīng)在2020年4月16日從Apache孵化器畢業(yè),成為Apache頂級(jí)項(xiàng)目。

2、系統(tǒng)改造

因?yàn)槲覀児緦儆诘谌街Ц镀脚_(tái),這個(gè)改造的點(diǎn)可以分為兩類:提供給商戶調(diào)用的對(duì)接系統(tǒng)(比如收銀臺(tái)),系統(tǒng)內(nèi)部調(diào)用的系統(tǒng)(支付引擎)。

  • 收銀臺(tái)系統(tǒng):核心功能是提供給商戶提交交易訂單,并且對(duì)這筆交易訂單進(jìn)行支付的支付訂單

  • 支付引擎:接收這個(gè)支付產(chǎn)品的請(qǐng)求,調(diào)用渠道,記賬,結(jié)算等功能

數(shù)據(jù)源使用分庫(kù)分表,在 Sharding JDBC 當(dāng)中如果進(jìn)行 修改、刪除、查詢操作中沒(méi)有包含分片鍵就會(huì)進(jìn)行全表掃描。所以在進(jìn)行業(yè)務(wù)改造的時(shí)候?qū)υ械臄?shù)據(jù)庫(kù)操作都進(jìn)行了業(yè)務(wù)優(yōu)化,基本改造后的所有的操作都使用了基于分片鍵進(jìn)行操作(定時(shí)任務(wù)除外)。

2.1 對(duì)接外部系統(tǒng)的系統(tǒng)

首先討論一下,提供給商戶調(diào)用的系統(tǒng)。在進(jìn)行下單操作的時(shí)候,商戶必須傳遞商戶號(hào)和外部訂單號(hào)。對(duì)于外部訂單號(hào)第三方支付系統(tǒng)無(wú)法控制,只需要商戶每次傳遞過(guò)來(lái)的時(shí)候與歷史的外部訂單號(hào)不重復(fù)就可以了。所以這里就涉及到一張映射表,這個(gè)表的主要功能如下:

  • 把商戶的外部訂單號(hào)映射成內(nèi)部訂單號(hào)

  • 通過(guò)商戶號(hào)與商戶的外部訂單號(hào)在數(shù)據(jù)庫(kù)聯(lián)合唯一達(dá)到冪等處理

  • 保存商戶請(qǐng)求的原始數(shù)據(jù),做為請(qǐng)求憑證

這個(gè)時(shí)候?qū)灰子唵尉鸵蕾囉谕獠坑成浔?,把?qǐng)求映射成內(nèi)部訂單號(hào)進(jìn)行分片就可以了

2.2 內(nèi)部系統(tǒng)間的調(diào)用

當(dāng)商戶下好了交易訂單的時(shí)候,需要進(jìn)行支付這個(gè)時(shí)候就產(chǎn)生了一筆支付訂單。交易訂單和支付訂單是一對(duì)多的關(guān)系。當(dāng)用戶進(jìn)行支付的時(shí)候會(huì)調(diào)用支付引擎,這個(gè)時(shí)候正常情況下一般會(huì)生成支付系統(tǒng)的支付訂單。然后支付引擎會(huì)調(diào)用后續(xù)的渠道、結(jié)算、記賬等系統(tǒng),系統(tǒng)之間的調(diào)用圖如下:

SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法

如果以支付系統(tǒng)的支付訂單的訂單號(hào)做為分片鍵時(shí):

  • 支付引擎的內(nèi)部系統(tǒng)可以使用分片鍵查詢,會(huì)路由到具體的庫(kù)表當(dāng)中,沒(méi)有問(wèn)題

  • 渠道、結(jié)算、記賬等系統(tǒng)如果涉及到回調(diào)支付引擎,在調(diào)用的時(shí)候會(huì)把支付引擎的支付單號(hào)傳遞給后續(xù)系統(tǒng),如果進(jìn)行回調(diào)操作時(shí)候,可以回傳這個(gè)支付單號(hào)。會(huì)路由到具體的庫(kù)表當(dāng)中,沒(méi)有問(wèn)題

  • 收銀臺(tái)需要根據(jù)交易的支付訂單查詢支付引擎生成的支付單。由于不是根據(jù)分片鍵查詢,不能路由到具體的庫(kù)中的具體表上,會(huì)進(jìn)行全表掃描。就會(huì)有問(wèn)題。

3、解決方案

首先想到的方案可以參考收銀臺(tái)系統(tǒng),把收銀臺(tái)調(diào)用支付引擎看到外部調(diào)用。然后添加一張映射表,把收銀臺(tái)生成的支付流水號(hào)與支付引擎的支付單號(hào)關(guān)聯(lián)起來(lái)。當(dāng)收銀臺(tái)需要查詢支付引擎時(shí),可以先通過(guò)映射表查詢到具體的支付單號(hào),這樣就可以進(jìn)行分片鍵操作數(shù)據(jù)源了。這個(gè)方案存在一個(gè)問(wèn)題存在以下幾個(gè)問(wèn)題:

引入了關(guān)聯(lián)表,添加了系統(tǒng)復(fù)雜度進(jìn)行數(shù)據(jù)查詢的時(shí)候會(huì)兩次查詢,先查詢映射表,然后再查詢支付單

那么有沒(méi)有其它方案呢?答案是肯定的。

我們來(lái)看一下收銀臺(tái)、支付引擎其實(shí)這兩個(gè)系統(tǒng)在支付系統(tǒng)中是同一個(gè)緯度的。如果收銀臺(tái)的交易訂單進(jìn)行支付的時(shí)候,就會(huì)在支付引擎當(dāng)中下一筆支付單。我們可以把交易單與支付單在同一個(gè)水平緯度上進(jìn)行數(shù)據(jù)庫(kù)拆分。

什么叫同一個(gè)緯度的數(shù)據(jù)庫(kù)拆分呢?

其實(shí)就是收銀臺(tái)的支付訂單進(jìn)行分庫(kù)分表之后,這條數(shù)據(jù)落在數(shù)據(jù)庫(kù)里面的哪一個(gè)庫(kù),哪一張表就一定了。這個(gè)時(shí)候支付引擎就可以通過(guò)這個(gè)單號(hào)獲取到具體的庫(kù)表信息。這樣就可以把支付引擎生成的的訂單號(hào)帶個(gè)具體的庫(kù)表信息。然后在進(jìn)行分庫(kù)分表算法定義的時(shí)候根據(jù)支付引擎生成的訂單號(hào)中帶的庫(kù)表信息路由到具體的庫(kù)表中去了。就樣就會(huì)解決上面的問(wèn)題,不需要映射表。同時(shí)這種方案也會(huì)帶來(lái)以下的問(wèn)題:

  • 數(shù)據(jù)上游與下游的分庫(kù)分表必須一致

  • 數(shù)據(jù)在進(jìn)行再次擴(kuò)容會(huì)有其它問(wèn)題

經(jīng)過(guò)討論決定使用方案二。

4、代碼實(shí)現(xiàn)

下面通過(guò) Sharding jdbc 的復(fù)合分片簡(jiǎn)單的模擬代碼實(shí)現(xiàn)。數(shù)據(jù)庫(kù)、表準(zhǔn)備:

數(shù)據(jù)庫(kù):
- order_0
- order_1

每個(gè)數(shù)據(jù)庫(kù)的表:
tb_order_0
tb_order_1
tb_order_2
tb_order_3
tb_order_4
tb_order_5
tb_order_6
tb_order_7

# 邏輯表
create table tb_order
(
    trade_master_no varchar(16),
    pay_order_no    varchar(16) ,
);

# 準(zhǔn)備數(shù)據(jù)
# 分庫(kù)分表規(guī)則是前一位代表庫(kù),后一位代表表,所以在 order_1.tb_order_1 中添加以下數(shù)據(jù)
insert into tb_order_1 values('11', '11'),

4.1 Sharding JDBC 配置

下面是針對(duì)訂單表的 sharding jdbc 的分庫(kù)分表配置,數(shù)據(jù)庫(kù)連接池使用 Hikari 。分片規(guī)則:前一位代表庫(kù),后一位代表表。使用交易主單號(hào)(trade_master_no) 和 支付單號(hào)(pay_order_no) 作為復(fù)合分片。當(dāng)查詢條件中只要包含一個(gè)查詢規(guī)則時(shí)就會(huì)路由到具體庫(kù)表中。

ComplexShardingJDBCConfig.java

@Configuration
public class ComplexShardingJDBCConfig {

	@Bean
	public DataSource getShardingDataSource(HikariCommonConfig commonConfig) throws SQLException {
		ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
		shardingRuleConfig.getTableRuleConfigs().add(getShardingMessageTableRuleConfiguration());
		Map<String, DataSource> dataSourceMap = new HashMap<>();
		dataSourceMap.put("order_0", createDataSource(datasourceOne(commonConfig)));
		dataSourceMap.put("order_1", createDataSource(datasourceTwo(commonConfig)));
		Properties properties = new Properties();
		properties.setProperty(ShardingPropertiesConstant.SQL_SHOW.getKey(), "true");
		return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, properties);
	}

	private TableRuleConfiguration getShardingMessageTableRuleConfiguration() {
		TableRuleConfiguration shardingMessageConfiguration = new TableRuleConfiguration("tb_order", "order_${0..1}.tb_order_${0..7}");
		shardingMessageConfiguration.setDatabaseShardingStrategyConfig(messageDatasourceShardingStrategyConfig());
		shardingMessageConfiguration.setTableShardingStrategyConfig(messageTableShardingStrategyConfig());
		return shardingMessageConfiguration;
	}

	private ComplexShardingStrategyConfiguration messageDatasourceShardingStrategyConfig(){
		return new ComplexShardingStrategyConfiguration("trade_master_no,pay_order_no", new OrderDatasourceComplexKeysShardingAlgorithm());
	}

	private ShardingStrategyConfiguration messageTableShardingStrategyConfig() {
		return new ComplexShardingStrategyConfiguration("trade_master_no,pay_order_no", new OrderTableComplexKeysShardingAlgorithm());
	}

	@Bean
	@ConfigurationProperties(prefix = "spring.datasource.ds1")
	public HikariConfig datasourceOne(HikariCommonConfig commonConfig){
		HikariConfig hikariConfig = new HikariConfig();
		hikariConfig.setMinimumIdle(commonConfig.getMinimumIdle());
		hikariConfig.setIdleTimeout(commonConfig.getIdleTimeout());
		hikariConfig.setMaximumPoolSize(commonConfig.getMaximumPoolSize());
		hikariConfig.setPoolName(commonConfig.getPoolName());
		hikariConfig.setMaxLifetime(commonConfig.getMaxLifetime());
		hikariConfig.setConnectionTimeout(commonConfig.getConnectionTimeout());
		hikariConfig.setConnectionTestQuery(commonConfig.getConnectionTestQuery());
		return hikariConfig;
	}

	@Bean
	@ConfigurationProperties(prefix = "spring.datasource.ds2")
	public HikariConfig datasourceTwo(HikariCommonConfig commonConfig){
		HikariConfig hikariConfig = new HikariConfig();
		hikariConfig.setMinimumIdle(commonConfig.getMinimumIdle());
		hikariConfig.setIdleTimeout(commonConfig.getIdleTimeout());
		hikariConfig.setMaximumPoolSize(commonConfig.getMaximumPoolSize());
		hikariConfig.setPoolName(commonConfig.getPoolName());
		hikariConfig.setMaxLifetime(commonConfig.getMaxLifetime());
		hikariConfig.setConnectionTimeout(commonConfig.getConnectionTimeout());
		hikariConfig.setConnectionTestQuery(commonConfig.getConnectionTestQuery());
		return hikariConfig;
	}

	private HikariDataSource createDataSource(HikariConfig hikariConfig) {
		HikariDataSource sharding = new HikariDataSource();
		BeanUtils.copyProperties(hikariConfig, sharding);
		return sharding;
	}

}

數(shù)據(jù)庫(kù)分片規(guī)則:

public class OrderDatasourceComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<String> {

	@Override
	public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
		Map<String, Collection<String>> columnNameAndShardingValuesMap = shardingValue.getColumnNameAndShardingValuesMap();
		if(columnNameAndShardingValuesMap.containsKey("trade_master_no")){
			Collection<String> tradeMasterNos = columnNameAndShardingValuesMap.get("trade_master_no");
			String tradeMasterNo = tradeMasterNos.iterator().next();
			String datasourceSuffix = tradeMasterNo.substring(0, 1);
			for (String availableTargetName : availableTargetNames) {
				if(availableTargetName.endsWith(datasourceSuffix)){
					return Lists.newArrayList(availableTargetName);
				}
			}
		}
		if(columnNameAndShardingValuesMap.containsKey("pay_order_no")){
			Collection<String> payOrderNos = columnNameAndShardingValuesMap.get("pay_order_no");
			String payOrderNo = payOrderNos.iterator().next();
			String datasourceSuffix = payOrderNo.substring(0, 1);
			for (String availableTargetName : availableTargetNames) {
				if(availableTargetName.endsWith(datasourceSuffix)){
					return Lists.newArrayList(availableTargetName);
				}
			}
		}
		throw new UnsupportedOperationException();
	}

}

數(shù)據(jù)庫(kù)中的表分片規(guī)則:

public class OrderTableComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<String> {

	@Override
	public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<String> shardingValue) {
		Map<String, Collection<String>> columnNameAndShardingValuesMap = shardingValue.getColumnNameAndShardingValuesMap();
		if(columnNameAndShardingValuesMap.containsKey("trade_master_no")){
			Collection<String> tradeMasterNos = columnNameAndShardingValuesMap.get("trade_master_no");
			String tradeMasterNo = tradeMasterNos.iterator().next();
			String datasourceSuffix = tradeMasterNo.substring(1, 2);
			for (String availableTargetName : availableTargetNames) {
				if(availableTargetName.endsWith(datasourceSuffix)){
					return Lists.newArrayList(availableTargetName);
				}
			}
		}
		if(columnNameAndShardingValuesMap.containsKey("pay_order_no")){
			Collection<String> payOrderNos = columnNameAndShardingValuesMap.get("pay_order_no");
			String payOrderNo = payOrderNos.iterator().next();
			String datasourceSuffix = payOrderNo.substring(1, 2);
			for (String availableTargetName : availableTargetNames) {
				if(availableTargetName.endsWith(datasourceSuffix)){
					return Lists.newArrayList(availableTargetName);
				}
			}
		}
		throw new UnsupportedOperationException();
	}

}

4.2 數(shù)據(jù)源操作類

這里使用 Mybatis 操作數(shù)據(jù)源,當(dāng)然使用其它 ORM 框架操作數(shù)據(jù)源 sharding jdbc 也是支持的。

public interface OrderMapper {
    int countByExample(OrderExample example);

    int deleteByExample(OrderExample example);

    int insert(Order record);

    int insertSelective(Order record);

    List<Order> selectByExample(OrderExample example);

    int updateByExampleSelective(@Param("record") Order record, @Param("example") OrderExample example);

    int updateByExample(@Param("record") Order record, @Param("example") OrderExample example);
}

4.3 分片測(cè)試類

通過(guò) Spring boot 定義一個(gè) Controller,使用 Order 對(duì)象查詢。即可以使用交易單號(hào)也可以使用支付單號(hào)查詢。

@Getter
@Setter
public class Order {

    private String tradeMasterNo;

    private String payOrderNo;

}

@RestController
@RequestMapping("order")
public class OrderController {

	@Resource
	private OrderDao orderDao;

	@RequestMapping("query")
	public Order query(@RequestBody Order order) {
		Order orderInDB = orderDao.queryOrder(order);
		return orderInDB;
	}
}

4.4 測(cè)試結(jié)果

由于我們?cè)?sharing jdbc 配置當(dāng)中配置了數(shù)據(jù)庫(kù)查詢 SQL,我們只需要觀察是不是只打印了一條數(shù)據(jù)庫(kù)操作語(yǔ)句就可以判斷之前的結(jié)論是否正確。

通過(guò) Postman 使用交易單號(hào)查詢:

SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法

控制臺(tái)打印:

SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法

然后通過(guò) Postman 使用支付單號(hào)查詢:

SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法

控制臺(tái)打?。?/p>

SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法

它們查詢都是路由到具體的庫(kù)表當(dāng)中,說(shuō)明我們的方案是可以的。

關(guān)于SpringBoot集成Sharding Jdbc使用復(fù)合分片的操作方法就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI