數(shù)據(jù)分表小結(jié)-創(chuàng)新互聯(lián)

數(shù)據(jù)分表小結(jié)

標(biāo)簽: sharding 數(shù)據(jù)表拆分

成都創(chuàng)新互聯(lián)專注于尼金平網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供尼金平營銷型網(wǎng)站建設(shè),尼金平網(wǎng)站制作、尼金平網(wǎng)頁設(shè)計(jì)、尼金平網(wǎng)站官網(wǎng)定制、微信小程序定制開發(fā)服務(wù),打造尼金平網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供尼金平網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
  • 背景
  • 分庫、分表帶來的后遺癥
  • 分表策略
  • 一些注意事項(xiàng)

背景

最近一段時(shí)間內(nèi)結(jié)束了數(shù)據(jù)庫表拆分項(xiàng)目,這里做個(gè)簡(jiǎn)單的小結(jié)。

本次拆分主要包括訂單和優(yōu)惠券兩大塊,這兩塊都是覆蓋全集團(tuán)所有分子公司所有業(yè)務(wù)線。隨著公司的業(yè)務(wù)飛速發(fā)展,不管是存儲(chǔ)的要求,還是寫入、讀取的性都基本上到了警戒水位。

訂單是交易的核心,優(yōu)惠券是營銷的核心,這兩塊基本上是整個(gè)平臺(tái)的正向最核心部分。為了支持未來三到五年的快速發(fā)展,我們需要對(duì)數(shù)據(jù)進(jìn)行拆分。

數(shù)據(jù)庫表拆分業(yè)內(nèi)已經(jīng)有很多成熟方案,已經(jīng)不是什么高深的技術(shù),基本上是純工程化的流程,但是能有機(jī)會(huì)進(jìn)行實(shí)際的操刀一把機(jī)會(huì)還是難得,所以非常有必要做個(gè)總結(jié)。

由于分庫分表包含的技術(shù)選型和方式方法多種多樣,這篇文章不是羅列和匯總介紹各種方法,而是總結(jié)我們?cè)趯?shí)施分庫分表過程中的一些經(jīng)驗(yàn)。

根據(jù)業(yè)務(wù)場(chǎng)景判斷,我們主要是做水平拆分,做邏輯 DB 拆分,考慮到未來數(shù)據(jù)庫寫入瓶頸可以將一組 sharding 表直接遷移進(jìn)分庫中。

分庫、分表帶來的后遺癥

分庫、分表會(huì)帶來很多的后遺癥,會(huì)使整個(gè)系統(tǒng)架構(gòu)變的復(fù)雜。分的好與不好最關(guān)鍵就是如何尋找那個(gè) sharding key,如果這個(gè) sharding key 剛好是業(yè)務(wù)維度上的分界線就會(huì)直接提升性能和改善復(fù)雜度,否則就會(huì)有各種腳手架來支撐,系統(tǒng)也就會(huì)變得復(fù)雜。

比如訂單系統(tǒng)中的用戶ID、訂單type、商家ID、渠道ID,優(yōu)惠券系統(tǒng)中的批次ID、渠道ID、機(jī)構(gòu)ID 等,這些都是潛在的 sharding key。

如果剛好有這么一個(gè) sharding key 存在后面處理路由(routing)就會(huì)很方便,否則就需要一些大而全的索引表來處理 OLAP 的查詢。

一旦 sharding 之后首先要面對(duì)的問題就是查詢時(shí)排序分頁問題。

歸并排序

原來在一個(gè)數(shù)據(jù)庫表中處理排序分頁是比較方便的,sharding 之后就會(huì)存在多個(gè)數(shù)據(jù)源,這里我們將多個(gè)數(shù)據(jù)源統(tǒng)稱為分片。

想要實(shí)現(xiàn)多分片排序分頁就需要將各個(gè)片的數(shù)據(jù)都匯集起來進(jìn)行排序,就需要用到 歸并排序 算法。這些數(shù)據(jù)在各個(gè)分片中可以做到有序的(輸出有序),但是整體上是無序的。

