摘要:如果要消滅大程序,那就得切分,做好切分必然離不開高內(nèi)聚低耦合的核心思想。分布式系統(tǒng)關(guān)注點高內(nèi)聚低耦合詳解這篇聊的就是這個。也是分布式系統(tǒng)的分治思想體現(xiàn)。垂直切分垂直切分有時候也會被稱作縱向切分。題外話不到迫不得己,盡量避免進行水平切分。
如果第二次看到我的文章,歡迎下方掃碼訂閱我的個人公眾號(跨界架構(gòu)師)喲~
本文長度為5389字,建議閱讀14分鐘。
堅持原創(chuàng),每一篇都是用心之作~
沒想到這篇文章寫了這么長,一時半會沒消化完的話,可以收藏一下先。
這是「伸縮性」章節(jié)的第四篇,先給新來的小伙伴們簡單回顧下前三篇的內(nèi)容。
做「伸縮性」最重要的就是先做好「無狀態(tài)」,如此才可以隨心所欲的進行橫向“擴展”,而不用擔心在多個副本之間切換會產(chǎn)生錯亂。《分布式系統(tǒng)關(guān)注點——「無狀態(tài)」詳解》聊的就是這個。
不過,就算做好了橫向擴展,本質(zhì)上還是一個“大程序”,只是變得「可復(fù)制」了而已。
如果要消滅“大程序”,那就得“切分”,做好切分必然離不開「高內(nèi)聚低耦合」的核心思想。《分布式系統(tǒng)關(guān)注點——「高內(nèi)聚低耦合」詳解》這篇聊的就是這個。
題外話:當你遇到單點單應(yīng)用支撐不住使用的時候,Z哥給你的普適性建議是:先考慮“擴”,再考慮“切”。這個和寫代碼一樣,“增加”新功能往往比在老功能上改容易。“擴”的話先考慮「垂直擴」(加硬件,錢能解決的都不是問題),再考慮「水平擴」(無狀態(tài)改造+多節(jié)點部署,這是小手術(shù))。
“切”的話一般就是「垂直切」(根據(jù)業(yè)務(wù)切分,這是大手術(shù)),偶爾會用到「水平切」(其實就是單個應(yīng)用里的分層,比如前后端分離)。
第三篇《分布式系統(tǒng)關(guān)注點——彈性架構(gòu)》我們聊了常見的兩種「松耦合」架構(gòu)模式,為的是讓應(yīng)用程序的「伸縮性」更上一層樓。
以上這些呢都是應(yīng)用程序?qū)用娴墓ぷ鳌R话闱闆r下,在應(yīng)用程序?qū)用孀鲎鍪中g(shù),再配合以緩存的充分運用,就可以支撐系統(tǒng)發(fā)展很長時間了。特別是數(shù)據(jù)量不大,只是請求量大的「CPU密集型」場景。
但是,如果所處的工作場景是一個非常成熟且具有一定規(guī)模的項目,越發(fā)展到后面瓶頸總是出現(xiàn)在數(shù)據(jù)庫這里。甚至會出現(xiàn)cpu長期高負荷、宕機等現(xiàn)象。
在如此場景下,就不得不對數(shù)據(jù)庫開刀了。這次Z哥就來和你聊聊做數(shù)據(jù)庫的「伸縮性」有哪些好方法。
面臨數(shù)據(jù)庫需要開刀的時候,整個系統(tǒng)往往已經(jīng)長成這個樣子了。
正如前面所說,這時候的瓶頸往往會體現(xiàn)在「CPU」上。
因為對數(shù)據(jù)庫來說,硬盤和內(nèi)存的擴容相對容易,因為它們都可以直接用“增加”的方式進行。
CPU就不同了,一旦CPU飆高,最多檢查下索引有沒有做好,完了之后基本就只能干看著。
所以解決這個問題的思路自然就變成了:如何將一個數(shù)據(jù)庫的CPU壓力分攤到多個CPU上去。甚至可以做到按需隨時增加。
那這不就是和應(yīng)用程序一樣做「切分」嘛。也是分布式系統(tǒng)的「分治」思想體現(xiàn)。
既然是切分,本質(zhì)上就和應(yīng)用程序一樣,也分為「垂直切分」和「水平切分」。
垂直切分有時候也會被稱作「縱向切分」。
同應(yīng)用程序一樣,它是以「業(yè)務(wù)」為維度的切分方式,在不同的數(shù)據(jù)庫服務(wù)器上跑不同業(yè)務(wù)的數(shù)據(jù)庫,各司其職。
一般情況下,Z哥建議你優(yōu)先考慮「垂直切分」而不是「水平切分」,為什么呢?你可以隨意打開手頭項目中的SQL語句看看,我想必然存在著大量的「join」和「transaction」關(guān)鍵字,這種關(guān)聯(lián)查詢和事務(wù)操作,本質(zhì)上是一種「關(guān)系捆綁」,一旦面臨數(shù)據(jù)庫拆分之后,就沒法玩了。
此時你只有2個選擇。
要么將不必要的「關(guān)系捆綁」邏輯舍棄掉,這需要在業(yè)務(wù)上作出調(diào)整,去除不必要的“批量操作”業(yè)務(wù),或者去除不必要的強一致性事務(wù)。不過你也知道,肯定有一些場景是去不完的。
要么將「合并」,「關(guān)聯(lián)」等邏輯上浮,體現(xiàn)到業(yè)務(wù)邏輯層甚至是應(yīng)用層的代碼中。
最終,不管怎么選擇,改動起來都是一個大工程。
為了讓這個工程盡可能的動作小一些,追求更好的性價比,需要堅持一個原則——“避免拆分緊密關(guān)聯(lián)的表”。
因為兩個表之間關(guān)聯(lián)越緊密,意味著對「join」和「transaction」的需求越多,所以堅持這個原則可以使得相同的模塊,緊密相關(guān)的業(yè)務(wù)都落在同一個庫中,這樣它們可以繼續(xù)使用「join」和「transaction」來工作。
因此,我們應(yīng)當優(yōu)先采用「垂直切分」的方式。
做「垂直切分」思路很簡單,一般情況下,建議是與切分后的應(yīng)用程序一一對應(yīng)就好,不用多也不用少。
實際工作中,要做好「垂直切分」主要體現(xiàn)在「業(yè)務(wù)」的熟悉度上,所以這里就不繼續(xù)展開了。
「垂直切分」的優(yōu)點是:
高內(nèi)聚,拆分規(guī)則清晰。相比「水平切分」數(shù)據(jù)冗余度更低。
與應(yīng)用程序是1:1的關(guān)系,方便維護和定位問題。一旦某個數(shù)據(jù)庫中發(fā)現(xiàn)異常數(shù)據(jù),排查這個數(shù)據(jù)庫的關(guān)聯(lián)程序就行了。
但是這并不是一個「一勞永逸」的方案,因為沒人能預(yù)料到未來業(yè)務(wù)會發(fā)展的怎么樣,所以最明顯的缺點就是:對于訪問極其頻繁或者數(shù)據(jù)量超大的表仍然存在性能瓶頸。
確實需要解決這個問題的話,就需要搬出「水平切分」了。
題外話:不到迫不得己,盡量避免進行「水平切分」。看完接下去的內(nèi)容你就知道原因了。
下面Z哥就給你好好聊聊「水平切分」,這才是本文的重點。
想象一下,在你做了「垂直切分」之后,還是在某個數(shù)據(jù)庫中發(fā)現(xiàn)了一張數(shù)據(jù)量超過10億條的表。
這個時候要對這個表做「水平切分」,你會怎么思考這個事情?
Z哥教給你的思路是:
先找到“最高頻“的「讀」字段。
再看這個字段的實際使用中有什么特點(批量查詢多還是單個查詢多,是否同時是其它表的關(guān)聯(lián)字段等等)。
再根據(jù)這個特點選擇合適的切分方案。
為什么要先找到高頻的「讀」字段呢?
因為在實際的使用中,「讀」操作往往是遠大于「寫」操作的。一般進行「寫」之前都得通過「讀」來做先行校驗,然而「讀」還有自己多帶帶的使用場景。所以針對更高頻的「讀」場景去考慮,產(chǎn)生的價值必然也更大。
比如,現(xiàn)在那張10億數(shù)據(jù)量的表是一張訂單表,結(jié)構(gòu)是這樣:
order (orderId long, createTime datetime, userId long)
下面我們先來看看有哪幾種「水平切分」的方式,完了才能明白什么樣的場景適合哪種方式。
這是一種「連續(xù)式」的切分方式。
比如根據(jù)時間(createTime)切分的話,我們可以按年月來分,order_201901一個庫,order_201902一個庫,以此類推。
根據(jù)順序數(shù)(orderId)切分的話,可以100000~199999一個庫,200000~299999一個庫,以此類推。
這種切分法的優(yōu)點是:單個表的大小可控,擴展的時候無需數(shù)據(jù)遷移。
缺點也很明顯,一般來說時間越近或者序號越大的數(shù)據(jù)越“新”,因此被訪問的頻率和概率相比“老”數(shù)據(jù)更多。會導(dǎo)致壓力主要集中在新的庫中,而歷史越久的庫,越空閑。
與「范圍切分」正好相反,這是一種「離散式」的切分方式。
它的優(yōu)點就是解決了「范圍切分」的缺點,新數(shù)據(jù)被分散到了各個節(jié)點中,避免了壓力集中在少數(shù)節(jié)點上。
同樣,缺點與「范圍切分」的優(yōu)點相反,一旦進行二次擴展,必然會涉及到數(shù)據(jù)遷移。因為Hash算法是固定的,算法一變,數(shù)據(jù)分布就變了。
大多數(shù)情況下,我們的hash算法可以通過簡單的「取模」運算來進行即可。就像下面這樣:
假如分成11個庫的話,公式就是 orderId % 10。100000 % 10 = 0,分配到db0。
100001 % 10 = 1,分配到db1。....
100010 % 10 = 0,分配到db0。
100011 % 10 = 1,分配到db1。
其實,在某些場景下,我們可以通過自定義id的生成(可以參考之前的文章,《分布式系統(tǒng)中的必備良藥 —— 全局唯一單據(jù)號生成》)來做到既可以通過hash切分來打散熱點數(shù)據(jù),又可以減少依賴全局表來定位具體的數(shù)據(jù)。
比如,在orderId中加入userId的尾數(shù),以此達到orderId和userId取模結(jié)果相等的效果。還是來舉個例子:
一個用戶的userId是200004,如果取一個4bit尾數(shù)的話,這里就是4,用0100表示。然后,我們通過自定義id算法生成orderId的前60位,在后面補上0100。
于是,orderId % 10和 userId % 10的結(jié)果就是一樣的了。
當然,除了userId之外還想加入其他的因子就不好使了。也就是,可以在不增加全局表的情況下,額外多支持1個維度。
提到了兩次全局表,那么啥是全局表呢?
這種方式就是將用作切分依據(jù)的分區(qū)Key與對應(yīng)的每一條具體數(shù)據(jù)的id保存到一個多帶帶的庫或者表中。例如要增加一張這樣的表:
nodeId orderId
01 100001
02 100002
01 100003
01 100004
...
如此一來,的確將大部分具體的數(shù)據(jù)分布在了不同服務(wù)器上,但是這張全局表會給人一種「形散神不散」的感覺。
因為請求數(shù)據(jù)的時候無法直接定位需要的數(shù)據(jù)在哪臺服務(wù)器上,所以每一次操作都要先查詢一下這張全局表好知道具體的數(shù)據(jù)被存放在哪里。
這種「中心化」的模式帶來的副作用就是瓶頸和風險轉(zhuǎn)移到了這張全局表上。但是,勝在邏輯簡單。
好了,那么這幾種切分方案怎么選擇呢?
Z哥給你的建議是,如果熱點數(shù)據(jù)不是特別集中的場景,建議先用「范圍切分」,否則選擇另外2種。
選擇另外兩種的時候,數(shù)據(jù)量越大越傾向選擇Hash切分。因為后者在整體的可用性和性能上都比前者好,就是實現(xiàn)成本高一些。
「水平切分」真正做到了可以“無限擴展”,但是也存在相應(yīng)的弊端。
1)批量查詢、分頁等需要做更多的額外工作。特別是當一個表存在多個高頻字段用于where、order by或者group by的時候。
2)拆分規(guī)則不如「垂直切分」那么明確。
所以還是多說一句“廢話”:沒有完美的方案只有合適的方案,要結(jié)合具體的場景來選擇。(歡迎你在留言區(qū)提出你有疑惑的場景,和Z哥來討論討論)
當你在具體實施「水平切分」的時候可以在2個層面動刀,可以是「表」層面,也可以是「庫」層面。
表在同一個數(shù)據(jù)庫下面分表,表名order_0 ,order_1, order_2.....。
它可以解決單表數(shù)據(jù)過大,但并不能解決CPU負荷的問題。所以,當CPU并沒多少壓力,只是由于表太大,導(dǎo)致執(zhí)行SQL操作比較慢的話,可以選擇這種方式。
這個時候表名可以不變,都叫order,只是分成10個庫。那么就是db0-user db1-user db2-user......。
我們前面大篇幅都是基于這個模式在聊,就不多說了。
也可以既分庫又分表,比如先分10個庫,然后每個庫再分10張表。
這其實是個二級索引的思路,通過庫來進行第一次定位,減少一定的資源消耗。
比如,先按年分庫,再按月分表。如此一來,如果需要獲取的數(shù)據(jù)只跨月但不跨年,我們就可以在單個庫內(nèi)做聚合運算來完成,不涉及到跨庫操作。
不過,不管選擇哪種方式來進行,你還是會或多或少面臨以下兩個問題,逃不掉的。
跨庫join。
全局聚合或者排序操作。
解決第一個問題最佳方式還是需要改變你的編程思維。盡量將一些邏輯、關(guān)系、約束等體現(xiàn)在應(yīng)用程序的代碼中,避免因為方便而在SQL中做這些事情。
畢竟代碼是可以寫成“無狀態(tài)”的,可以隨時做擴展,但是SQL是跟著數(shù)據(jù)走的,而數(shù)據(jù)就是“狀態(tài)”,天然不利于擴展。
當然了,退而求其次,你也可以冗余大量的全局表來應(yīng)對。只是如此一來,對「數(shù)據(jù)一致性」工作是個很大的考驗,另外,對存儲資源也是很大的開銷。
第二個問題的解決方案就是需要將原本的一次聚合或者一次排序變成兩次操作。其中的遍歷多個節(jié)點可以以「并行」的方式進行。
那么數(shù)據(jù)切分完之后程序如何來使用呢?這又可以分為兩種模式,「進程內(nèi)」和「進程外」。
「進程內(nèi)」的話,可以在封裝好的DAL訪問框架中做,也可以在ORM框架中做,還可以在數(shù)據(jù)庫驅(qū)動中做。這個模式比較知名的解決方案如阿里的tddl。
「進程外」的話,就是代理模式,這個模式比較知名的解決方案是mycat、cobar、atlas等等,相對多一些,因為這種模式對應(yīng)用程序是「低侵入」的,使用起來像“一個數(shù)據(jù)庫”。但是由于多了一道網(wǎng)絡(luò)通信,性能上會多一些損耗。
老規(guī)矩,下面再分享一些最佳實踐。
首先分享兩個可以不停機做數(shù)據(jù)切分的小竅門。我們以實施hash法做水平切分的例子來看一下。
第一次做切分的時候,你可以以「主-從」的形式將新增的節(jié)點作為原始節(jié)點的副本,進行全量實時同步。
然后在這個基礎(chǔ)上刪除不屬于它的數(shù)據(jù)。(當然了,不刪也沒啥問題,就是多占用一些空間)
這樣就可以不用停機了。
第二,隨著時間的推移,如果后續(xù)支撐不住了,需要二次切分的話,我們可以選擇用2的倍數(shù)來擴展。
如此一來,數(shù)據(jù)的遷移變得很簡單,只需要做局部的遷移,和第一次做切分的思路是一樣的。
當然了,如果選擇的切分方式是「范圍切分」的話,就沒有二次切分時的困擾,數(shù)據(jù)自然跑到最新的節(jié)點上去了。比如我們按年月分表的話。2019年3月的數(shù)據(jù)自然就落到了xxxx_201903的表中。
到這里,Z哥還是想特別強調(diào)的是,能不切分盡量不要切分,可以先使用「讀寫分離」之類的方案先來應(yīng)對面臨的問題。
如果實在要進行切分的話,務(wù)必先「垂直切分」,再考慮「水平切分」。
一般來說,以這樣的順序來考慮,性價比更好。
好了,我們總結(jié)一下。
這次呢,Z哥先向你介紹了做數(shù)據(jù)庫切分的兩種思路。兩種思路通俗理解就是:「垂直拆分」等于“列”變“行”不變,「水平拆分」等于“行”變“列”不變。
然后著重聊了下「水平切分」的3種實現(xiàn)方式和具體實施的思路。
最后分享了一些實踐中的經(jīng)驗給你。
希望對你有所啟發(fā)。
相關(guān)文章:
分布式系統(tǒng)關(guān)注點——「無狀態(tài)」詳解
分布式系統(tǒng)關(guān)注點——「高內(nèi)聚低耦合」詳解
分布式系統(tǒng)關(guān)注點——彈性架構(gòu)
分布式系統(tǒng)中的必備良藥 —— 全局唯一單據(jù)號生成
作者:Zachary
出處:https://www.cnblogs.com/Zacha...
如果你喜歡這篇文章,可以點一下文末的「贊」。
這樣可以給我一點反饋。: )
謝謝你的舉手之勞。
?關(guān)于作者:張帆(Zachary,個人微信號:Zachary-ZF)。堅持用心打磨每一篇高質(zhì)量原創(chuàng)。歡迎掃描下方的二維碼~。定期發(fā)表原創(chuàng)內(nèi)容:架構(gòu)設(shè)計丨分布式系統(tǒng)丨產(chǎn)品丨運營丨一些思考。
如果你是初級程序員,想提升但不知道如何下手。又或者做程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關(guān)注我的公眾號「跨界架構(gòu)師」,回復(fù)「技術(shù)」,送你一份我長期收集和整理的思維導(dǎo)圖。
如果你是運營,面對不斷變化的市場束手無策。又或者想了解主流的運營策略,以豐富自己的“倉庫”。歡迎關(guān)注我的公眾號「跨界架構(gòu)師」,回復(fù)「運營」,送你一份我長期收集和整理的思維導(dǎo)圖。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/73602.html
摘要:能不能只修改其中的一部分代碼并且進行熱更新呢微內(nèi)核架構(gòu)插件架構(gòu)就適合來解決這個問題。微內(nèi)核架構(gòu)顧名思義,微內(nèi)核架構(gòu)的關(guān)鍵是內(nèi)核。微內(nèi)核架構(gòu)整體上由兩部分組成核心系統(tǒng)和插件模塊。微內(nèi)核架構(gòu)它的優(yōu)點是為遞進設(shè)計和增量開發(fā)提供了方便。 如果第二次看到我的文章,歡迎下方掃碼訂閱我的個人公眾號(跨界架構(gòu)師)喲~本文長度為3633字,建議閱讀10分鐘。堅持原創(chuàng),每一篇都是用心之作~ 如果我們的...
摘要:這里的某種規(guī)則都包含哪些規(guī)則呢這就涉及到數(shù)據(jù)庫的分片規(guī)則問題了,這個松哥在后面的文章中也會和大家一一展開詳述。 千萬量級的數(shù)據(jù),用 MySQL 要怎么存? 初學(xué)者在看到這個問題的時候,可能首先想到的是 MySQL 一張表到底能存放多少條數(shù)據(jù)? 根據(jù) MySQL 官方文檔的介紹,MySQL 理論上限是 (232)2 條數(shù)據(jù),然而實際操作中,往往還受限于下面兩條因素: myisam_d...
閱讀 3692·2021-11-22 15:24
閱讀 1598·2021-09-26 09:46
閱讀 1912·2021-09-14 18:01
閱讀 2609·2019-08-30 15:45
閱讀 3528·2019-08-30 14:23
閱讀 1873·2019-08-30 12:43
閱讀 2917·2019-08-30 10:56
閱讀 803·2019-08-29 12:20