談一談數(shù)據(jù)分片
前言
一般情況下,項(xiàng)目都是從一個(gè)小項(xiàng)目不斷迭代成一個(gè)龐大的、復(fù)雜的大項(xiàng)目,比如我們做一個(gè)電商系統(tǒng)的時(shí)候,首先會把所有的業(yè)務(wù)表(比如訂單表、用戶表、商品表等等)放在同一個(gè)數(shù)據(jù)庫中。

但后來項(xiàng)目越做越大,這樣把所有數(shù)據(jù)存儲到單一數(shù)據(jù)節(jié)點(diǎn)的解決方案,在性能、可用性和維護(hù)成本這三個(gè)方面無法滿足互聯(lián)網(wǎng)海量數(shù)據(jù)的應(yīng)用場景。
性能
以MySQL的innerDB存儲引擎來講,由于大多數(shù)關(guān)系型的數(shù)據(jù)庫采用的是B+樹的索引,在數(shù)據(jù)量超過超過閾值的情況下,索引深度的增加也將使得磁盤訪問的IO次數(shù)增加,進(jìn)一步會導(dǎo)致查詢性能的下降(一般B+樹的控制在1-3之間,也就是說,一次主鍵索引查詢,可能會出現(xiàn)1-3次IO,一張表存2000萬條數(shù)據(jù)就要掂量掂量了);與此同時(shí),高并發(fā)訪問請求也使得單一數(shù)據(jù)節(jié)點(diǎn)成為系統(tǒng)瓶頸。
可用性
服務(wù)的無狀態(tài)化,能夠達(dá)到很小成本的隨意擴(kuò)容,這將會導(dǎo)致系統(tǒng)的最終壓力都落在數(shù)據(jù)庫。然而單一數(shù)據(jù)節(jié)點(diǎn),很難承擔(dān)。數(shù)據(jù)節(jié)點(diǎn)的可用性,是整個(gè)系統(tǒng)的關(guān)鍵。
運(yùn)維成本
在一個(gè)數(shù)據(jù)庫實(shí)例中的數(shù)據(jù)達(dá)到閾值以上,對于DBA的運(yùn)維壓力就會增大。數(shù)據(jù)備份和數(shù)據(jù)恢復(fù)時(shí)間成本都將隨著數(shù)據(jù)量的大小而愈發(fā)不可控。一般來講,單一數(shù)據(jù)庫實(shí)例的數(shù)據(jù)的閾值在1TB之內(nèi)。
在傳統(tǒng)的關(guān)系型數(shù)據(jù)庫無法滿足互聯(lián)網(wǎng)場景需要的情況下,將數(shù)據(jù)存儲在天然支持分布式的 NoSQL 數(shù)據(jù)庫(比如我公司的訂單表就存儲到MongoDB上)。但 NoSQL 對 SQL 的不友好以及事務(wù)等問題,使得它們始終無法完全的替代關(guān)系型數(shù)據(jù)庫。
數(shù)據(jù)分片
數(shù)據(jù)分片是指按照某個(gè)維度將存放在單一數(shù)據(jù)節(jié)點(diǎn)中的數(shù)據(jù)分散地存放在多個(gè)數(shù)據(jù)節(jié)點(diǎn)或表中,來達(dá)到提升性能瓶頸以及可用性的目的。
數(shù)據(jù)分片的核心手段就是對關(guān)系型數(shù)據(jù)庫進(jìn)行分庫和分表。分庫和分表均可以有效的避免由數(shù)據(jù)量超過可承受閾值而產(chǎn)生的查詢瓶頸。除此之外,分庫還能夠用于有效的分散對數(shù)據(jù)節(jié)點(diǎn)單點(diǎn)的訪問量(你想想看,查訂單的去訂單節(jié)點(diǎn)查,查用戶的去用戶節(jié)點(diǎn)去查);
分表雖然無法緩解數(shù)據(jù)節(jié)點(diǎn)的壓力,但卻能夠提供盡量將分布式事務(wù)變?yōu)楸镜厥聞?wù)來處理,一旦涉及到跨庫的更新操作,分布式事務(wù)往往會使問題變得復(fù)雜(比如用戶下訂單時(shí),扣積分、減庫存就夠你喝一壺了)。
使用主從的分片方式,可以有效的避免數(shù)據(jù)單點(diǎn),從而提升數(shù)據(jù)架構(gòu)的可用性(更新操作去主節(jié)點(diǎn),查詢操作去從節(jié)點(diǎn))。
通過分庫和分表進(jìn)行數(shù)據(jù)的拆分來使得各個(gè)表的數(shù)據(jù)量保持在閾值以下,以及對流量進(jìn)行疏導(dǎo)應(yīng)對高訪問量,是應(yīng)對高并發(fā)和海量數(shù)據(jù)系統(tǒng)的有效手段。
數(shù)據(jù)分片的拆分方式又分為垂直分片和水平分片。
垂直分片
按照業(yè)務(wù)拆分的方式叫做垂直分片,又叫做縱向拆分,就是專庫專用的意思(有點(diǎn)像公司各個(gè)部門的意思,各司其職)。拆分之前,一個(gè)數(shù)據(jù)節(jié)點(diǎn)由多個(gè)業(yè)務(wù)表構(gòu)成,每個(gè)表存儲著不同的業(yè)務(wù)數(shù)據(jù)。而拆分之后,我們把表按照業(yè)務(wù)進(jìn)行歸類,分布到不同的數(shù)據(jù)節(jié)點(diǎn)中,從而將壓力分散至不同的數(shù)據(jù)節(jié)點(diǎn)。比如與用戶相關(guān)的放到用戶庫,訂單相關(guān)的放到訂單庫。