我們看個(gè)簡(jiǎn)單的例子:

shard node 1: {1、3、5、7、9}
shard node 2: {2、4、6、8、10}

這是做 奇偶 sharding 的兩個(gè)分片,我們假設(shè)分頁參數(shù)設(shè)置為每頁4條,當(dāng)前第1頁,參數(shù)如下:

pageParameter:pageSize:4、currentPage:1

最樂觀情況下我們需要分別讀取兩個(gè)分片節(jié)點(diǎn)中的前兩條:

shard node 1: {1、3}
shard node 2: {2、4}

排序完剛好是 {1、2、3、4},但是這種場(chǎng)景基本上不太可能出現(xiàn),假設(shè)如下分片節(jié)點(diǎn)數(shù)據(jù):

shard node 1: {7、9、11、13、15}
shard node 2: {2、4、6、8、10、12、14}

我們還是按照讀取每個(gè)節(jié)點(diǎn)前兩條肯定是錯(cuò)誤的,因?yàn)樽畋^情況下也是最真實(shí)的情況就是排序完后所有的數(shù)據(jù)都來自一個(gè)分片。所以我們需要讀取每個(gè)節(jié)點(diǎn)的 pageSize 大小的數(shù)據(jù)出來才有可能保證數(shù)據(jù)的正確性。

這個(gè)例子只是假設(shè)我們的查詢條件輸出的數(shù)據(jù)剛好是均等的,真實(shí)的情況一定是各種各樣的查詢條件篩選出來的數(shù)據(jù)集合,此時(shí)這個(gè)數(shù)據(jù)一定不是這樣的排列方式,最真實(shí)的就是最后者這種結(jié)構(gòu)。

我們以此類推,如果我們的 currentPage:1000 那么會(huì)出現(xiàn)什么問題,我們需要每個(gè) sharding node 讀取 4000(1000*4=4000) 條數(shù)據(jù)出來排序,因?yàn)樽畋^情況下有可能所有的數(shù)據(jù)均來自一個(gè) sharding node 。

這樣無限制的翻頁下去,處理排序分頁的機(jī)器肯定會(huì)內(nèi)存撐爆,就算不撐爆一定會(huì)觸發(fā)性能瓶頸。

這個(gè)簡(jiǎn)單的例子用來說明分片之后,排序分頁帶來的現(xiàn)實(shí)問題,這也有助于我們理解分布式系統(tǒng)在做多節(jié)點(diǎn)排序分頁時(shí)為什么有大分頁限制。

深分頁性能問題-改變查詢條件重新分頁

一個(gè)龐大的數(shù)據(jù)集會(huì)通過多種方式進(jìn)行數(shù)據(jù)拆分,按機(jī)構(gòu)、按時(shí)間、按渠道等等,拆分在不同的數(shù)據(jù)源中。一般的深分頁問題我們可以通過改變查詢條件來平滑解決,但是這種方案并不能解決所有的業(yè)務(wù)場(chǎng)景。

比如,我們有一個(gè)訂單列表,從C端用戶來查詢自己的訂單列表數(shù)據(jù)量不會(huì)很大,但是運(yùn)營后臺(tái)系統(tǒng)可能面對(duì)全平臺(tái)的所有訂單數(shù)據(jù)量,所以數(shù)據(jù)量會(huì)很大。

改變查詢條件有兩種方式,一種是顯示的設(shè)置,盡量縮小查詢范圍,這種設(shè)置一般都會(huì)優(yōu)先考慮,比如時(shí)間范圍、支付狀態(tài)、配送狀態(tài)等等,通過多個(gè)疊加條件就可以橫豎過濾出很小一部分?jǐn)?shù)據(jù)集。

