亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

      
      

            <dl id="hur0q"><div id="hur0q"></div></dl>

                百億數(shù)據(jù)分庫分表核心流程詳解

                百億數(shù)據(jù)分庫分表核心流程詳解

                前言

                俗話說:面試造火箭,入職擰螺絲。盡管99.99%的業(yè)務(wù)都不需要用到分庫分表,但是分庫分表還是頻繁出現(xiàn)在大廠的面試中。

                分庫分表涉及到的內(nèi)容非常多,有很多細節(jié),如果在面試中被問到了,既是挑戰(zhàn),也是機會,如果你能回答好的話,會給你的面試加很多分。

                由于業(yè)務(wù)量的關(guān)系,絕大部分同學都很難有實際分庫分表的機會,因此很多同學在碰到這個問題時很容易懵逼。

                因此今天跟大家分享一下分庫分表的相關(guān)知識,本文內(nèi)容源于實際高并發(fā)+海量數(shù)據(jù)業(yè)務(wù)下的實戰(zhàn)和個人的思考總結(jié)。

                什么是分庫分表

                分表

                分表指的是在數(shù)據(jù)庫數(shù)量不變的情況下,對數(shù)據(jù)庫里面的表進行拆分。

                例如我們將SPU表從一張拆成四張。

                分庫

                分庫指的是在表數(shù)量不變的情況下對數(shù)據(jù)庫進行拆分。

                例如我們本來有一個庫里面放了兩張表,一張是SPU表,一張是SKU表。我們將這兩張表拆到兩個不同的庫里面去。

                分庫分表

                也就是數(shù)據(jù)庫的數(shù)量,還有表的數(shù)量都發(fā)生變更。

                例如我們有一個數(shù)據(jù)庫里面本來有一張SPU表。我們將這個SPU表拆成四張表,并且放在兩個數(shù)據(jù)庫里面。

                拆分方式

                當前主要的拆分方式有兩種:水平拆分和垂直拆分。

                水平拆分就是從左往右橫著切,垂直拆分就是從上往下豎著切。當然具體切幾刀,這個要看具體的業(yè)務(wù)需求。

                水平拆分

                水平拆分指的是在整個表數(shù)據(jù)結(jié)構(gòu)不發(fā)生變更的情況下,將一張表的數(shù)據(jù)拆分成多張表。因為當單張表的數(shù)據(jù)量越來越大時,這張表的查詢跟寫入性能也會相應(yīng)的變得越來越慢。

                因此這個時候我們可以將單張表拆分成多張表,從而讓每張表的數(shù)據(jù)量都變小,從而可以提供更好的讀寫性能。

                垂直拆分

                垂直拆分指的是將本來放在一張表的字段拆分到多張表中。

                例如在這個例子中,我們將pic這個字段單獨拆分出來,然后剩下的三個字段還保留在原表里面。

                這種場景主要是因為在業(yè)務(wù)的初期,為了業(yè)務(wù)的快速發(fā)展,我們將商品的所有字段都放在一張表里面。但是隨著后面的業(yè)務(wù)的發(fā)展,我們發(fā)現(xiàn)這個pic字段可能變得越來越大,從而影響到我們商品的基本信息的查詢性能。因此這個時候我們可以將這個pic字段單獨拆分出去。

                當然這個pic字段拆分出去之后,它應(yīng)該要存儲這個原來這個商品的這個id。

                為什么需要分庫分表

                因為單臺MySQL服務(wù)器的硬件資源是有限的,隨著業(yè)務(wù)的不斷發(fā)展,請求量和數(shù)據(jù)量會不斷增加,數(shù)據(jù)庫的壓力會越來越大,到了某一時刻,數(shù)據(jù)庫的讀寫性能可能會開始下降,這個時候數(shù)據(jù)庫就成為請求鏈路中的瓶頸。

                此時可能就需要我們?nèi)?shù)據(jù)庫進行優(yōu)化,業(yè)務(wù)初期我們可能會使用增加索引、優(yōu)化索引、讀寫分離、增加從庫等手段來進行優(yōu)化,但是隨著數(shù)據(jù)量的不斷增大,這些優(yōu)化手段的效果會變得越來越小,此時可能就需要使用分庫分表來進行優(yōu)化,對數(shù)據(jù)進行切分,將單庫和單表的數(shù)據(jù)量控制在合理的范圍內(nèi),以保證數(shù)據(jù)庫可以提供高效的讀寫能力。

                何時需要分庫分表

                總體來說:當性能出現(xiàn)瓶頸,并且其他優(yōu)化手段無法很好的解決的時候。

                我們這邊必須首先明確分庫分表一般是作為最終的解決手段,我們會優(yōu)先使用其他的方法來進行優(yōu)化。常見的優(yōu)化手段有增加索引、優(yōu)化索引、讀寫分離、增加數(shù)據(jù)庫的從庫等等。當我們使用這些手段都無法解決的時候,就需要來考慮分庫分表。

                單表出現(xiàn)瓶頸:

                • 單表數(shù)據(jù)量較大,導(dǎo)致讀寫性能較慢。

                單庫出現(xiàn)瓶頸:

                • CPU壓力過大(busy、load過高),導(dǎo)致讀寫性能較慢。
                • 內(nèi)存不足(緩存池命中率較低、磁盤讀寫IOPS過高),導(dǎo)致讀寫性能較慢。
                • 磁盤空間不足,導(dǎo)致無法正常寫入數(shù)據(jù)。
                • 網(wǎng)絡(luò)帶寬不足,導(dǎo)致讀寫性能較慢。

                單表超過千萬級,就需要進行分庫分表?

                這種說法不完全準確。因為有的表它本身的結(jié)構(gòu)比較簡單,字段也比較少。這種表可能即使數(shù)據(jù)量已經(jīng)超過了億級,整體的讀寫性能也是比較高的。而有的表如果整體的結(jié)構(gòu)比較復(fù)雜,字段本身也比較大,可能只是百萬級,整體的性能已經(jīng)比較慢了。所以這個還是得結(jié)合自己的業(yè)務(wù)情況來進行分析。這個千萬級只能是作為一個參考。

                如何選擇分庫分表

                只分表:

                • 單表數(shù)據(jù)量較大,單表讀寫性能出現(xiàn)瓶頸。
                • 經(jīng)過評估單庫的容量和性能可以支撐未來幾年的增長。

                只分庫:

                • 數(shù)據(jù)庫(讀)寫壓力較大,數(shù)據(jù)庫出現(xiàn)存儲性能瓶頸。

                分庫分表:

                • 單表數(shù)據(jù)量較大,單表讀寫性能出現(xiàn)瓶頸。
                • 數(shù)據(jù)庫(讀)寫壓力較大,數(shù)據(jù)庫出現(xiàn)存儲性能瓶頸。

                注意點:

                我們在進行選擇的時候,必須以未來三到五年的業(yè)務(wù)發(fā)展情況去進行評估。不能只是以當前的數(shù)據(jù)量和業(yè)務(wù)量來進行評估。否則可能就會出現(xiàn)頻繁的進行分庫分表的情況。因為分庫分表整體的代價是比較大的。所以我們最好是進行充分的評估,保證最少可以支撐未來三到五年的業(yè)務(wù)增長。

                小結(jié)

                當數(shù)據(jù)庫出現(xiàn)了讀寫性能瓶頸的時候,我們優(yōu)先使用一些比較常規(guī)的優(yōu)化手段來進行解決。例如比較常見的有:增加索引、優(yōu)化索引、讀寫分離、增加從庫等方式。

                如果使用這些常規(guī)的手段也無法解決的時候啊,我們才會去考慮用分庫分表來進行解決。

                在使用分庫分表的時候,必須充分考慮業(yè)務(wù)未來的整體發(fā)展。至少做到這次分庫分表之后,未來的三到五年內(nèi)不需要再進行分庫分表。

                拆分完整流程概覽

                1、評估是否需要拆分。主要就是評估是否有其他更輕量的優(yōu)化手段可以解決問題,從而可以避免進行分庫分表。

                2、拆分詳細技術(shù)方案設(shè)計。最核心的內(nèi)容是拆分SOP,也是我們今天后續(xù)要詳細講的內(nèi)容。

                3、技術(shù)方案評審優(yōu)化。分庫分表的整體改動比較大,需要讓大家一起評估下方案是否有問題,或者是否存在可以優(yōu)化的地方。

                4、同步相關(guān)影響方。拆分可能需要一些下游配合改造,需要提前周知他們。

                5、正式進入拆分。

                接下來我們來看一下拆分的SOP。

                拆分SOP(核心)

                1、目標評估。

                我們首先要評估本次拆分需要拆成幾個庫和幾個表,這個主要取決于我們的拆分目標,例如:讀寫能力要提升到現(xiàn)在的X倍、負載降低Y%、容量要支撐未來的Z年發(fā)展等等。

                在大多數(shù)情況下,我們可以將單表的行數(shù)作為一個重要參考指標,例如將單表控制在千萬級以下。特殊情況下如果你要拆分的表單行數(shù)據(jù)很大,例如字段很多或者某字段很大,這種情況你需要結(jié)合實際的性能表現(xiàn)去評估一個合理的值。

                一個例子:當前數(shù)據(jù)20億,5年后評估為100億。分幾個表?分幾個庫?

                解答:一個合理的答案,1024個表,16個庫。按1024個表算,拆分完單表200萬,5年后為1000萬。

                2、切分策略

                當前主流的方案有3種:范圍切分、中間表映射、hash切分。

                范圍切分

                范圍切分是指按某個字段的區(qū)間來進行切分。例如每個表放1000萬數(shù)據(jù),id從0~1000萬的放在第一個表,1000萬~2000萬放在第2個表,依次類推。

                優(yōu)點:后續(xù)擴容很方便,無需進行遷移數(shù)據(jù),甚至可以將后續(xù)的表擴容、數(shù)據(jù)庫擴庫全部做到自動化。

                缺點:存在明顯的寫偏移,寫流量其實是全部集中在最新的表上。因此范圍切分并沒有起到將寫流量均勻分攤到各個庫各個表的效果,同時讀流量可能也會存在偏移,因為一般來說,最近增加的數(shù)據(jù)被查詢的概率通常會更大一點。

                中間表映射

                中間表映射是將分表鍵和數(shù)據(jù)庫的映射關(guān)系記錄在一個單獨的表中,每次路由前先查詢該表,得到具體路由的數(shù)據(jù)庫,然后進行操作。

                優(yōu)點:很靈活,可以隨意設(shè)置路由規(guī)則。

                缺點:引入了額外的單點,增加了復(fù)雜度,這個映射表可能也會很大,并且其查詢QPS會非常高,怎么保障高性能和高可用會是一個新的問題。

                Hash切分

                通過對分表鍵進行一定的運算(通常是取模),從而決定路由到哪個庫哪個表。

                優(yōu)點:數(shù)據(jù)分片比較均勻,讀寫也會比較均勻的分攤到各個庫和各個表。

                缺點:可能存在跨節(jié)點查詢和分頁等問題。

                小結(jié)

                目前大多數(shù)互聯(lián)網(wǎng)服務(wù)主要使用的是hash切分。

                范圍切分存在寫流量集中在單表的問題,這個會有嚴重的寫性能問題,特別是隨著業(yè)務(wù)的發(fā)展,寫流量的QPS會越來越高,這個會成為一個嚴重的瓶頸,目前看這個方案可能更適合一些歸檔類的功能。

                中間表映射的方案則是太復(fù)雜了,如果你的映射數(shù)據(jù)太多的話,甚至有可能這個映射表也需要進行分庫分表,那就進入惡性循環(huán)了。

                不過,雖然中間表映射雖然有一些問題,但是我覺得可能在一些特殊的場景下可以使用,例如大商家問題。如果有少量商家的數(shù)據(jù)量特別大,導(dǎo)致出現(xiàn)偏移,一種思路是將這些商家的數(shù)據(jù)使用單獨的表存放,這部分大商家通過中間表映射路由,其他的商家還是走hash路由。當然,這只是一個簡單的思考,沒有經(jīng)過嚴格的驗證。

                3、選擇分表字

                在單庫單表的時候,全部數(shù)據(jù)都放在一張表中,因此我們可以隨意的進行 join 操作和分頁操作,但是如果進行了分庫分表,數(shù)據(jù)會分到不同的數(shù)據(jù)庫和數(shù)據(jù)表上,可能導(dǎo)致原本進行分頁的數(shù)據(jù)分到了不同的數(shù)據(jù)庫中,從而導(dǎo)致跨庫查詢等問題。而分表字段就是決定數(shù)據(jù)如何劃分的關(guān)鍵因素,通過合理的選擇分表字段,我們可以將原本需要進行分頁的數(shù)據(jù)劃分到同一張表上,從而避免跨庫查詢的問題。

                例子:以美團外賣的商品數(shù)據(jù)為例,我們可以思考下主要有哪些查詢商品的場景。

                第一個是用戶視角,我們在點外賣時需要查詢商品,但是我們在點外賣時會首先進入到商家頁面,所以這個地方有商家id字段。

                第二個是商家視角,商家在后臺管理自己的商品,這個地方也有商家id字段。

                因此在美團外賣商品數(shù)據(jù)的這個例子中,商家id字段作為分表鍵就是一個比較合理的選擇,因為他覆蓋了最高頻的幾個使用場景。

                一個例子:10個庫,1000張表:0~99、100~199、200~299、…

                分表字段:shopId,值為1234

                數(shù)據(jù)表編號:shopId % 1000 = 1234 % 1000 = 234

                數(shù)據(jù)庫編號:shopId % 1000 / 10 = 1234 % 1000 / 10 = 2

                4、資源準備和代碼改造

                新集群的所需數(shù)據(jù)庫資源可以盡早跟DBA申請,特別是拆分集群比較多的情況,一方面是因為DBA搭建新集群需要花一定的時間,另一方面是避免出現(xiàn)資源不足導(dǎo)致延期的情況。

                至于代碼的改造,主要會涉及到幾個部分:

                • 將新集群的數(shù)據(jù)源引入到我們的服務(wù)中
                • 支持靈活的灰度讀寫操作
                • 第三是數(shù)據(jù)全量遷移、一致性校驗等任務(wù)

                因為整個分庫分表過程是不停機,并且無損的拆分,因此拆分過程中新老數(shù)據(jù)源會同時存在一段時間,在這段灰度期間,我們會通過配置中心和相關(guān)規(guī)則去靈活的控制究竟是寫新庫、寫老庫,還是雙寫,讀操作也類似。

                5、增量數(shù)據(jù)同步(雙寫)

                雙寫是為了保證增量數(shù)據(jù)在新庫和老庫都存在。

                寫新庫是因為我們后續(xù)準備切換到新庫,因此新庫必須要有全部的數(shù)據(jù)。

                寫老庫是因為我們不確定拆分過程中是否存在問題,通過寫老保證了老庫有全部的數(shù)據(jù),這樣萬一新流程有問題的時候,我們可以即使切回老庫的流程。從而保障了服務(wù)的可用性和穩(wěn)定性。

                常見方案:

                • 同步雙寫,在所有寫數(shù)據(jù)庫的地方進行修改,修改成寫兩份數(shù)據(jù)。當然,這個地方一般不會去修改全部的寫邏輯,而是在底層使用AOP來實現(xiàn)。
                • 異步雙寫:寫老庫,監(jiān)聽binlog異步同步到新庫
                • 中間件同步工具:通過一定的規(guī)則將數(shù)據(jù)同步到目標庫表

                異步雙寫和中間件工具同步兩者本質(zhì)上類似,都是通過binlog的方式將數(shù)據(jù)寫入到新庫。只不過一個是你自己做,一個是中間件團隊幫你做。

                這幾種方式一般來說不會差別太大,同步雙寫的寫入延遲可能會稍微小一點。

                6、全量數(shù)據(jù)遷移

                光有增量數(shù)據(jù)同步還沒法保證新庫有全部的數(shù)據(jù),我們還需要將以前的老數(shù)據(jù)全部遷移到新庫中。通過增量同步+全量遷移,我們才能保證新庫有完整的數(shù)據(jù)。

                常見方案:

                • 自己開發(fā)一個任務(wù)將老庫數(shù)據(jù)遷移到新庫。
                • 使用中間件同步工具,將老庫數(shù)據(jù)同步到新庫。如果中間件有現(xiàn)成工具支持的話,一般建議好接使用現(xiàn)成的工具,這樣自己就不用再花時間去額外開發(fā)了。

                注意點:

                • 控制好同步速率
                • 增量同步和全量遷移會同時進行,因此可能會存在并發(fā)寫同一條數(shù)據(jù),從而可能導(dǎo)致一些數(shù)據(jù)不一致的問題。

                7、數(shù)據(jù)校驗、優(yōu)化和補償

                在全量數(shù)據(jù)遷移完畢,增量同步也正常運行后,并不能直接將流量切到新庫。因為可能存在很多情況,導(dǎo)致新庫和老庫的數(shù)據(jù)可能沒法完全一致。

                例如:我們的改造存在遺漏的地方,或者說并發(fā)修改導(dǎo)致數(shù)據(jù)問題,等等。因此,我們需要進行新老庫的數(shù)據(jù)校驗和補償,直到新老庫的數(shù)據(jù)一致了,才能進行流量切換。

                方案:

                • 增量數(shù)據(jù)校驗
                • 全量數(shù)據(jù)校驗
                • 人工抽檢

                核心流程:

                • 讀取老庫數(shù)據(jù)
                • 讀取新庫數(shù)據(jù)
                • 比較新老庫數(shù)據(jù),一致則繼續(xù)比較下一條數(shù)據(jù)
                • 不一致則進行補償:
                  • 新庫存在,老庫不存在:新庫刪除數(shù)據(jù)
                  • 新庫不存在,老庫存在:新庫插入數(shù)據(jù)
                  • 新庫存在、老庫存在:比較所有字段,不一致則將新庫更新為老庫數(shù)據(jù)

                注意點:

                數(shù)據(jù)校驗是整個流程中最重要,通常也是花時間最多的一步。一方面是在并發(fā)下會出現(xiàn)很多種不一致的場景,另外是因為這一步是切讀之前的最后一個保障,因此我們必須再三確認數(shù)據(jù)是正確的。否則,切讀后可能就會導(dǎo)致一些線上問題。

                8、灰度切讀

                在數(shù)據(jù)一致性校驗通過后,我們開始將部分讀流量切換到新數(shù)據(jù)庫。

                這一步必須遵循以下幾個原則:

                • 必須支持靈活的切換,有問題可以及時切回老庫。
                • 支持靈活的灰度規(guī)則,灰度早期我們會先拿少量門店進行灰度,觀察一段時間,如果沒問題再繼續(xù)增加灰度門店。依此類推,然后到后面開始逐步使用比例來進行灰度,直到最終我們將全部流量都切到新的數(shù)據(jù)庫上。
                • 灰度放量先慢后快,每次放量觀察一段時間

                9、binlog 切新庫

                在讀流量全部切換到新庫后,此時新流程已經(jīng)驗證通過,我們開始為停寫老庫做準備,首先就是將監(jiān)聽的 binlog 從老庫切換到新庫。

                核心流程:

                • 啟動新庫的 binlog,此時下游會同時收到新老庫的 binlog
                • 觀察一段時間是否正常
                • 如果不正在,則將新庫的 binlog 關(guān)閉,排查修復(fù)問題
                • 如果一切正常,則將老庫的 binlog 關(guān)閉,此時監(jiān)聽的 binlog 切換到新庫

                注意點:

                監(jiān)聽 binlog 的流程我們一般會收斂在團隊內(nèi)部,如果外部團隊想監(jiān)聽 binlog,一般會使用我們封裝過的消息,這樣在改造時,對外部團隊就基本沒有影響,我們改造起來也比較方便。

                10、下游切換數(shù)據(jù)源

                目前來看,除了 binlog 之外,主要的下游是數(shù)倉。數(shù)倉會將商品數(shù)據(jù)定期同步到 hive 上,用于進行數(shù)據(jù)的相關(guān)工作,因此需要讓數(shù)倉同學將數(shù)據(jù)源切換到新數(shù)據(jù)源。

                數(shù)倉一般是定期同步數(shù)據(jù),例如一天同步一次全量數(shù)據(jù),對實時性要求不高,因此只需在指定時間內(nèi)切換即可。

                11、停寫老庫

                在我們確認老庫數(shù)據(jù)源的所有依賴都切換和下線后,停寫老庫,此時讀寫流程全部切換到新數(shù)據(jù)源。至此,整個拆分流程基本結(jié)束。

                完整SOP

                最后我們通過一張流程圖來回顧下整個拆分流程,整個流程主要包含5個階段。

                第一階段:拆分前的相關(guān)準備,包含了拆分的目標評估、切分策略和分表字段的選擇,還有數(shù)據(jù)庫相關(guān)資源的準備。

                第二階段:代碼改造,主要是將新數(shù)據(jù)源引入到服務(wù)中,同時支持靈活的灰度讀寫。

                第三階段:數(shù)據(jù)遷移,包含了全量和增量數(shù)據(jù)遷移,還有數(shù)據(jù)一致性的校驗和修復(fù)。

                第四階段:流量遷移,主要是將數(shù)據(jù)庫的讀寫流量按灰度規(guī)則逐步切換到新庫。

                第五階段:停寫老庫,當讀寫流量全部遷移到新庫,老庫的相關(guān)依賴都全部下線后,停寫老庫并釋放相關(guān)資源。

                相關(guān)工具

                1、binlog監(jiān)聽工具

                • Databus
                • Canal

                關(guān)于binlog

                binlog是一個二進制文件,用于記錄數(shù)據(jù)庫表結(jié)構(gòu)和表記錄的變更。簡單點說,就是通過 binlog 文件你可以知道數(shù)據(jù)庫中究竟哪些數(shù)據(jù)發(fā)生了變更,從什么變成了什么。

                而binlog監(jiān)聽工具主要就是用于監(jiān)聽MySQL產(chǎn)生的binlog,然后進行解析,解析成我們比較容易懂的格式,最后通過一定的手段發(fā)送到下游,例如比較常見的方式是消息隊列。

                在分庫分表中就可以通過binlog監(jiān)聽工具來將老庫的數(shù)據(jù)變更實時同步到新庫中,以保證新老庫的數(shù)據(jù)一致。

                2、分庫分表工具

                目前主要有兩種,一種是增強版JDBC驅(qū)動,另一種是數(shù)據(jù)庫代理。

                1)增強版JDBC驅(qū)動

                以客戶端 jar 包形式提供了對 JDBC 的封裝,客戶端直連數(shù)據(jù)庫

                開源:Sharding-JDBC、TDDL、Zebra

                2)數(shù)據(jù)庫代理

                需要單獨部署,客戶端連接代理服務(wù),代理服務(wù)負責跟數(shù)據(jù)庫打交道。

                開源:Sharding-Proxy、MyCat

                兩種方案的核心思想都是類似的,就是他們負責將分庫分表的邏輯進行抽象封裝,做到讓分庫分表對使用方無感知,使用方只需按照制定的規(guī)則進行簡單的配置和開發(fā),就可以像沒有分庫分表一樣正常的使用分庫分表規(guī)則了。

                兩者的主要區(qū)別在于使用增強版JDBC驅(qū)動只需要依賴一個jar包,此時應(yīng)用服務(wù)還是直連數(shù)據(jù)庫的。

                而數(shù)據(jù)庫代理則需要額外部署一個單獨的代理服務(wù),應(yīng)用服務(wù)從之前的直連數(shù)據(jù)庫,變成調(diào)用代理服務(wù),由代理服務(wù)來負責跟數(shù)據(jù)庫打交道。

                目前使用的比較廣泛的是增強版JDBC驅(qū)動,一方面是增強版JDBC驅(qū)動比較輕量,另外是性能也會比較好。

                分庫分表問題

                在我們使用分庫分表之后,系統(tǒng)的性能和容量都會有很大的提升,但是也會隨之帶來一些問題。我們一起來看一下有哪些問題,當前的主流方案是如何解決的。

                1、分布式唯一ID

                在單庫單表情況下,我們使用表的自增ID就可以保證ID的唯一性,但是分庫分表后,一張表被拆成了多張表,此時自增ID就沒辦法保證唯一性了。因此,需要引入一種方案來保證ID的唯一性。

                目前主流的方案有3種:UUID、雪花算法、號段模式。

                UUID

                UUID相信大家都不陌生,UUID是JDK中自帶的一個工具類。什么都不需要引入就可以直接使用了,同時因為是本地生成的,性能也非常好。

                但是UUID并不適合拿來做MySQl數(shù)據(jù)庫的主鍵,MySQL的主鍵一般推薦使用單調(diào)遞增的數(shù)字,這個因為MySQL主鍵使用的是聚簇索引,會把相鄰主鍵的數(shù)據(jù)放在相鄰的物理存儲位置上。

                當MySQL的主鍵是單調(diào)遞增時,每次只需要簡單的將數(shù)據(jù)追加到索引的最后面即可,類似于順序?qū)懘疟P。而如果MySQL的主鍵是無序的,則可能需要將數(shù)據(jù)插入到之前已有的數(shù)據(jù)中間。如果這個插入位置所在的數(shù)據(jù)頁不在內(nèi)存中,則需要先從磁盤讀取到內(nèi)存中,這會導(dǎo)致產(chǎn)生磁盤的隨機IO。同時,如果該數(shù)據(jù)頁的空間不足,則可能會產(chǎn)生頁分裂,導(dǎo)致需要移動大量數(shù)據(jù)。

                最后就是,MySQL的普通索引需要存儲主鍵索引值,如果主鍵值更占用空間了,會導(dǎo)致普通索引的B+樹層高變高,磁盤IO次數(shù)變多,最終導(dǎo)致性能變慢。

                雪花算法

                雪花算法的核心思想是通過一定的規(guī)則生成一個64位的long類型數(shù)字。除了最高位的1位不用之外,其他63位由三部分組成。分別是41位用于存儲時間戳,10位用于存儲機器ID,12位用于存儲序列號。

                簡單來說就是支持部署1024臺服務(wù)器,同時每臺服務(wù)器1毫秒最多可以生成4096個ID,也就是每秒可以生成四百零九萬個,并且可以使用69年。

                這個量級應(yīng)該基本可以滿足任何業(yè)務(wù)了,當然在實際使用過程中,這三部分的位數(shù)可以結(jié)合自己的場景去進行修改。

                號段模式

                在講號段模式之前,我們先介紹下數(shù)據(jù)庫生成的方式。

                數(shù)據(jù)庫生成指的是使用一個額外表的自增ID來作為分布式ID,因為ID都是由同一張表自增生成,所以可以保證全局唯一性。但是這種方案有個嚴重的問題,每次使用分布式唯一ID都需要來讀寫這張表。一旦并發(fā)量比較大,數(shù)據(jù)庫會有嚴重的性能問題。

                號段模式就是在此基礎(chǔ)上進行了優(yōu)化,之前是每次獲取分布式ID都需要讀寫數(shù)據(jù)庫,號段模式優(yōu)化成批量的方式,每次讀寫數(shù)據(jù)庫時獲取一批ID,例如每次獲取1000個,將這1000個ID放在本地緩存中,1000個用完之后再來申請下一批,從而大大降低數(shù)據(jù)庫的讀寫壓力。

                小結(jié)

                這三種方案中,目前應(yīng)用的比較廣泛的是雪花算法和號段模式,美團開源的分布式ID生成組件 Leaf 就是提供了這兩種方案,如果大家對底層細節(jié)感興趣的話,可以去自己下載源碼來看。

                最后需要說一下的是,對于訂單ID這種比較特殊的字段來說,一般可能不會直接使用上述的方案,而是會按照一定的規(guī)則去生成。同時可能會攜帶一些業(yè)務(wù)字段,例如用戶ID和商家ID。

                2、分布式事務(wù)

                在分庫分表之前,全部的表都在同一個庫里,我們可以使用本地事務(wù)來保障數(shù)據(jù)的正確性。引入了分庫分表之后,數(shù)據(jù)庫表被分到不同的數(shù)據(jù)庫中,此時就沒辦法使用本地事務(wù)了,因此就需要引入分布式事務(wù)來保障數(shù)據(jù)的正確性,我們來看一下當前有哪些常見的分布式事務(wù)。

                2PC

                兩階段提交,核心思想是將事務(wù)操作分為兩個階段。

                第一階段:協(xié)調(diào)者首先詢問所有的事務(wù)參與者是否可以執(zhí)行事務(wù)提交操作。

                第二階段:協(xié)調(diào)者根據(jù)所有參與者的返回結(jié)果決定是否提交事務(wù),如果全部的參與者都返回成功,則協(xié)調(diào)者向所有參與者發(fā)送事務(wù)提交請求。否則,協(xié)調(diào)者向所有參與者發(fā)送事務(wù)中斷回滾請求。

                兩階段提交是目前比較出名也是用的相對比較多的分布式事務(wù),優(yōu)點是整體流程比較簡單,缺點是存在同步阻塞、協(xié)調(diào)者單點等問題。

                TCC

                核心思想是針對每個操作都有一個對應(yīng)的確認和取消操作。

                TCC中有主服務(wù)和從服務(wù)兩個角色,例如在下單的流程中,首先會走到交易服務(wù),然后交易服務(wù)分別請求定訂單服務(wù)和庫存服務(wù)進行訂單創(chuàng)建和庫存扣減,此時交易服務(wù)就是主服務(wù),而訂單服務(wù)和庫存服務(wù)為從服務(wù)。

                TCC的核心流程如下:

                首先,主服務(wù)調(diào)用所有從服務(wù)的try接口,進行業(yè)務(wù)檢查和資源預(yù)留。

                接著,主服務(wù)根據(jù)所有從服務(wù)的返回結(jié)果決定是否提交事務(wù),如果所有從服務(wù)都返回成功,則調(diào)用所有從服務(wù)的confirm接口執(zhí)行事務(wù)確認提交操作。否則,調(diào)用所有從服務(wù)的cancel接口執(zhí)行事務(wù)取消,并釋放預(yù)留資源。

                估計大家應(yīng)該發(fā)現(xiàn)了,TCC其實跟兩階段提交非常像。其實很多分布式事務(wù)的思想都是很類似的,核心都是先詢問,然后提交。這兩者的主要區(qū)別在于TCC是應(yīng)用層的處理,而兩階段提交是數(shù)據(jù)庫層面的處理。

                這兩種分布式事務(wù)應(yīng)該是目前分布式事務(wù)中比較出名的了,其他的分布式事務(wù)還有三階段提交、本地消息表、事務(wù)消息等等,這邊不做過多的介紹,有興趣的可以自己查閱資料。

                高并發(fā)業(yè)務(wù)實際使用

                首先說一下結(jié)論:在實際的高并發(fā)業(yè)務(wù)中一般都不會使用強一致性的分布式事務(wù),金融場景是個特例,因為涉及到太多錢了,所以可能會用強一致性的分布式事務(wù)。

                更多的是通過各種各樣的手段來保證最終的一致性,常見的手段有:回滾、重試、監(jiān)控、告警、冪等、對賬等等,終極手段就是人工補償。

                我之前在某篇文章中說過:每個看著光鮮亮麗的系統(tǒng)背后可能都有一堆苦逼的程序員在默默的修數(shù)據(jù),這個不是開玩笑的。

                例子:

                以外賣下單為例,整個用戶下單流程會涉及到很多步驟,最核心的包括:創(chuàng)建訂單、扣減商品庫存、核銷優(yōu)惠券、核銷會員紅包等等,如果其中有一步失敗,則會導(dǎo)致整個下單流程失敗,需要將其他的流程都進行回滾,以保證不會產(chǎn)生資損,否則有可能出現(xiàn)用戶下單失敗,但是會員紅包卻被扣掉等情況。

                為了避免網(wǎng)絡(luò)抖動等情況導(dǎo)致回滾失敗,一般都會有回滾重試流程,但是重試一般會有次數(shù)上限,因為如果重試多次還是失敗,則可能是其他問題,例如代碼BUG,這種情況再怎么重試也沒用。因此在重試達到上限后,如果還是回滾失敗,則需要發(fā)送告警,人為介入排查,然后人工修復(fù)這些數(shù)據(jù)。

                而對于這些訂單的下游服務(wù)來說,例如庫存、優(yōu)惠券等等,就需要做好接口的冪等,如果沒做好冪等,可能會導(dǎo)致數(shù)據(jù)出現(xiàn)重復(fù)回滾,造成數(shù)據(jù)錯誤和資損。

                當然,從廣義上來說,保證最終一致性,也是屬于分布式事務(wù)的一種。

                為什么不直接使用強一致性事務(wù)?

                個人覺得主要有以下幾個原因:

                • 會帶來嚴重的性能損耗,導(dǎo)致下單流程的耗時增加,最終導(dǎo)致服務(wù)吞吐量下降、用戶下單體驗變差。
                • 會引入額外的復(fù)雜度,開發(fā)和維護成本較高。
                • 實際業(yè)務(wù)中,由于部分成功導(dǎo)致數(shù)據(jù)不一致的場景,發(fā)生的概率比較低。

                總結(jié)來說就是一個取舍的問題,目前大部分業(yè)務(wù)場景,使用強一致性分布式事務(wù)的ROI不夠高,因此一般不會選擇強一致性事務(wù),而是選擇柔性事務(wù),保障事務(wù)的最終一致性。

                3、跨庫JOIN/分頁查詢問題

                在單庫單表的時候,全部數(shù)據(jù)都放在一張表中,因此我們可以隨意的進行 join 和分頁操作,但是如果進行了分庫分表,數(shù)據(jù)會分到不同的數(shù)據(jù)庫和數(shù)據(jù)表上,可能導(dǎo)致原本進行分頁的數(shù)據(jù)分到了不同的數(shù)據(jù)庫中,從而導(dǎo)致跨庫查詢問題。

                目前業(yè)界主流解決方案有以下幾種。

                1)選擇合適的分表字段

                這個在上文已經(jīng)詳細解釋過了??偨Y(jié)來說就是,分表字段的選擇,要能保證絕大部分高頻查詢場景,不會出現(xiàn)跨庫的問題。在實際業(yè)務(wù)中,分表字段選擇合理的話,基本可以避免95%,甚至99%以上的跨庫查詢問題,從而將問題的難度大大降低了。

                2)使用搜索引擎支持,例如ES

                我們可以將全量數(shù)據(jù)冗余一份到ES中,當出現(xiàn)分表字段支持不了的跨庫查詢時,可以使用ES來支持。除此之外,ES也會用于支持一些復(fù)雜搜索查詢請求。

                使用ES需要注意的是:

                • ES只存儲需要進行搜索的字段,查詢完ES后再根據(jù)關(guān)鍵字段去數(shù)據(jù)庫查詢完整的數(shù)據(jù),這樣是為了控制ES的大小,否則ES會容易過大,導(dǎo)致性能和存儲問題。
                • ES只用于支持數(shù)據(jù)庫難以支持的查詢,就如上面說的跨庫查詢、復(fù)雜搜索查詢,這種復(fù)雜的查詢一般不會太多,因此可以保障ES的整體壓力不會太大。

                3)分開查詢,內(nèi)存中聚合

                這個方案跟使用join其實大同小異。區(qū)別在于,join是數(shù)據(jù)庫來做這個聚合操作,分開查詢是應(yīng)用層面來做聚合操作。

                即使不分庫分表,當表的數(shù)據(jù)量比較大時,通常也是建議不要在數(shù)據(jù)庫中使用join操作,而是分開查詢,然后在應(yīng)用層內(nèi)存中聚合。

                這是因為數(shù)據(jù)庫資源相對應(yīng)用服務(wù)器來說會更寶貴,通常也更容易成為鏈路中的瓶頸,因此盡量不要讓其做復(fù)雜的查詢,避免占用過多的數(shù)據(jù)庫資源。

                注意點:

                • 查詢出來的數(shù)據(jù)量
                • 占用內(nèi)存情況

                4)冗余字段

                如果每次join操作只是為了獲取少量的字段,那么可以考慮直接將這些字段冗余到表上。

                小結(jié)

                這幾種方案在實際工作中都挺常使用的,一般看具體的業(yè)務(wù)場景選擇合適的方案即可。

                原文出自公眾號:程序員囧輝

                原文鏈接:https://mp.weixin.qq.com/s/X7ciEPZWLzgg_fnsCsr6wg

                鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
                用戶投稿
                上一篇 2022年6月18日 15:04
                下一篇 2022年6月18日 15:05

                相關(guān)推薦

                聯(lián)系我們

                聯(lián)系郵箱:admin#wlmqw.com
                工作時間:周一至周五,10:30-18:30,節(jié)假日休息