您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關SpringBoot集成Sharding Jdbc使用復合分片的操作方法,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
隨著業務的逐漸增大,原有保存在單表的數據量也日益增強。數據庫數據會隨著業務的發展而不斷增多,因此數據操作,如增刪改查的開銷也會越來越大。再加上物理服務器的資源有限(CPU、磁盤、內存、IO 等)。最終數據庫所能承載的數據量、數據處理能力都將遭遇瓶頸。換句話說需要合理的數據庫架構來存放不斷增長的數據,這個就是分庫分表的設計初衷。目的就是為了緩解數據庫的壓力,大限度提高數據操作的效率。
數據庫分庫分表中間件是采用的 apache sharding。
ShardingSphere是一套開源的分布式數據庫中間件解決方案組成的生態圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(計劃中)這3款相互獨立的產品組成。 他們均提供標準化的數據分片、分布式事務和數據庫治理功能,可適用于如Java同構、異構語言、云原生等各種多樣化的應用場景。
ShardingSphere定位為關系型數據庫中間件,旨在充分合理地在分布式的場景下利用關系型數據庫的計算和存儲能力,而并非實現一個全新的關系型數據庫。 它與NoSQL和NewSQL是并存而非互斥的關系。NoSQL和NewSQL作為新技術探索的前沿,放眼未來,擁抱變化,是非常值得推薦的。反之,也可以用另一種思路看待問題,放眼未來,關注不變的東西,進而抓住事物本質。 關系型數據庫當今依然占有巨大市場,是各個公司核心業務的基石,未來也難于撼動,我們目前階段更加關注在原有基礎上的增量,而非顛覆。
ShardingSphere已經在2020年4月16日從Apache孵化器畢業,成為Apache頂級項目。
因為我們公司屬于第三方支付平臺,這個改造的點可以分為兩類:提供給商戶調用的對接系統(比如收銀臺),系統內部調用的系統(支付引擎)。
收銀臺系統:核心功能是提供給商戶提交交易訂單,并且對這筆交易訂單進行支付的支付訂單
支付引擎:接收這個支付產品的請求,調用渠道,記賬,結算等功能
數據源使用分庫分表,在 Sharding JDBC 當中如果進行 修改、刪除、查詢操作中沒有包含分片鍵就會進行全表掃描。所以在進行業務改造的時候對原有的數據庫操作都進行了業務優化,基本改造后的所有的操作都使用了基于分片鍵進行操作(定時任務除外)。
首先討論一下,提供給商戶調用的系統。在進行下單操作的時候,商戶必須傳遞商戶號和外部訂單號。對于外部訂單號第三方支付系統無法控制,只需要商戶每次傳遞過來的時候與歷史的外部訂單號不重復就可以了。所以這里就涉及到一張映射表,這個表的主要功能如下:
把商戶的外部訂單號映射成內部訂單號
通過商戶號與商戶的外部訂單號在數據庫聯合唯一達到冪等處理
保存商戶請求的原始數據,做為請求憑證
這個時候對交易訂單就依賴于外部映射表,把請求映射成內部訂單號進行分片就可以了
當商戶下好了交易訂單的時候,需要進行支付這個時候就產生了一筆支付訂單。交易訂單和支付訂單是一對多的關系。當用戶進行支付的時候會調用支付引擎,這個時候正常情況下一般會生成支付系統的支付訂單。然后支付引擎會調用后續的渠道、結算、記賬等系統,系統之間的調用圖如下:
如果以支付系統的支付訂單的訂單號做為分片鍵時:
支付引擎的內部系統可以使用分片鍵查詢,會路由到具體的庫表當中,沒有問題
渠道、結算、記賬等系統如果涉及到回調支付引擎,在調用的時候會把支付引擎的支付單號傳遞給后續系統,如果進行回調操作時候,可以回傳這個支付單號。會路由到具體的庫表當中,沒有問題
收銀臺需要根據交易的支付訂單查詢支付引擎生成的支付單。由于不是根據分片鍵查詢,不能路由到具體的庫中的具體表上,會進行全表掃描。就會有問題。
首先想到的方案可以參考收銀臺系統,把收銀臺調用支付引擎看到外部調用。然后添加一張映射表,把收銀臺生成的支付流水號與支付引擎的支付單號關聯起來。當收銀臺需要查詢支付引擎時,可以先通過映射表查詢到具體的支付單號,這樣就可以進行分片鍵操作數據源了。這個方案存在一個問題存在以下幾個問題:
引入了關聯表,添加了系統復雜度進行數據查詢的時候會兩次查詢,先查詢映射表,然后再查詢支付單
那么有沒有其它方案呢?答案是肯定的。
我們來看一下收銀臺、支付引擎其實這兩個系統在支付系統中是同一個緯度的。如果收銀臺的交易訂單進行支付的時候,就會在支付引擎當中下一筆支付單。我們可以把交易單與支付單在同一個水平緯度上進行數據庫拆分。
什么叫同一個緯度的數據庫拆分呢?
其實就是收銀臺的支付訂單進行分庫分表之后,這條數據落在數據庫里面的哪一個庫,哪一張表就一定了。這個時候支付引擎就可以通過這個單號獲取到具體的庫表信息。這樣就可以把支付引擎生成的的訂單號帶個具體的庫表信息。然后在進行分庫分表算法定義的時候根據支付引擎生成的訂單號中帶的庫表信息路由到具體的庫表中去了。就樣就會解決上面的問題,不需要映射表。同時這種方案也會帶來以下的問題:
數據上游與下游的分庫分表必須一致
數據在進行再次擴容會有其它問題
經過討論決定使用方案二。
下面通過 Sharding jdbc 的復合分片簡單的模擬代碼實現。數據庫、表準備:
數據庫: - order_0 - order_1 每個數據庫的表: 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) , ); # 準備數據 # 分庫分表規則是前一位代表庫,后一位代表表,所以在 order_1.tb_order_1 中添加以下數據 insert into tb_order_1 values('11', '11'),
下面是針對訂單表的 sharding jdbc 的分庫分表配置,數據庫連接池使用 Hikari 。分片規則:前一位代表庫,后一位代表表。使用交易主單號(trade_master_no) 和 支付單號(pay_order_no) 作為復合分片。當查詢條件中只要包含一個查詢規則時就會路由到具體庫表中。
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; } }
數據庫分片規則:
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(); } }
數據庫中的表分片規則:
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(); } }
這里使用 Mybatis 操作數據源,當然使用其它 ORM 框架操作數據源 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); }
通過 Spring boot 定義一個 Controller,使用 Order 對象查詢。即可以使用交易單號也可以使用支付單號查詢。
@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; } }
由于我們在 sharing jdbc 配置當中配置了數據庫查詢 SQL,我們只需要觀察是不是只打印了一條數據庫操作語句就可以判斷之前的結論是否正確。
通過 Postman 使用交易單號查詢:
控制臺打印:
然后通過 Postman 使用支付單號查詢:
控制臺打印:
它們查詢都是路由到具體的庫表當中,說明我們的方案是可以的。
關于SpringBoot集成Sharding Jdbc使用復合分片的操作方法就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。