您現在的位置是:首頁 > 娛樂

阿里大牛的資料庫最佳化方案:寫入資料量增加時,如何實現分庫分表?

由 Java架構師丨蘇先生 發表于 娛樂2021-07-07
簡介當然,雖然分庫分表會對我們使用資料庫帶來一些不便,但是相比它所帶來的擴充套件性和效能方面的提升,我們還是需要做的,因為,經歷過分庫分表後的系統,才能夠突破單機的容量和請求量的瓶頸,就比如說,我在開篇提到的我們的電商系統,它正是經歷了分庫分表

表太多分成資料庫對效能有影響嗎

之前個大家分享給在高併發下資料庫的一種最佳化方案:讀寫分離,它就是依靠主從複製的技術使得資料庫實現了資料複製為多份,增強了抵抗大量併發讀請求的能力,提升了資料庫的查詢效能的同時,也提升了資料的安全性,當某一個數據庫節點,無論是主庫還是從庫發生故障時,我們還有其他的節點中儲存著全量的資料,保證資料不會丟失。此時,你的電商系統的架構圖變成了下面這樣:

阿里大牛的資料庫最佳化方案:寫入資料量增加時,如何實現分庫分表?

這時,公司 CEO 突然傳來一個好訊息,運營推廣持續帶來了流量,你所設計的電商系統的訂單量突破了五千萬,訂單資料都是單表儲存的,你的壓力倍增,因為無論是資料庫的查詢還是寫入效能都在下降,資料庫的磁碟空間也在報警。所以,你主動分析現階段自己需要考慮的問題,並尋求高效的解決方式,以便系統能正常運轉下去。你考慮的問題主要有以下幾點:

系統正在持續不斷地的發展,註冊的使用者越來越多,產生的訂單越來越多,資料庫中儲存的資料也越來越多,單個表的資料量超過了千萬甚至到了億級別。這時即使你使用了索引,索引佔用的空間也隨著資料量的增長而增大,資料庫就無法快取全量的索引資訊,那麼就需要從磁碟上讀取索引資料,就會影響到查詢的效能了。

那麼這時你要如何提升查詢效能呢

資料量的增加也佔據了磁碟的空間,資料庫在備份和恢復的時間變長,

你如何讓資料庫系統支援如此大的資料量呢

不同模組的資料,比如使用者資料和使用者關係資料,全都儲存在一個主庫中,一旦主庫發生故障,所有的模組兒都會受到影響,

那麼如何做到不同模組的故障隔離呢

你已經知道了,在 4 核 8G 的雲伺服器上對 MySQL5。7 做 Benchmark,大概可以支撐500TPS 和 10000QPS,你可以看到資料庫對於寫入效能要弱於資料查詢的能力,那麼隨著系統寫入請求量的增長,

資料庫系統如何來處理更高的併發寫入請求呢

這些問題你可以歸納成,資料庫的寫入請求量大造成的效能和可用性方面的問題,要解決這些問題,你所採取的措施就是對資料進行分片,對資料進行分片,可以很好地分攤資料庫的讀寫壓力,也可以突破單機的儲存瓶頸,而常見的一種方式是對資料庫做“分庫分表”。

分庫分表是一個很常見的技術方案,你應該有所瞭解。那你會說了:“既然這個技術很普遍,而我又有所瞭解,那你為什麼還要提及這個話題呢?”因為以我過往的經驗來看,不少人會在“分庫分表”這裡踩坑,主要體現在:

對如何使用正確的分庫分表方式一知半解,沒有明白使用場景和方法。比如,一些同學會在查詢時不使用分割槽鍵;

分庫分表引入了一些問題後,沒有找到合適的解決方案。比如,會在查詢時使用大量連表查詢等等。

本文就帶你解決這兩個問題,從常人容易踩坑的地方,跳出來。

如何對資料庫做垂直拆分

分庫分表是一種常見的將資料分片的方式,它的基本思想是依照某一種策略將資料儘量平均的分配到多個數據庫節點或者多個表中。

不同於主從複製時資料是全量地被複製到多個節點,分庫分表後,每個節點只儲存部分的資料,這樣可以有效地減少單個數據庫節點和單個數據表中儲存的資料量,在解決了資料儲存瓶頸的同時也能有效的提升資料查詢的效能。同時,因為資料被分配到多個數據庫節點上,那麼資料的寫入請求也從請求單一主庫變成了請求多個數據分片節點,在一定程度上也會提升併發寫入的效能。

比如,我之前做過一個直播專案,在這個專案中,需要儲存使用者在直播間中發的訊息以及直播間中的系統訊息,你知道這些訊息量極大,有些比較火的直播間有上萬條留言是很常見的事兒,日積月累下來就積攢了幾億的資料,查詢的效能和儲存空間都扛不住了。沒辦法,就只能加班加點重構,啟動多個數據庫來分攤寫入壓力和容量的壓力,也需要將原來單庫的資料遷移到新啟動的資料庫節點上,好在最後成功完成分庫分表和資料遷移校驗工作,不過也著實花費了不少的時間和精力。