那么第二種條件為隱式設(shè)置。比如訂單列表通常是按照訂單創(chuàng)建時(shí)間來排序,那么當(dāng)翻頁到限制的條件時(shí),我們可以改變這個(gè)時(shí)間。

sharding node 1:
orderID     createDateTime
100000      2018-01-10 10:10:10
200000      2018-01-10 10:10:11
300000      2018-01-10 10:10:12
400000      2018-01-10 10:10:13
500000      2018-01-20 10:10:10
600000      2018-01-20 10:10:11
700000      2018-01-20 10:10:12
sharding node 2:
orderID     createDateTime
110000      2018-01-11 10:10:10
220000      2018-01-11 10:10:11
320000      2018-01-11 10:10:12
420000      2018-01-11 10:10:13
520000      2018-01-21 10:10:10
620000      2018-01-21 10:10:11
720000      2018-01-21 10:10:12

我們假設(shè)上面是一個(gè)訂單列表,orderID 訂單號(hào)大家就不要在意順序性了。因?yàn)?sharding 之后所有的 orderID 都會(huì)由發(fā)號(hào)器統(tǒng)一發(fā)放,多個(gè)集群多個(gè)消費(fèi)者同時(shí)獲取,但是創(chuàng)建訂單的速度是不一樣的,所以順序性已經(jīng)不存在了。

上面的兩個(gè) sharding node 基本上訂單號(hào)是交叉的,如果按照時(shí)間排序 node 1node 2 是要交替獲取數(shù)據(jù)。

比如我們的查詢條件和分頁參數(shù):

where createDateTime>'2018-01-11 00:00:00'
pageParameter:pageSize:5、currentPage:1

獲取的結(jié)果集為:

orderID     createDateTime
100000      2018-01-10 10:10:10
200000      2018-01-10 10:10:11
300000      2018-01-10 10:10:12
400000      2018-01-10 10:10:13
110000      2018-01-11 10:10:10

前面 4 條記錄來自 node 1 后面 1 條數(shù)據(jù)來自 node 2 ,整個(gè)排序集合為:

sharding node 1:
orderID     createDateTime
100000      2018-01-10 10:10:10
200000      2018-01-10 10:10:11
300000      2018-01-10 10:10:12
400000      2018-01-10 10:10:13
500000      2018-01-20 10:10:10

sharding node 2:
orderID     createDateTime
110000      2018-01-11 10:10:10
220000      2018-01-11 10:10:11
320000      2018-01-11 10:10:12
420000      2018-01-11 10:10:13
520000      2018-01-21 10:10:10

按照這樣一直翻頁下去每翻頁一次就需要在 node 1 、node 2 多獲取 5 條數(shù)據(jù)。這里我們可以通過修改查詢條件來讓整個(gè)翻頁變?yōu)橹匦虏樵儭?/p>

where createDateTime>'2018-01-11 10:10:13'

因?yàn)槲覀兛梢源_定在 ‘2018-01-11 10:10:13’ 時(shí)間之前所有的數(shù)據(jù)都已經(jīng)查詢過,但是為什么時(shí)間不是從 ‘2018-01-21 10:10:10’ 開始,因?yàn)槲覀円紤]并發(fā)情況,在 1s 內(nèi)會(huì)有多個(gè)訂單進(jìn)來。

這種方式是實(shí)現(xiàn)最簡(jiǎn)單,不需要借助外部的計(jì)算來支撐。這種方式有一個(gè)問題就是要想重新計(jì)算分頁的時(shí)候不丟失數(shù)據(jù)就需要保留原來一條數(shù)據(jù),這樣才能知道開始的時(shí)間在哪里,這樣就會(huì)在下次的分頁中看到這條時(shí)間。但是從真實(shí)的深分頁場(chǎng)景來看也可以忽略,因?yàn)楹苌儆腥藭?huì)一頁一頁一直到翻到500頁,而是直接跳到最后幾頁,這個(gè)時(shí)候就不存在那個(gè)問題。

