<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          分庫(kù)分表Sharding-JDBC入門與項(xiàng)目實(shí)戰(zhàn),數(shù)據(jù)量大了一定要分表 !

          共 9605字,需瀏覽 20分鐘

           ·

          2020-10-21 21:38


          最近項(xiàng)目中不少表的數(shù)據(jù)量越來(lái)越大,并且導(dǎo)致了一些數(shù)據(jù)庫(kù)的性能問(wèn)題。因此想借助一些分庫(kù)分表的中間件,實(shí)現(xiàn)自動(dòng)化分庫(kù)分表實(shí)現(xiàn)。調(diào)研下來(lái),發(fā)現(xiàn)Sharding-JDBC目前成熟度最高并且應(yīng)用最廣的Java分庫(kù)分表的客戶端組件。本文主要介紹一些Sharding-JDBC核心概念以及生產(chǎn)環(huán)境下的實(shí)戰(zhàn)指南,旨在幫助組內(nèi)成員快速了解Sharding-JDBC并且能夠快速將其使用起來(lái)。


          核心概念


          在使用Sharding-JDBC之前,一定是先理解清楚下面幾個(gè)核心概念。


          邏輯表


          水平拆分的數(shù)據(jù)庫(kù)(表)的相同邏輯和數(shù)據(jù)結(jié)構(gòu)表的總稱。例:訂單數(shù)據(jù)根據(jù)主鍵尾數(shù)拆分為10張表,分別是t_order_0到t_order_9,他們的邏輯表名為t_order。


          真實(shí)表


          在分片的數(shù)據(jù)庫(kù)中真實(shí)存在的物理表。即上個(gè)示例中的t_order_0到t_order_9。


          數(shù)據(jù)節(jié)點(diǎn)


          數(shù)據(jù)分片的最小單元。由數(shù)據(jù)源名稱和數(shù)據(jù)表組成,例:ds_0.t_order_0。


          綁定表


          指分片規(guī)則一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,則此兩張表互為綁定表關(guān)系。綁定表之間的多表關(guān)聯(lián)查詢不會(huì)出現(xiàn)笛卡爾積關(guān)聯(lián),關(guān)聯(lián)查詢效率將大大提升。舉例說(shuō)明,如果SQL為:


          SELECT?i.* FROM?t_order o JOIN?t_order_item i ON?o.order_id=i.order_id WHERE?o.order_id in?(10, 11);


          假設(shè)t_order和t_order_item對(duì)應(yīng)的真實(shí)表各有2個(gè),那么真實(shí)表就有t_order_0、t_order_1、t_order_item_0、t_order_item_1。在不配置綁定表關(guān)系時(shí),假設(shè)分片鍵order_id將數(shù)值10路由至第0片,將數(shù)值11路由至第1片,那么路由后的SQL應(yīng)該為4條,它們呈現(xiàn)為笛卡爾積:


          SELECT?i.* FROM?t_order_0 o JOIN?t_order_item_0 i ON?o.order_id=i.order_id WHERE?o.order_id in?(10, 11);
          SELECT?i.* FROM?t_order_0 o JOIN?t_order_item_1 i ON?o.order_id=i.order_id WHERE?o.order_id in?(10, 11);
          SELECT?i.* FROM?t_order_1 o JOIN?t_order_item_0 i ON?o.order_id=i.order_id WHERE?o.order_id in?(10, 11);
          SELECT?i.* FROM?t_order_1 o JOIN?t_order_item_1 i ON?o.order_id=i.order_id WHERE?o.order_id in?(10, 11);


          在配置綁定表關(guān)系后,路由的SQL應(yīng)該為2條:


          SELECT?i.* FROM?t_order_0 o JOIN?t_order_item_0 i ON?o.order_id=i.order_id WHERE?o.order_id in?(10, 11);
          SELECT?i.* FROM?t_order_1 o JOIN?t_order_item_1 i ON?o.order_id=i.order_id WHERE?o.order_id in?(10, 11);


          廣播表


          指所有的分片數(shù)據(jù)源中都存在的表,表結(jié)構(gòu)和表中的數(shù)據(jù)在每個(gè)數(shù)據(jù)庫(kù)中均完全一致。適用于數(shù)據(jù)量不大且需要與海量數(shù)據(jù)的表進(jìn)行關(guān)聯(lián)查詢的場(chǎng)景,例如:字典表。


          數(shù)據(jù)分片分片鍵


          用于分片的數(shù)據(jù)庫(kù)字段,是將數(shù)據(jù)庫(kù)(表)水平拆分的關(guān)鍵字段。例:將訂單表中的訂單主鍵的尾數(shù)取模分片,則訂單主鍵為分片字段。SQL 中如果無(wú)分片字段,將執(zhí)行全路由,性能較差。除了對(duì)單分片字段的支持,Sharding-JDBC 也支持根據(jù)多個(gè)字段進(jìn)行分片。


          分片算法


          通過(guò)分片算法將數(shù)據(jù)分片,支持通過(guò)=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要應(yīng)用方開發(fā)者自行實(shí)現(xiàn),可實(shí)現(xiàn)的靈活度非常高。


          目前提供4種分片算法。由于分片算法和業(yè)務(wù)實(shí)現(xiàn)緊密相關(guān),因此并未提供內(nèi)置分片算法,而是通過(guò)分片策略將各種場(chǎng)景提煉出來(lái),提供更高層級(jí)的抽象,并提供接口讓應(yīng)用開發(fā)者自行實(shí)現(xiàn)分片算法。


          精確分片算法


          對(duì)應(yīng) PreciseShardingAlgorithm,用于處理使用單一鍵作為分片鍵的 = 與 IN 進(jìn)行分片的場(chǎng)景。需要配合 StandardShardingStrategy 使用。


          范圍分片算法


          對(duì)應(yīng) RangeShardingAlgorithm,用于處理使用單一鍵作為分片鍵的 BETWEEN AND、>、<、>=、<=進(jìn)行分片的場(chǎng)景。需要配合 StandardShardingStrategy 使用。


          復(fù)合分片算法


          對(duì)應(yīng) ComplexKeysShardingAlgorithm,用于處理使用多鍵作為分片鍵進(jìn)行分片的場(chǎng)景,包含多個(gè)分片鍵的邏輯較復(fù)雜,需要應(yīng)用開發(fā)者自行處理其中的復(fù)雜度。需要配合 ComplexShardingStrategy 使用。


          Hint分片算法


          對(duì)應(yīng) HintShardingAlgorithm,用于處理通過(guò)Hint指定分片值而非從SQL中提取分片值的場(chǎng)景。需要配合 HintShardingStrategy 使用。


          分片策略


          包含分片鍵和分片算法,由于分片算法的獨(dú)立性,將其獨(dú)立抽離。真正可用于分片操作的是分片鍵 + 分片算法,也就是分片策略。目前提供 5 種分片策略。


          標(biāo)準(zhǔn)分片策略


          對(duì)應(yīng) StandardShardingStrategy。提供對(duì) SQ L語(yǔ)句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。StandardShardingStrategy 只支持單分片鍵,提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 兩個(gè)分片算法。PreciseShardingAlgorithm 是必選的,用于處理 = 和 IN 的分片。RangeShardingAlgorithm 是可選的,用于處理 BETWEEN AND, >, <, >=, <=分片,如果不配置 RangeShardingAlgorithm,SQL 中的 BETWEEN AND 將按照全庫(kù)路由處理。


          復(fù)合分片策略


          對(duì)應(yīng) ComplexShardingStrategy。復(fù)合分片策略。提供對(duì) SQL 語(yǔ)句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。ComplexShardingStrategy 支持多分片鍵,由于多分片鍵之間的關(guān)系復(fù)雜,因此并未進(jìn)行過(guò)多的封裝,而是直接將分片鍵值組合以及分片操作符透?jìng)髦练制惴?,完全由?yīng)用開發(fā)者實(shí)現(xiàn),提供最大的靈活度。


          行表達(dá)式分片策略


          對(duì)應(yīng) InlineShardingStrategy。使用 Groovy 的表達(dá)式,提供對(duì) SQL 語(yǔ)句中的 = 和 IN的分片操作支持,只支持單分片鍵。對(duì)于簡(jiǎn)單的分片算法,可以通過(guò)簡(jiǎn)單的配置使用,從而避免繁瑣的Java代碼開發(fā),如: t_user_$->{u_id % 8} 表示 t_user 表根據(jù) u_id 模 8,而分成 8 張表,表名稱為 t_user_0 到 t_user_7。可以認(rèn)為是精確分片算法的簡(jiǎn)易實(shí)現(xiàn)


          Hint分片策略


          對(duì)應(yīng) HintShardingStrategy。通過(guò) Hint 指定分片值而非從 SQL 中提取分片值的方式進(jìn)行分片的策略。


          分布式主鍵


          用于在分布式環(huán)境下,生成全局唯一的id。Sharding-JDBC 提供了內(nèi)置的分布式主鍵生成器,例如 UUID、SNOWFLAKE。還抽離出分布式主鍵生成器的接口,方便用戶自行實(shí)現(xiàn)自定義的自增主鍵生成器。為了保證數(shù)據(jù)庫(kù)性能,主鍵id還必須趨勢(shì)遞增,避免造成頻繁的數(shù)據(jù)頁(yè)面分裂。


          讀寫分離


          提供一主多從的讀寫分離配置,可獨(dú)立使用,也可配合分庫(kù)分表使用。


          • 同一線程且同一數(shù)據(jù)庫(kù)連接內(nèi),如有寫入操作,以后的讀操作均從主庫(kù)讀取,用于保證數(shù)據(jù)一致性

          • 基于Hint的強(qiáng)制主庫(kù)路由。

          • 主從模型中,事務(wù)中讀寫均用主庫(kù)。


          執(zhí)行流程


          Sharding-JDBC 的原理總結(jié)起來(lái)很簡(jiǎn)單: 核心由 SQL解析 => 執(zhí)行器優(yōu)化 => SQL路由 => SQL改寫 => SQL執(zhí)行 => 結(jié)果歸并的流程組成。



          項(xiàng)目實(shí)戰(zhàn)


          spring-boot項(xiàng)目實(shí)戰(zhàn)


          引入依賴


          <dependency>
          ????<groupId>org.apache.shardingspheregroupId>
          ????<artifactId>sharding-jdbc-spring-boot-starterartifactId>
          ????<version>4.0.1version>
          dependency>


          數(shù)據(jù)源配置


          如果使用sharding-jdbc-spring-boot-starter, 并且數(shù)據(jù)源以及數(shù)據(jù)分片都使用shardingsphere進(jìn)行配置,對(duì)應(yīng)的數(shù)據(jù)源會(huì)自動(dòng)創(chuàng)建并注入到spring容器中。


          spring.shardingsphere.datasource.names=ds0,ds1

          spring.shardingsphere.datasource.ds0.type=org.apache.commons.dbcp.BasicDataSource
          spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
          spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/ds0
          spring.shardingsphere.datasource.ds0.username=root
          spring.shardingsphere.datasource.ds0.password=

          spring.shardingsphere.datasource.ds1.type=org.apache.commons.dbcp.BasicDataSource
          spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
          spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/ds1
          spring.shardingsphere.datasource.ds1.username=root
          spring.shardingsphere.datasource.ds1.password=

          # 其它分片配置


          但是在我們已有的項(xiàng)目中,數(shù)據(jù)源配置是單獨(dú)的。因此要禁用sharding-jdbc-spring-boot-starter里面的自動(dòng)裝配,而是參考源碼自己重寫數(shù)據(jù)源配置。需要在啟動(dòng)類上加上


          @SpringBootApplication(exclude?= {org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration.class})


          來(lái)排除。然后自定義配置類來(lái)裝配DataSource。


          @Configuration
          @Slf4j
          @EnableConfigurationProperties({
          ????????SpringBootShardingRuleConfigurationProperties.class,
          ????????SpringBootMasterSlaveRuleConfigurationProperties.class, SpringBootEncryptRuleConfigurationProperties.class, SpringBootPropertiesConfigurationProperties.class})
          @AutoConfigureBefore(DataSourceConfiguration.class)
          public?class?DataSourceConfig?implements?ApplicationContextAware?{

          ????@Autowired
          ????private?SpringBootShardingRuleConfigurationProperties shardingRule;

          ????@Autowired
          ????private?SpringBootPropertiesConfigurationProperties props;

          ????private?ApplicationContext applicationContext;

          ????@Bean("shardingDataSource")
          ????@Conditional(ShardingRuleCondition.class)
          ????public?DataSource shardingDataSource()?throws?SQLException {
          ????????// 獲取其它方式配置的數(shù)據(jù)源
          ????????Map beans = applicationContext.getBeansOfType(DruidDataSourceWrapper.class);
          ????????Map dataSourceMap = new?HashMap<>(4);
          ????????beans.forEach(dataSourceMap::put);
          ????????// 創(chuàng)建shardingDataSource
          ????????return?ShardingDataSourceFactory.createDataSource(dataSourceMap, new?ShardingRuleConfigurationYamlSwapper().swap(shardingRule), props.getProps());
          ????}

          ????@Bean
          ????public?SqlSessionFactory sqlSessionFactory()?throws?SQLException {
          ????????SqlSessionFactoryBean sqlSessionFactoryBean = new?SqlSessionFactoryBean();
          ????????// 將shardingDataSource設(shè)置到SqlSessionFactory中
          ????????sqlSessionFactoryBean.setDataSource(shardingDataSource());
          ????????// 其它設(shè)置
          ????????return?sqlSessionFactoryBean.getObject();
          ????}
          }


          分布式id生成器配置


          Sharding-JDBC提供了UUID、SNOWFLAKE生成器,還支持用戶實(shí)現(xiàn)自定義id生成器。比如可以實(shí)現(xiàn)了type為SEQ的分布式id生成器,調(diào)用統(tǒng)一的分布式id服務(wù)獲取id。


          @Data
          public?class?SeqShardingKeyGenerator?implements?ShardingKeyGenerator?{

          ????private?Properties properties = new?Properties();

          ????@Override
          ????public?String getType()?{
          ????????return?"SEQ";
          ????}

          ????@Override
          ????public?synchronized?Comparable generateKey() {
          ???????// 獲取分布式id邏輯
          ????}
          }


          由于擴(kuò)展ShardingKeyGenerator是通過(guò)JDK的serviceloader的SPI機(jī)制實(shí)現(xiàn)的,因此還需要在resources/META-INF/services目錄下配置org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator文件。 文件內(nèi)容就是SeqShardingKeyGenerator類的全路徑名。這樣使用的時(shí)候,指定分布式主鍵生成器的type為SEQ就好了。


          至此,Sharding-JDBC就整合進(jìn)spring-boot項(xiàng)目中了,后面就可以進(jìn)行數(shù)據(jù)分片相關(guān)的配置了。


          數(shù)據(jù)分片實(shí)戰(zhàn)


          如果項(xiàng)目初期就能預(yù)估出表的數(shù)據(jù)量級(jí),當(dāng)然可以一開始就按照這個(gè)預(yù)估值進(jìn)行分庫(kù)分表處理。但是大多數(shù)情況下,我們一開始并不能準(zhǔn)備預(yù)估出數(shù)量級(jí)。這時(shí)候通常的做法是:


          1. 線上數(shù)據(jù)某張表查詢性能開始下降,排查下來(lái)是因?yàn)閿?shù)據(jù)量過(guò)大導(dǎo)致的。

          2. 根據(jù)歷史數(shù)據(jù)量預(yù)估出未來(lái)的數(shù)據(jù)量級(jí),并結(jié)合具體業(yè)務(wù)場(chǎng)景確定分庫(kù)分表策略。

          3. 自動(dòng)分庫(kù)分表代碼實(shí)現(xiàn)。


          下面就以一個(gè)具體事例,闡述具體數(shù)據(jù)分片實(shí)戰(zhàn)。比如有張表數(shù)據(jù)結(jié)構(gòu)如下:


          CREATE?TABLE?`hc_question_reply_record`?(
          ??`id`?bigint?NOT?NULL?AUTO_INCREMENT COMMENT?'自增ID',
          ??`reply_text`?varchar(500) NOT?NULL?DEFAULT?''?COMMENT?'回復(fù)內(nèi)容',
          ??`reply_wheel_time`?datetime NOT?NULL?DEFAULT?CURRENT_TIMESTAMP?COMMENT?'回復(fù)時(shí)間',

          ??`ctime`?datetime NOT?NULL?DEFAULT?CURRENT_TIMESTAMP?COMMENT?'創(chuàng)建時(shí)間',
          ??`mtime`?datetime NOT?NULL?DEFAULT?CURRENT_TIMESTAMP?ON?UPDATE?CURRENT_TIMESTAMP?COMMENT?'更新時(shí)間',
          ??PRIMARY KEY?(`id`),
          ??INDEX?`idx_reply_wheel_time`?(`reply_wheel_time`)
          ) ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
          ??COMMENT='回復(fù)明細(xì)記錄';


          分片方案確定


          先查詢目前目標(biāo)表月新增趨勢(shì):


          SELECT?count(*), date_format(ctime, '%Y-%m') AS?`日期`
          FROM?hc_question_reply_record
          GROUP?BY?date_format(ctime, '%Y-%m');



          目前月新增在180w左右,預(yù)估未來(lái)達(dá)到300w(基本以2倍計(jì)算)以上。期望單表數(shù)據(jù)量不超過(guò)1000w,可使用reply_wheel_time作為分片鍵按季度歸檔。


          分片配置spring:

          spring:
          ??# sharing-jdbc配置
          ??shardingsphere:
          ????# 數(shù)據(jù)源名稱
          ????datasource:
          ??????names:?defaultDataSource,slaveDataSource
          ????sharding:
          ??????# 主從節(jié)點(diǎn)配置
          ??????master-slave-rules:
          ????????defaultDataSource:
          ??????????# maser數(shù)據(jù)源
          ??????????master-data-source-name:?defaultDataSource
          ??????????# slave數(shù)據(jù)源
          ??????????slave-data-source-names:?slaveDataSource
          ??????tables:
          ????????# hc_question_reply_record 分庫(kù)分表配置
          ????????hc_question_reply_record:
          ??????????# 真實(shí)數(shù)據(jù)節(jié)點(diǎn) hc_question_reply_record_2020_q1
          ??????????actual-data-nodes:?defaultDataSource.hc_question_reply_record_$->{2020..2025}_q$->{1..4}
          ??????????# 表分片策略
          ??????????table-strategy:
          ????????????standard:
          ??????????????# 分片鍵
          ??????????????sharding-column:?reply_wheel_time
          ??????????????# 精確分片算法 全路徑名
          ??????????????preciseAlgorithmClassName:?com.xx.QuestionRecordPreciseShardingAlgorithm
          ??????????????# 范圍分片算法,用于BETWEEN,可選。。該類需實(shí)現(xiàn)RangeShardingAlgorithm接口并提供無(wú)參數(shù)的構(gòu)造器
          ??????????????rangeAlgorithmClassName:?com.xx.QuestionRecordRangeShardingAlgorithm

          ??????# 默認(rèn)分布式id生成器
          ??????default-key-generator:
          ????????type:?SEQ
          ????????column:?id


          分片算法實(shí)現(xiàn)


          精確分片算法:


          public?class?QuestionRecordPreciseShardingAlgorithm?implements?PreciseShardingAlgorithm<Date> {
          ??/**
          ???* Sharding.
          ???*
          ???* @param?availableTargetNames available data sources or tables's names
          ???* @param?shardingValue sharding value
          ???* @return?sharding result for data source or table's name
          ???*/

          ??@Override
          ??public?String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue)?{
          ??????return?ShardingUtils.quarterPreciseSharding(availableTargetNames, shardingValue);
          ??}
          }


          范圍分片算法:


          public?class?QuestionRecordRangeShardingAlgorithm?implements?RangeShardingAlgorithm<Date> {

          ??/**
          ???* Sharding.
          ???*
          ???* @param?availableTargetNames available data sources or tables's names
          ???* @param?shardingValue sharding value
          ???* @return?sharding results for data sources or tables's names
          ???*/

          ??@Override
          ??public?Collection doSharding(Collection availableTargetNames, RangeShardingValue shardingValue)?{
          ??????return?ShardingUtils.quarterRangeSharding(availableTargetNames, shardingValue);
          ??}
          }


          具體分片實(shí)現(xiàn)邏輯:


          @UtilityClass
          public?class?ShardingUtils?{
          ????public?static?final?String QUARTER_SHARDING_PATTERN = "%s_%d_q%d";

          ????/**
          ????* logicTableName_{year}_q{quarter}
          ????* 按季度范圍分片
          ????* @param?availableTargetNames 可用的真實(shí)表集合
          ????* @param?shardingValue 分片值
          ????* @return
          ????*/

          ????public?Collection quarterRangeSharding(Collection availableTargetNames, RangeShardingValue shardingValue)?{
          ????????// 這里就是根據(jù)范圍查詢條件,篩選出匹配的真實(shí)表集合
          ????}

          ????/**
          ????* logicTableName_{year}_q{quarter}
          ????* 按季度精確分片
          ????* @param?availableTargetNames 可用的真實(shí)表集合
          ????* @param?shardingValue 分片值
          ????* @return
          ????*/

          ????public?static?String quarterPreciseSharding(Collection availableTargetNames, PreciseShardingValue shardingValue)?{
          ????????// 這里就是根據(jù)等值查詢條件,計(jì)算出匹配的真實(shí)表
          ????}
          }


          到這里,針對(duì)hc_question_reply_record表,使用reply_wheel_time作為分片鍵,按照季度分片的處理就完成了。還有一點(diǎn)要注意的就是,分庫(kù)分表之后,查詢的時(shí)候最好都帶上分片鍵作為查詢條件,否則就會(huì)使用全庫(kù)路由,性能很低。還有就是Sharing-JDBC對(duì)mysql的全文索引支持的不是很好,項(xiàng)目有使用到的地方也要注意一下??偨Y(jié)來(lái)說(shuō)整個(gè)過(guò)程還是比較簡(jiǎn)單的,后續(xù)碰到其它業(yè)務(wù)場(chǎng)景,相信大家按照這個(gè)思路肯定都能解決的。


          原文鏈接:cnblogs.com/chentianming/p/13414237.html




          瀏覽 57
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  手机看片亚洲 | 久久三级精品视频 | 国产夜间福利视频 | 丁香性爱在线视频 | 天堂网视频 |