垂直分片需要對架構(gòu)和設(shè)計(jì)進(jìn)行調(diào)整。通常來講,是來不及應(yīng)對互聯(lián)網(wǎng)業(yè)務(wù)需求快速變化的;而且,它也無法真正地解決單點(diǎn)瓶頸。垂直拆分可以緩解數(shù)據(jù)量和訪問量帶來的問題,但無法根治。如果垂直拆分之后,表中的數(shù)據(jù)量依然超過單節(jié)點(diǎn)所能承載的閾值(比如我司在前段時(shí)間的618大促的時(shí)候,其他業(yè)務(wù)節(jié)點(diǎn)都沒有問題,只有營銷節(jié)點(diǎn)CPU打滿,以至于拖累整個(gè)支付鏈不可用),則需要水平分片來進(jìn)一步處理。
水平分片
水平分片又叫做橫向拆分(就像一個(gè)公司會在不同的城市設(shè)置不同的分公司去處理日常工作)。相對于垂直分片,它不再將數(shù)據(jù)根據(jù)業(yè)務(wù)邏輯歸類,而是通過某個(gè)字段(或某幾個(gè)字段),根據(jù)某種規(guī)則將數(shù)據(jù)分散至多個(gè)節(jié)點(diǎn)或表中,每個(gè)分片僅包含數(shù)據(jù)的一部分。例如:根據(jù)主鍵分片,偶數(shù)主鍵的記錄放入 0 庫(或表),奇數(shù)主鍵的記錄放入 1 庫(或表)。

水平分片從理論上突破了單機(jī)數(shù)據(jù)量處理的瓶頸,并且擴(kuò)展相對自由,是分庫分表的標(biāo)準(zhǔn)解決方案。
問題
雖然數(shù)據(jù)分片解決了性能、可用性以及單點(diǎn)備份恢復(fù)等問題,但分布式的架構(gòu)也引入了新的問題。
復(fù)雜的數(shù)據(jù)庫操作
面對散亂的分庫分表之后的數(shù)據(jù),開發(fā)工程師和數(shù)據(jù)庫管理員對數(shù)據(jù)庫的操作變得復(fù)雜就是其中的重要挑戰(zhàn)之一。他們需要知道數(shù)據(jù)需要從哪個(gè)具體的數(shù)據(jù)庫的哪個(gè)表中獲取。
你想想看,原來我們都在同一個(gè)地方辦公,你想叫張三、李四都很容易,但現(xiàn)在大家都分派在天南海北,交流起來就不方便。
數(shù)據(jù)聚合
能夠正確地運(yùn)行在單節(jié)點(diǎn)數(shù)據(jù)庫中的 SQL,在分片之后的數(shù)據(jù)庫中并不一定能夠正確運(yùn)行。例如,分表導(dǎo)致表名稱的修改,或者分頁、排序、聚合分組等操作的不正確處理。
比如,我們想查詢訂單表的前10條數(shù)據(jù),以前只要一條SQL語句搞定,現(xiàn)在分到不同的數(shù)據(jù)節(jié)點(diǎn),這時(shí)再想分頁查詢會讓你崩潰。

分布式事務(wù)(跨庫事務(wù))
采用分表策略,在降低一個(gè)表數(shù)據(jù)量的情況下,盡量使用本地事務(wù),因?yàn)槎荚谝粋€(gè)數(shù)據(jù)節(jié)點(diǎn)下就可以避免跨庫事務(wù)的問題。
在不能避免跨庫事務(wù)的場景,有些業(yè)務(wù)還是要保證事務(wù)的一致性,基于 XA 分布式事務(wù)在高并發(fā)的場景中性能無法滿足需要,大多采用最終一致性的柔性事務(wù)代替強(qiáng)一致事務(wù)。

寫在最后
好兄弟可以點(diǎn)贊并關(guān)注我的公眾號“javaAnswer”,全部都是干貨。

談一談數(shù)據(jù)分片的評論 (共 條)