如果非要精準(zhǔn)控制這個(gè)偏差就需要記住區(qū)間,或者用其他方式來實(shí)現(xiàn)了,比如全量查詢表、sharding 索引表、大下單 tps 值之類的,用來輔助計(jì)算。

(可以利用數(shù)據(jù)同步中間件建立單表多級(jí)索引、多表多維度索引來輔助計(jì)算。我們使用到的數(shù)據(jù)同步中間件有 datax、yugong、otter、canal 可以解決全量、增量同步問題)。

分表策略

分表有多種方式,mod、rang、presharding自定義路由,每種方式都有一定的側(cè)重。

我們主要使用 mod + presharding 的方式,這種方式帶來的大的一個(gè)問題就是后期的節(jié)點(diǎn)變動(dòng)數(shù)據(jù)遷移問題,可以通過參考一致性 hash 算法的虛擬節(jié)點(diǎn)來解決。

數(shù)據(jù)表拆分和 cache sharding 有一些區(qū)別,cache 能接受 cache miss ,通過被動(dòng)緩存的方式可以維護(hù)起 cache 數(shù)據(jù)。但是數(shù)據(jù)庫不存在 select miss 這種場(chǎng)景。

cache sharding 場(chǎng)景下一致性 hash 可以用來消除減少、增加 sharding node 時(shí)相鄰分片壓力問題。 但是數(shù)據(jù)庫一旦出現(xiàn)數(shù)據(jù)遷移一定是不能接受數(shù)據(jù)查詢不出來的。所以我們?yōu)榱藢頂?shù)據(jù)的平滑遷移,做了一個(gè) 虛擬節(jié)點(diǎn) + 真實(shí)節(jié)點(diǎn) mapping 。

physics node : node 1 node 2 node 3 node 4
virtual node : node 1 node 2 node 3.....node 20
node mapping :
virtual node 1 ~ node 5 {physics node 1}
virtual node 6 ~ node 10 {physics node 2}
virtual node 11 ~ node 15 {physics node 3}
virtual node 16 ~ node 20 {physics node 4}

為了減少將來遷移數(shù)據(jù)時(shí) rehash 的成本和延遲的開銷,將 hash 后的值保存在表里,將來遷移直接查詢出來快速導(dǎo)入。

hash 片 2 的次方問題

在我們熟悉的 hashmap 里,為了減少?zèng)_突和提供一定的性能將 hash 桶的大小設(shè)置成 2 的 n 次方,然后采用 hash&(legnth-1) 位與的方式計(jì)算,這樣主要是大師們發(fā)現(xiàn) 2 的 n 次方的二進(jìn)制除了高位是 0 之外所有地位都是 1,通過位與可以快速反轉(zhuǎn)二進(jìn)制然后地位加 1 就是最終的值。

我們?cè)谧鰯?shù)據(jù)庫 sharding 的時(shí)候不需要參考這一原則,這一原則主要是為了程序內(nèi)部 hash 表使用,外部我們本來就是要 hash mod 確定 sharding node 。

通過 mod 取模的方式會(huì)出現(xiàn)不均勻問題,在此基礎(chǔ)上可以做個(gè) 自定義奇偶路由,這樣可以均勻兩邊的數(shù)據(jù)。

一些注意事項(xiàng)

1.在現(xiàn)有項(xiàng)目中集成 sharding-JDBC 有一些小問題,sharding-jdbc 不支持批量插入,如果項(xiàng)目中已經(jīng)使用了大量的批量插入語句就需要改造,或者使用 輔助hash計(jì)算物理表名,在批量插入。

2.原有項(xiàng)目數(shù)據(jù)層使用 Druid + MyBatis,集成了 sharding-JDBC 之后 sharding-JDBC包裝了 Druid ,所以一些 sharding-JDBC 不支持的sql語句基本就過不去了。

3.使用 springboot 集成 sharding-JDBC 的時(shí)候,在bean加載的時(shí)候我需要設(shè)置 IncrementIdGenerator ,但是出現(xiàn)classloader問題。

