國(guó)際財(cái)務(wù)系統(tǒng)基于ShardingSphere的數(shù)據(jù)分片和一主多從實(shí)踐
作者:京東物流 張廣治
1 背景
傳統(tǒng)的將數(shù)據(jù)集中存儲(chǔ)至單一數(shù)據(jù)節(jié)點(diǎn)的解決方案,在性能和可用性方面已經(jīng)難于滿足海量數(shù)據(jù)的場(chǎng)景,系統(tǒng)最大的瓶頸在于單個(gè)節(jié)點(diǎn)讀寫性能,許多的資源受到單機(jī)的限制,例如連接數(shù)、網(wǎng)絡(luò)IO、磁盤IO等,從而導(dǎo)致它的并發(fā)能力不高,對(duì)于高并發(fā)的要求不滿足。
每到月初國(guó)際財(cái)務(wù)系統(tǒng)壓力巨大,因?yàn)樵鲁跤写罅垦a(bǔ)全任務(wù),重算、計(jì)算任務(wù)、賬單生成任務(wù)、推送集成等都要趕在月初1號(hào)完成,顯然我們需要一個(gè)支持高性能、高并發(fā)的方案來解決我們的問題。
2 我們的目標(biāo)
- 支持每月接單量一億以上。
- 一億的單量補(bǔ)全,計(jì)算,生成賬單在24小時(shí)內(nèi)完成(支持前面說的月初大數(shù)據(jù)量計(jì)算的場(chǎng)景)
3 數(shù)據(jù)分配規(guī)則
現(xiàn)實(shí)世界中,每一個(gè)資源都有其提供服務(wù)能力的上限,當(dāng)某一個(gè)資源達(dá)到最大上限后就無法及時(shí)處理溢出的需求,這樣就需要使用多個(gè)資源同時(shí)提供服務(wù)來滿足大量的任務(wù)。當(dāng)使用了多個(gè)資源來提供服務(wù)時(shí),最為關(guān)鍵的是如何讓每一個(gè)資源比較均勻的承擔(dān)壓力,而不至于其中的某些資源壓力過大,所以分配規(guī)則就變得非常重要。
制定分配規(guī)則:要根據(jù)查詢和存儲(chǔ)的場(chǎng)景,一般按照類型、時(shí)間、城市、區(qū)域等作為分片鍵。
財(cái)務(wù)系統(tǒng)的租戶以業(yè)務(wù)線為單位,缺點(diǎn)為拆分的粒度太大,不能實(shí)現(xiàn)打散數(shù)據(jù)的目的,所以不適合做為分片鍵,事件定義作為分片鍵,缺點(diǎn)是非常不均勻,目前2C進(jìn)口清關(guān),一個(gè)事件,每月有一千多萬數(shù)據(jù),鯤鵬的事件,每月單量很少,如果按照事件定義拆分,會(huì)導(dǎo)致數(shù)據(jù)極度傾斜。
目前最適合作為分片鍵的就是時(shí)間,因?yàn)橄到y(tǒng)中計(jì)算,賬單,匯總,都是基于時(shí)間的,所以時(shí)間非常適合做分片鍵,適合使用月、周、作為Range的周期。目前使用的就是時(shí)間分區(qū),但只按照時(shí)間分區(qū)顯然已經(jīng)不能滿足我們的需求了。
經(jīng)過篩選,理論上最適合的分區(qū)鍵就剩下時(shí)間和收付款對(duì)象了。
最終我們決定使用收付款對(duì)象分庫,時(shí)間作為表分區(qū)。
數(shù)據(jù)拆分前結(jié)構(gòu)(圖一):
數(shù)據(jù)水平拆分后結(jié)構(gòu)(圖二):
分配規(guī)則
(payer.toUpperCase()+"_"+payee.toUpperCase()).hashCode().abs()%128
收款對(duì)象大寫加分隔符加付款對(duì)象大寫,取HASH值的絕對(duì)值模分庫數(shù)量
重要:payer和payee字母統(tǒng)一大寫,因?yàn)榇笮懖唤y(tǒng)一,會(huì)導(dǎo)致HASH值不一致,最終導(dǎo)致路由到不同的庫。
4 讀寫分離一主多從
4.1 ShardingSphere對(duì)讀寫分離的解釋
對(duì)于同一時(shí)刻有大量并發(fā)讀操作和較少寫操作類型的數(shù)據(jù)來說,將數(shù)據(jù)庫拆分為主庫和從庫,主庫負(fù)責(zé)處理事務(wù)性的增刪改操作,從庫負(fù)責(zé)處理查詢操作,能夠有效的避免由數(shù)據(jù)更新導(dǎo)致的行鎖,使得整個(gè)系統(tǒng)的查詢性能得到極大的改善。
通過一主多從的配置方式,可以將查詢請(qǐng)求均勻的分散到多個(gè)數(shù)據(jù)副本,能夠進(jìn)一步的提升系統(tǒng)的處理能力。 使用多主多從的方式,不但能夠提升系統(tǒng)的吞吐量,還能夠提升系統(tǒng)的可用性,可以達(dá)到在任何一個(gè)數(shù)據(jù)庫宕機(jī),甚至磁盤物理損壞的情況下仍然不影響系統(tǒng)的正常運(yùn)行。
把數(shù)據(jù)量大的大表進(jìn)行數(shù)據(jù)分片,其余大量并發(fā)讀操作且寫入小的數(shù)據(jù)進(jìn)行讀寫分離,如(圖三):
左側(cè)為主從結(jié)構(gòu),右側(cè)為數(shù)據(jù)分片
4.2 讀寫分離+數(shù)據(jù)分片實(shí)戰(zhàn)
當(dāng)我們實(shí)際使用sharding進(jìn)行讀寫分離+數(shù)據(jù)分片時(shí)遇到了一個(gè)很大的問題,官網(wǎng)文檔中的實(shí)現(xiàn)方式只適合分庫和從庫在一起時(shí)的場(chǎng)景如(圖四)
而我們的場(chǎng)景為(圖三)所示,從庫和分庫時(shí)徹底分開的,參考官網(wǎng)的實(shí)現(xiàn)方法如下:
官網(wǎng)給出的讀寫分離+數(shù)據(jù)分片方案不能配置
spring.shardingsphere.sharding.default-data-source-name默認(rèn)數(shù)據(jù)源,如果配置了,所有讀操作將全部指向主庫,無法達(dá)到讀寫分離的目的。
當(dāng)我們困擾在讀從庫的查詢會(huì)被輪詢到分庫中,我們實(shí)際的場(chǎng)景從庫和分庫是分離的,分庫中根本就不存在從庫中的表。此問題困擾了我近兩天的時(shí)間,我閱讀源碼發(fā)現(xiàn)
spring.shardingsphere.sharding.default-data-source-name可以被賦值一個(gè)DataNodeGroup,不僅僅支持配置datasourceName,sharding源碼如下圖:
由此spring.shardingsphere.sharding.default-data-source-name配置為讀寫分離的groupname1,問題解決
從庫和分庫不在一起的場(chǎng)景下,讀寫分離+數(shù)據(jù)分配的配置如下:
可以看到讀操作可以被均勻的路由到slave0、slave1中,分片的讀會(huì)被分配到ds0,ds1中如下圖:
4.3 實(shí)現(xiàn)自己的讀寫分離負(fù)載均衡算法
Sharding提供了SPI形式的接口
org.apache.shardingsphere.spi.masterslave.MasterSlaveLoadBalanceAlgorithm實(shí)現(xiàn)讀寫分離多個(gè)從的具體負(fù)載均衡規(guī)則,代碼如下:
RoundRobinMasterSlaveLoadBalanceAlgorithm 實(shí)現(xiàn)為所有從輪詢負(fù)載
RandomMasterSlaveLoadBalanceAlgorithm 實(shí)現(xiàn)為所有從隨機(jī)負(fù)載均衡
4.4 關(guān)于某些場(chǎng)景下必須讀主庫的解決方案
某些場(chǎng)景比如分布式場(chǎng)景下寫入馬上讀取的場(chǎng)景,可以使用hint方式進(jìn)行強(qiáng)制讀取主庫,Sharding源碼使用ThreadLocal實(shí)現(xiàn)強(qiáng)制路由標(biāo)記。
下面封裝了一個(gè)注解可以直接使用,代碼如下:
使用時(shí)方法上打SeekMaster注解即可,方法下的所有讀操作將自動(dòng)路由到主庫中,方法外的所有查詢還是讀取從庫,如下圖:
4.5 關(guān)于官網(wǎng)對(duì)讀寫分離描述不夠明確的補(bǔ)充說明
版本4.1.1
經(jīng)實(shí)踐補(bǔ)充說明為:
同一線程且同一數(shù)據(jù)庫連接且一個(gè)事務(wù)中,如有寫入操作,以后的讀操作均從主庫讀取,只限存在寫入的表,沒有寫入的表,事務(wù)中的查詢會(huì)繼續(xù)路由至從庫中,用于保證數(shù)據(jù)一致性。
5 關(guān)于分庫的JOIN操作
方法1
使用default-data-source-name配置默認(rèn)庫,即沒有配置數(shù)據(jù)分片策略的表都會(huì)使用默認(rèn)庫。默認(rèn)庫中表禁止與拆分表進(jìn)行JOIN操作,此處需要做一些改造,目前系統(tǒng)有一些JOIN操作。(推薦使用此方法)
方法2
使用全局表,廣播表,讓128個(gè)庫中冗余基礎(chǔ)庫中的表,并實(shí)時(shí)改變。
方法3
分庫表中冗余需要JOIN表中的字段,可以解決JOIN問題,此方案單個(gè)表字段會(huì)增加。
6 分布式事務(wù)
6.1 XA事務(wù)管理器參數(shù)配置
XA是由X/Open組織提出的分布式事務(wù)的規(guī)范。 XA規(guī)范主要定義了(全局)事務(wù)管理器(TM)和(局 部)資源管理器(RM)之間的接口。主流的關(guān)系型 數(shù)據(jù)庫產(chǎn)品都是實(shí)現(xiàn)了XA接口的。
分段提交
XA需要兩階段提交: prepare 和 commit.
第一階段為 準(zhǔn)備(prepare)階段。即所有的參與者準(zhǔn)備執(zhí)行事務(wù)并鎖住需要的資源。參與者ready時(shí),向transaction manager報(bào)告已準(zhǔn)備就緒。
第二階段為提交階段(commit)。當(dāng)transaction manager確認(rèn)所有參與者都ready后,向所有參與者發(fā)送commit命令。
ShardingSphere默認(rèn)的XA事務(wù)管理器為Atomikos,在項(xiàng)目的logs目錄中會(huì)生成xa_tx.log, 這是XA崩潰恢復(fù)時(shí)所需的日志,請(qǐng)勿刪除。
6.2 BASE柔性事務(wù)管理器(SEATA-AT配置)
Seata 是一款開源的分布式事務(wù)解決方案,提供簡(jiǎn)單易用的分布式事務(wù)服務(wù)。隨著業(yè)務(wù)的快速發(fā)展,應(yīng)用單體架構(gòu)暴露出代碼可維護(hù)性差,容錯(cuò)率低,測(cè)試難度大,敏捷交付能力差等諸多問題,微服務(wù)應(yīng)運(yùn)而生。微服務(wù)的誕生一方面解決了上述問題,但是另一方面卻引入新的問題,其中主要問題之一就是如何保證微服務(wù)間的業(yè)務(wù)數(shù)據(jù)一致性。Seata 注冊(cè)配置服務(wù)中心均使用 Nacos。Seata 0.2.1+ 開始支持 Nacos 注冊(cè)配置服務(wù)中心。
- 按照seata-work-shop中的步驟,下載并啟動(dòng)seata server。
- 在每一個(gè)分片數(shù)據(jù)庫實(shí)例中執(zhí)創(chuàng)建undo_log表(以MySQL為例)
- 在classpath中增加seata.conf
6.3 Sharding-Jdbc默認(rèn)提供弱XA事務(wù)
官方說明:
完全支持非跨庫事務(wù),例如:僅分表,或分庫但是路由的結(jié)果在單庫中。
完全支持因邏輯異常導(dǎo)致的跨庫事務(wù)。例如:同一事務(wù)中,跨兩個(gè)庫更新。更新完畢后,拋出空指針,則兩個(gè)庫的內(nèi)容都能回滾。
不支持因網(wǎng)絡(luò)、硬件異常導(dǎo)致的跨庫事務(wù)。例如:同一事務(wù)中,跨兩個(gè)庫更新,更新完畢后、未提交之前,第一個(gè)庫死機(jī),則只有第二個(gè)庫數(shù)據(jù)提交。
6.4 分布式事務(wù)場(chǎng)景
- 保存場(chǎng)景
推薦使用第三種弱XA事務(wù),盡量設(shè)計(jì)時(shí)避免跨庫事務(wù),目前設(shè)計(jì)為事件和事件數(shù)據(jù)為同庫(分庫時(shí),將一個(gè)線索號(hào)的事件和事件數(shù)據(jù)HASH進(jìn)入同一個(gè)分庫),盡量避免跨庫事務(wù)。
事件和計(jì)費(fèi)結(jié)果本身設(shè)計(jì)為異步,非同一事務(wù),所以事件和對(duì)應(yīng)的結(jié)果不涉及跨庫事務(wù)。
保存多個(gè)計(jì)費(fèi)結(jié)果,每次保存都屬于一個(gè)事件,一個(gè)事件的計(jì)費(fèi)結(jié)果都屬于一個(gè)收付款對(duì)象,天然同庫。
弱XA事務(wù)的性能最佳。
- 更新場(chǎng)景
對(duì)一些根據(jù)ID IN的更新場(chǎng)景,根據(jù)收付款對(duì)象分組執(zhí)行,可以避免在所有分庫執(zhí)行更新。
- 刪除場(chǎng)景
無,目前都是邏輯刪除,實(shí)際為更新。
7 總結(jié)
1.推薦使用Sharding-Sphere進(jìn)行分庫,分表可以考慮使用MYSQL分區(qū)表,對(duì)于研發(fā)來講完全是透明的,可以規(guī)避JOIN\分布式事務(wù)等問題。(分區(qū)表需要為分區(qū)鍵+ID建立了一個(gè)聯(lián)合索引)MYSQL分區(qū)得到了大量的實(shí)踐印證,沒有BUG,包括我在新計(jì)費(fèi)初期,一直堅(jiān)持推動(dòng)使用的分表方案,不會(huì)引起一些難以發(fā)現(xiàn)的問題,在同庫同磁盤下性能與分表相當(dāng)。
2.對(duì)于同一時(shí)刻有大量并發(fā)讀操作和較少寫操作類型的數(shù)據(jù)來說,適合使用讀寫分離,增加多個(gè)讀庫,緩解主庫壓力,要注意的是必須讀主庫的場(chǎng)景使用SeekMaster注解來實(shí)現(xiàn)。
3.數(shù)據(jù)分庫選擇合適的分片鍵非常重要,要根據(jù)業(yè)務(wù)需求選擇好分庫鍵,盡力避免數(shù)據(jù)傾斜,數(shù)據(jù)不均勻是目前數(shù)據(jù)拆分的一個(gè)共同問題,不可能實(shí)現(xiàn)數(shù)據(jù)的完全均勻;當(dāng)查詢條件沒有分庫鍵時(shí)會(huì)遍歷所有分庫,查詢盡量帶上分庫鍵。
4.在我們使用中間件時(shí),不要只看官網(wǎng)解釋,要多做測(cè)試,用實(shí)際來驗(yàn)證,有的時(shí)候官網(wǎng)解釋話術(shù)可能存在歧義或表達(dá)不夠全面的地方,分析源碼和實(shí)際測(cè)試可以清晰的獲得想要的結(jié)果。