資料庫分庫分表的方式有兩種:一種是垂直拆分,另一種是水平拆分。這兩種方式,在我看來,掌握拆分方式是關鍵,理解拆分原理是核心。所以你在學習時,最好可以結合自身業務來思考。

垂直拆分,顧名思義就是對資料庫豎著拆分,也就是將資料庫的表拆分到多個不同的資料庫中。

垂直拆分的原則一般是按照業務型別來拆分,核心思想是專庫專用,將業務耦合度比較高的表拆分到單獨的庫中。舉個形象的例子就是在整理衣服的時候,將羽絨服、毛衣、T 恤分別放在不同的格子裡。這樣可以解決我在開篇提到的第三個問題:把不同的業務的資料分拆到不同的資料庫節點上,這樣一旦資料庫發生故障時只會影響到某一個模組的功能,不會影響到整體功能,從而實現了資料層面的故障隔離。

我還是以微博系統為例來給你說明一下

在微博系統中有和使用者相關的表,有和內容相關的表,有和關係相關的表,這些表都儲存在主庫中。在拆分後,我們期望使用者相關的表分拆到使用者庫中,內容相關的表分拆到內容庫中,關係相關的表分拆到關係庫中。

阿里大牛的資料庫最佳化方案:寫入資料量增加時,如何實現分庫分表?

對資料庫進行垂直拆分是一種偏常規的方式,這種方式其實你會比較常用,不過拆分之後,雖然可以暫時緩解儲存容量的瓶頸,但並不是萬事大吉,因為資料庫垂直拆分後依然不能解決某一個業務模組的資料大量膨脹的問題,一旦你的系統遭遇某一個業務庫的資料量暴增,在這個情況下,你還需要繼續尋找可以彌補的方式。

比如微博關係量早已經過了千億,單一的資料庫或者資料表已經遠遠不能滿足儲存和查詢的需求了,這個時候,你需要將資料拆分到多個數據庫和資料表中,也就是對資料庫和資料表做水平拆分了。

如何對資料庫做水平拆分

和垂直拆分的關注點不同,垂直拆分的關注點在於業務相關性,而水平拆分指的是將單一資料表按照某一種規則拆分到多個數據庫和多個數據表中,關注點在資料的特點。

拆分的規則有下面這兩種

按照某一個欄位的雜湊值做拆分,這種拆分規則比較適用於實體表,比如說使用者表,內容表,我們一般按照這些實體表的 ID 欄位來拆分。比如說我們想把使用者表拆分成 16 個庫,64 張表,那麼可以先對使用者 ID 做雜湊,雜湊的目的是將 ID 儘量打散,然後再對 16取餘,這樣就得到了分庫後的索引值;對 64 取餘,就得到了分表後的索引值。

阿里大牛的資料庫最佳化方案:寫入資料量增加時,如何實現分庫分表?

另一種比較常用的是按照某一個欄位的區間來拆分,比較常用的是時間欄位。你知道在內容表裡面有“建立時間”的欄位,而我們也是按照時間來檢視一個人釋出的內容。我們可能會要看昨天的內容,也可能會看一個月前釋出的內容,這時就可以按照建立時間的區間來分庫分表,比如說可以把一個月的資料放入一張表中,這樣在查詢時就可以根據建立時間先定位資料儲存在哪個表裡面,再按照查詢條件來查詢。

一般來說,列表資料可以使用這種拆分方式,比如一個人一段時間的訂單,一段時間釋出的內容。但是這種方式可能會存在明顯的熱點,這很好理解嘛,你當然會更關注最近我買了什麼,發了什麼,所以查詢的 QPS 也會更多一些,對效能有一定的影響。另外,使用這種拆分規則後,資料表要提前建立好,否則如果時間到了 2020 年元旦,DBA(DatabaseAdministrator,資料庫管理員)卻忘記了建表,那麼 2020 年的資料就沒有庫表可寫了,就會發生故障了。

阿里大牛的資料庫最佳化方案:寫入資料量增加時,如何實現分庫分表?

資料庫在分庫分表之後,資料的訪問方式也有了極大的改變,原先只需要根據查詢條件到從庫中查詢資料即可,現在則需要先確認資料在哪一個庫表中,再到那個庫表中查詢資料。這種複雜度也可以透過資料庫中介軟體來解決。

不過,你要知道的是,分庫分表雖然能夠解決資料庫擴充套件性的問題,但是它也給我們的使用帶來了一些問題。

解決分庫分表引入的問題

分庫分表引入的一個最大的問題就是

引入了分庫分表鍵,也叫做分割槽鍵

,也就是我們對資料庫做分庫分表所依據的欄位。

從分庫分表規則中你可以看到,無論是雜湊拆分還是區間段的拆分,我們首先都需要選取一個數據庫欄位,這帶來一個問題是:我們之後所有的查詢都需要帶上這個欄位,才能找到資料所在的庫和表,否則就只能向所有的資料庫和資料表傳送查詢命令。如果像上面說的要拆分成 16 個庫和 64 張表,那麼一次資料的查詢會變成 16*64=1024 次查詢,查詢的效能肯定是極差的。