IncrementIdGenerator incrementIdGenerator = this.getIncrementIdGenerator(dataSource);

ShardingRule shardingRule = shardingRuleConfiguration.build(dataSourceMap);
((IdGenerator) shardingRule.getDefaultKeyGenerator()).setIncrementIdGenerator(incrementIdGenerator);
private IncrementIdGenerator getIncrementIdGenerator(DataSource druidDataSource) {
...
    }

后來發(fā)現(xiàn) springboot的類加載器使用的是 restartclassloader,所以導(dǎo)致轉(zhuǎn)換一直失敗。只要去掉 spring-boot-devtools package即可,restartclassloader 是為了熱啟動(dòng)。

4.dao.xml 逆向工程問題,我們使用的很多數(shù)據(jù)庫表mybatis生成工具生成的時(shí)候都是物理表名,一旦我們使用了sharding-JDCB之后都是用的邏輯表名,所以生成工具需要提供選項(xiàng)來設(shè)置邏輯表名。

5.為 mybatis 提供的 SqlSessionFactory 需要在Druid的基礎(chǔ)上用shading-JDCB包裝下。

6.sharding-JDBC DefaultkeyGenerator 默認(rèn)采用是 snowflake 算法,但是我們不能直接用我們需要根據(jù) datacenterid-workerid 自己配合zookeeper來設(shè)置 workerId 段。
(snowflake workId 10 bit 十進(jìn)制 1023,dataCenterId 5 bit 十進(jìn)制 31 、WorkId 5 bit 十進(jìn)制 31)

7.由于我們使用的是 mysql com.mysql.jdbc.ReplicationDriver 自帶的實(shí)現(xiàn)讀寫分離,所以處理讀寫分離會(huì)方便很多。如果不是使用的這種就需要手動(dòng)設(shè)置 Datasource Hint 來處理。

8.在使用 mybatis dao mapper 的時(shí)候需要多份邏輯表,因?yàn)橛行?shù)據(jù)源數(shù)據(jù)表是不需要走sharding的,自定義shardingStragety 來處理分支邏輯。

9 全局id幾種方法
9.1 如果使用 zookeeper 來做分布式ID,就要注意 session expired 可能會(huì)存在重復(fù) workid 問題,加鎖或者接受一定程度的并行(有序列號(hào)保證一段時(shí)間空間)。

9.2.采用集中發(fā)號(hào)器服務(wù),在主DB中采用預(yù)生成表+incrment 插件(經(jīng)典取號(hào)器實(shí)現(xiàn),innodb 存儲(chǔ)引擎中的 TRX_SYS_TRX_ID_STORE 事務(wù)號(hào)也是這種方式)

9.3.定長(zhǎng)發(fā)號(hào)器、業(yè)務(wù)規(guī)則發(fā)號(hào)器,這種需要業(yè)務(wù)上下文的發(fā)號(hào)器實(shí)現(xiàn)都需要預(yù)先配置,然后每次請(qǐng)求帶上獲取上下文來說明獲取業(yè)務(wù)類型

10.在項(xiàng)目中有些地方使用了自增id排序,數(shù)據(jù)表拆分之后就需要進(jìn)行改造,因?yàn)镮D大小順序已經(jīng)不存在了。根據(jù)數(shù)據(jù)的最新排序時(shí)使用了id排序需要改造成用時(shí)間字段排序。

作者:王清培 (滬江集團(tuán)資深JAVA架構(gòu)師)

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。

分享標(biāo)題:數(shù)據(jù)分表小結(jié)-創(chuàng)新互聯(lián)
網(wǎng)站路徑:http://muchs.cn/article30/dchpso.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站、App開發(fā)、網(wǎng)站內(nèi)鏈、營銷型網(wǎng)站建設(shè)、云服務(wù)器網(wǎng)站策劃

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)