當然,方法總比問題多,針對這個問題,我們也會有一些相應的解決思路。比如,在使用者庫中我們使用 ID 作為分割槽鍵,這時如果需要按照暱稱來查詢使用者時,你可以按照暱稱作為分割槽鍵再做一次拆分,但是這樣會極大的增加儲存成本,如果以後我們還需要按照註冊時間來查詢時要怎麼辦呢,再做一次拆分嗎?

所以最合適的思路是你要建立一個暱稱和 ID 的對映表,在查詢的時候要先透過暱稱查詢到ID,再透過 ID 查詢完整的資料,這個表也可以是分庫分表的,也需要佔用一定的儲存空間,但是因為表中只有兩個欄位,所以相比重新做一次拆分還是會節省不少的空間的。

分庫分表引入的另外一個問題是一些資料庫的特性在實現時可能變得很困難。比如說多表的join 在單庫時是可以透過一個 SQL 語句完成的,但是拆分到多個數據庫之後就無法跨庫執行 SQL 了,不過好在我們對於 join 的需求不高,即使有也一般是把兩個表的資料取出後在業務程式碼裡面做篩選,複雜是有一些,不過是可以實現的。再比如說在未分庫分表之前查詢資料總數時只需要在 SQL 中執行 count() 即可,現在資料被分散到多個庫表中,我們可能要考慮其他的方案,比方說將計數的資料單獨儲存在一張表中或者記錄在 Redis 裡面。

當然,雖然分庫分表會對我們使用資料庫帶來一些不便,但是相比它所帶來的擴充套件性和效能方面的提升,我們還是需要做的,因為,經歷過分庫分表後的系統,才能夠突破單機的容量和請求量的瓶頸,就比如說,我在開篇提到的我們的電商系統,它正是經歷了分庫分表,才會解決訂單表資料量過大帶來的效能衰減和容量瓶頸。

總結

總的來說,在面對資料庫容量瓶頸和寫併發量大的問題時,你可以採用垂直拆分和水平拆分來解決,不過你要注意,這兩種方式雖然能夠解決問題,但是也會引入諸如查詢資料必須帶上分割槽鍵,列表總數需要單獨冗餘儲存等問題。

而且,你需要了解的是在實現分庫分表過程中,資料從單庫單表遷移多庫多表是一件即繁雜又容易出錯的事情,而且如果我們初期沒有規劃得當,後面要繼續增加資料庫數或者表數時,我們還要經歷這個遷移的過程。所以,從我的經驗出發,對於分庫分表的原則主要有以下幾點:

如果在效能上沒有瓶頸點那麼就儘量不做分庫分表;

如果要做,就儘量一次到位,比如說 16 庫 64 表就基本能夠滿足為了幾年內你的業務的需求。

很多的 NoSQL 資料庫,例如 Hbase,MongoDB 都提供 auto sharding 的特性,如果你的團隊內部對於這些元件比較熟悉,有較強的運維能力,那麼也可以考慮使用這些NoSQL 資料庫替代傳統的關係型資料庫。

其實,在我看來,有很多人並沒有真正從根本上搞懂為什麼要拆分,拆分後會帶來哪些問題,只是一味地學習大廠現有的拆分方法,從而導致問題頻出。所以,你在使用一個方案解決一個問題的時候一定要弄清楚原理,搞清楚這個方案會帶來什麼問題,要如何來解決,要知其然也知其所以然,這樣才能在解決問題的同時避免踩坑。

推薦閱讀:

盤點:2020年最新、最全、最實用的Java崗面試真題,已收錄GitHub

絕對乾貨,掌握這27個知識點,輕鬆拿下80%的技術面試(Java崗)

一線大廠為什麼面試必問分散式?

在一次又一次的失敗中,我總結了這份萬字的《MySQL效能調優筆記》

併發程式設計詳解:十三個工具類,十大設計模式,從理論基礎到案例實戰

如何高效部署分散式訊息佇列?這份《RabbitMQ實戰》絕對可以幫到你

推薦文章

  • 嶽敏君:“幸福”像“花人”綻放

    嶽敏君:“幸福”像“花人”綻放”“大笑人”“紅彤彤”“笑咧咧”,這樣的形象與那個年代有關,與平質且隱喻化的傳統文化有關,更與嶽敏君在上世紀七八十年代感知體驗過的生活有關...

  • 【關注】沽源縣退役軍人事務局關於《河北省退役軍人優待證》審驗工作的通知

    沽源縣退役軍人事務局關於《河北省退役軍人優待證》審驗工作的通知各鄉鎮退役軍人服務站:為紮實落實《河北省退役軍人公共服務優待辦法(試行)》,確保全縣退役軍人在河北省行政區域內的公共服務機構和場所繼續享受優先或優惠待遇,自即日起啟動《河北省退役...

  • 自己做家裡做的麻花不好吃?原因是你第一步就做錯了

    食物是人的根本,俗話說人是鐵飯是鋼,一頓不吃餓得慌,在以前,勞動人民把食物看得比什麼都重要,只要能讓自己和家人吃飽飯,付出再多的辛苦也是值得,到了現代,人們的生活水平提高了,吃飯不僅僅是為了飽腹,更多的是為了享受,但是生活的節奏太快,讓太多...