好好的系統,為什麼要分庫分表?

程式設計師小富發表於2022-11-25

大家好,我是小富~

說在前邊

今天是《分庫分表 ShardingSphere 原理與實戰》系列的開篇文章,之前寫過幾篇關於分庫分表的文章反響都還不錯,到現在公眾號:程式設計師小富後臺不斷的有人留言、諮詢分庫分表的問題,我也沒想到大家對於分庫分表的話題會這麼感興趣,可能很多人的工作內容業務量較小很難接觸到這方面的技能。這個系列在我腦子裡籌劃了挺久的,奈何手說啥也不幹活,就一直拖到了現在。

其實網上關於分庫分表相關的文章很多,但我還是堅持出這個系列,主要是自己學習研究,順便給分享,對於一個知識,不同的人從不同的角度理解的不盡相同。

網上的資料看似很多,不過值得學有價值的得仔細挑,很多時候在篩選甄別的過程中,逐漸的磨滅了本就不高的學習熱情。搬運抄襲雷同的東西太多,而且知識點又都比較零碎,很少有細緻的原理實戰案例。對新手來說妥妥的從入門到放棄,即便有成體系的基本上幾篇後就斷更了(希望我不會吧!)。

我不太喜歡堆砌名詞概念,熟悉我的朋友不難發現,我的文章從來都是講完原理緊跟著來一波實戰操作。學習技術原理必須配合實操鞏固一下,不然三天半不到忘得乾乾淨淨,純純的經驗之談。

ShardingSphere知識點梳理

上圖是我初步羅列的ShardingSphere提綱,在官網文件基礎上補充了很多基礎知識,這個系列會用幾十篇文章,詳細的梳理分庫分表基礎理論,手把手的實戰ShardingSphere 5.X框架的功能和解讀原始碼,以及開發中容易踩坑的點,每篇附帶程式碼案例demo,旨在讓新手也能看的懂,後續系列完結全部內容會整理成PDF分享給大家,期待一下吧!

話不多說,我們們這就進入正題~

不急於上手實戰ShardingSphere框架,先來複習下分庫分表的基礎概念,技術名詞大多晦澀難懂,不要死記硬背理解最重要,當你捅破那層窗戶紙,發現其實它也就那麼回事。

什麼是分庫分表

分庫分表是在海量資料下,由於單庫、表資料量過大,導致資料庫效能持續下降的問題,演變出的技術方案。

分庫分表是由分庫分表這兩個獨立概念組成的,只不過通常分庫與分表的操作會同時進行,以至於我們習慣性的將它們合在一起叫做分庫分表。

透過一定的規則,將原本資料量大的資料庫拆分成多個單獨的資料庫,將原本資料量大的表拆分成若干個資料表,使得單一的庫、表效能達到最優的效果(響應速度快),以此提升整體資料庫效能。

為什麼分庫分表

單機資料庫的儲存能力、連線數是有限的,它自身就很容易會成為系統的瓶頸。當單表資料量在百萬以裡時,我們還可以透過新增從庫、最佳化索引提升效能。

一旦資料量朝著千萬以上趨勢增長,再怎麼最佳化資料庫,很多操作效能仍下降嚴重。為了減少資料庫的負擔,提升資料庫響應速度,縮短查詢時間,這時候就需要進行分庫分表。

為什麼需要分庫?

容量

我們給資料庫例項分配的磁碟容量是固定的,資料量持續的大幅增長,用不了多久單機的容量就會承載不了這麼多資料,解決辦法簡單粗暴,加容量!

連線數

單機的容量可以隨意擴充套件,但資料庫的連線數卻是有限的,在高併發場景下多個業務同時對一個資料庫操作,很容易將連線數耗盡導致too many connections報錯,導致後續資料庫無法正常訪問。

可以透過max_connections檢視MySQL最大連線數。

show variables like '%max_connections%'

將原本單資料庫按不同業務拆分成訂單庫、物流庫、積分庫等不僅可以有效分攤資料庫讀寫壓力,也提高了系統容錯性。

為什麼需要分表?

做過報表業務的同學應該都體驗過,一條SQL執行時間超過幾十秒的場景。

導致資料庫查詢慢的原因有很多,SQL沒命中索引、like掃全表、用了函式計算,這些都可以透過最佳化手段解決,可唯獨資料量大是MySQL無法透過自身最佳化解決的。慢的根本原因是InnoDB儲存引擎,聚簇索引結構的 B+tree 層級變高,磁碟IO變多查詢效能變慢,詳細原理自行查詢一下,這裡不用過多篇幅說明。

阿里的開發手冊中有條建議,單錶行數超500萬行或者單表容量超過2GB,就推薦分庫分表,然而理想和實現總是有差距的,阿里這種體量的公司不差錢當然可以這麼用,實際上很多公司單表資料幾千萬、億級別仍然不選擇分庫分表。

什麼時候分庫分表

技術群裡經常會有小夥伴問,到底什麼情況下會用分庫分表呢?

分庫分表要解決的是現存海量資料訪問的效能瓶頸,對持續激增的資料量所做出的架構預見性。

是否分庫分表的關鍵指標是資料量,我們以fire100.top這個網站的資源表 t_resource為例,系統在執行初始的時候,每天只有可憐的幾十個資源上傳,這時使用單庫、單表的方式足以支援系統的儲存,資料量小几乎沒什麼資料庫效能瓶頸。

但某天開始一股神秘的流量進入,系統每日產生的資源資料量暴增至十萬甚至上百萬級別,這時資源表資料量到達千萬級,查詢響應變得緩慢,資料庫的效能瓶頸逐漸顯現。

以MySQL資料庫為例,單表的資料量在達到億條級別,透過加索引、SQL調優等傳統最佳化策略,效能提升依舊微乎其微時,就可以考慮做分庫分表了。

既然MySQL儲存海量資料時會出現效能瓶頸,那麼我們是不是可以考慮用其他方案替代它?比如高效能的非關係型資料庫MongoDB

可以,但要看儲存的資料型別!

現在網際網路上大部分公司的核心資料幾乎是儲存在關係型資料庫(MySQL、Oracle等),因為它們有著NoSQL如法比擬的穩定性和可靠性,產品成熟生態系統完善,還有核心的事務功能特性,也是其他儲存工具不具備的,而評論、點贊這些非核心資料還是可以考慮用MongoDB的。

如何分庫分表

分庫分表的核心就是對資料的分片(Sharding)並相對均勻的路由在不同的庫、表中,以及分片後對資料的快速定位與檢索結果的整合。

分庫與分表可以從:垂直(縱向)和 水平(橫向)兩種緯度進行拆分。下邊我們以經典的訂單業務舉例,看看如何拆分。

垂直拆分

1、垂直分庫

垂直分庫一般來說按照業務和功能的維度進行拆分,將不同業務資料分別放到不同的資料庫中,核心理念 專庫專用

按業務型別對資料分離,剝離為多個資料庫,像訂單、支付、會員、積分相關等表放在對應的訂單庫、支付庫、會員庫、積分庫。不同業務禁止跨庫直連,獲取對方業務資料一律透過API介面互動,這也是微服務拆分的一個重要依據。

垂直分庫

垂直分庫很大程度上取決於業務的劃分,但有時候業務間的劃分並不是那麼清晰,比如:電商中訂單資料的拆分,其他很多業務都依賴於訂單資料,有時候界線不是很好劃分。

垂直分庫把一個庫的壓力分攤到多個庫,提升了一些資料庫效能,但並沒有解決由於單表資料量過大導致的效能問題,所以就需要配合後邊的分表來解決。

2、垂直分表

垂直分錶針對業務上欄位比較多的大表進行的,一般是把業務寬表中比較獨立的欄位,或者不常用的欄位拆分到單獨的資料表中,是一種大表拆小表的模式。

例如:一張t_order訂單表上有幾十個欄位,其中訂單金額相關欄位計算頻繁,為了不影響訂單表t_order的效能,就可以把訂單金額相關欄位拆出來單獨維護一個t_order_price_expansion擴充套件表,這樣每張表只儲存原表的一部分欄位,透過訂單號order_no做關聯,再將拆分出來的表路由到不同的庫中。

資料庫它是以行為單位將資料載入到記憶體中,這樣拆分以後核心表大多是訪問頻率較高的欄位,而且欄位長度也都較短,因而可以載入更多資料到記憶體中,減少磁碟IO,增加索引查詢的命中率,進一步提升資料庫效能。

水平拆分

上邊垂直分庫、垂直分表後還是會存在單庫、表資料量過大的問題,當我們的應用已經無法在細粒度的垂直切分時,依舊存在單庫讀寫、儲存效能瓶頸,這時就要配合水平分庫、水平分表一起了。

1、水平分庫

水平分庫是把同一個表按一定規則拆分到不同的資料庫中,每個庫可以位於不同的伺服器上,以此實現水平擴充套件,是一種常見的提升資料庫效能的方式。

例如:db_orde_1db_order_2兩個資料庫內有完全相同的t_order表,我們在訪問某一筆訂單時可以透過對訂單的訂單編號取模的方式 訂單編號 mod 2 (資料庫例項數) ,指定該訂單應該在哪個資料庫中操作。

這種方案往往能解決單庫儲存量及效能瓶頸問題,但由於同一個表被分配在不同的資料庫中,資料的訪問需要額外的路由工作,因此係統的複雜度也被提升了。

2、水平分表

水平分表是在同一個資料庫內,把一張大資料量的表按一定規則,切分成多個結構完全相同表,而每個表只存原表的一部分資料。

例如:一張t_order訂單表有900萬資料,經過水平拆分出來三個表,t_order_1t_order_2t_order_3,每張表存有資料300萬,以此類推。

水平分表儘管拆分了表,但子表都還是在同一個資料庫例項中,只是解決了單一表資料量過大的問題,並沒有將拆分後的表分散到不同的機器上,還在競爭同一個物理機的CPU、記憶體、網路IO等。要想進一步提升效能,就需要將拆分後的表分散到不同的資料庫中,達到分散式的效果。

資料存在哪個庫的表

分庫分表以後會出現一個問題,一張表會出現在多個資料庫裡,到底該往哪個庫的哪個表裡存呢?

上邊我們多次提到過一定規則 ,其實這個規則它是一種路由演算法,決定了一條資料具體應該存在哪個資料庫的哪張表裡。

常見的有 取模演算法範圍限定演算法範圍+取模演算法預定義演算法

1、取模演算法

關鍵欄位取模(對hash結果取餘數 hash(XXX) mod N),N為資料庫例項數或子表數量)是最為常見的一種路由方式。

t_order訂單表為例,先給資料庫從 0 到 N-1進行編號,對 t_order訂單表中order_no訂單編號欄位進行取模hash(order_no) mod N,得到餘數ii=0存第一個庫,i=1存第二個庫,i=2存第三個庫,以此類推。

同一筆訂單資料會落在同一個庫、表裡,查詢時用相同的規則,用t_order訂單編號作為查詢條件,就能快速的定位到資料。

優點

實現簡單,資料分佈相對比較均勻,不易出現請求都打到一個庫上的情況。

缺點

取模演算法對叢集的伸縮支援不太友好,叢集中有N個資料庫實·hash(user_id) mod N,當某一臺機器當機,本應該落在該資料庫的請求就無法得到處理,這時宕掉的例項會被踢出叢集。

此時機器數減少演算法發生變化hash(user_id) mod N-1,同一使用者資料落在了在不同資料庫中,等這臺機器恢復,用user_id作為條件查詢使用者資料就會少一部分。

2、範圍限定演算法

範圍限定演算法以某些範圍欄位,如時間ID區拆分。

使用者表t_user被拆分成t_user_1t_user_2t_user_3三張表,後續將user_id範圍為1 ~ 1000w的使用者資料放入t_user_1,1000~ 2000w放入t_user_2,2000~3000w放入t_user_3,以此類推。按日期範圍劃分同理。

優點

  • 單表資料量是可控的
  • 水平擴充套件簡單隻需增加節點即可,無需對其他分片的資料進行遷移

缺點

  • 由於連續分片可能存在資料熱點,比如按時間欄位分片時,如果某一段時間(雙11等大促)訂單驟增,存11月資料的表可能會被頻繁的讀寫,其他分片表儲存的歷史資料則很少被查詢,導致資料傾斜,資料庫壓力分攤不均勻。

3、範圍 + 取模演算法

為了避免熱點資料的問題,我們可以對上範圍演算法最佳化一下

這次我們先透過範圍演算法定義每個庫的使用者表t_user只存1000w資料,第一個db_order_1庫存放userId從1 ~ 1000w,第二個庫10002000w,第三個庫20003000w,以此類推。

每個庫裡再把使用者表t_user拆分成t_user_1t_user_2t_user_3等,對userd進行取模路由到對應的表中。

有效的避免資料分佈不均勻的問題,資料庫水平擴充套件也簡單,直接新增例項無需遷移歷史資料。

4、地理位置分片

地理位置分片其實是一個更大的範圍,按城市或者地域劃分,比如華東、華北資料放在不同的分片庫、表。

5、預定義演算法

預定義演算法是事先已經明確知道分庫和分表的數量,可以直接將某類資料路由到指定庫或表中,查詢的時候亦是如此。

分庫分表出來的問題

瞭解了上邊分庫分表的拆分方式不難發現,相比於拆分前的單庫單表,系統的資料儲存架構演變到現在已經變得非常複雜。看幾個具有代表性的問題,比如:

分頁、排序、跨節點聯合查詢

分頁、排序、聯合查詢,這些看似普通,開發中使用頻率較高的操作,在分庫分表後卻是讓人非常頭疼的問題。把分散在不同庫中表的資料查詢出來,再將所有結果進行彙總合併整理後提供給使用者。

比如:我們要查詢11、12月的訂單資料,如果兩個月的資料是分散到了不同的資料庫例項,則要查詢兩個資料庫相關的資料,在對資料合併排序、分頁,過程繁瑣複雜。

事務一致性

分庫分表後由於表分佈在不同庫中,不可避免會帶來跨庫事務問題。後續會分別以阿里的Seata和MySQL的XA協議實現分散式事務,用來比較各自的優勢與不足。

全域性唯一的主鍵

分庫分表後資料庫表的主鍵ID業務意義就不大了,因為無法在標識唯一一條記錄,例如:多張表t_order_1t_order_2的主鍵ID全部從1開始會重複,此時我們需要主動為一條記錄分配一個ID,這個全域性唯一的ID就叫分散式ID,發放這個ID的系統通常被叫發號器。

多資料庫高效治理

對多個資料庫以及庫內大量分片表的高效治理,是非常有必要,因為像某寶這種大廠一次大促下來,訂單表可能會被拆分成成千上萬個t_order_n表,如果沒有高效的管理方案,手動建表、排查問題是一件很恐怖的事。

歷史資料遷移

分庫分表架構落地以後,首要的問題就是如何平滑的遷移歷史資料,增量資料和全量資料遷移,這又是一個比較麻煩的事情,後邊詳細講。

分庫分表架構模式

分庫分表架構主要有兩種模式:client客戶端模式和proxy代理模式

客戶模式

client模式指分庫分表的邏輯都在你的系統應用內部進行控制,應用會將拆分後的SQL直連多個資料庫進行操作,然後本地進行資料的合併彙總等操作。

代理模式

proxy代理模式將應用程式與MySQL資料庫隔離,業務方的應用不在需要直連資料庫,而是連線proxy代理服務,代理服務實現了MySQL的協議,對業務方來說代理服務就是資料庫,它會將SQL分發到具體的資料庫進行執行,並返回結果。該服務內有分庫分表的配置,根據配置自動建立分片表。

如何抉擇

如何選擇client模式和proxy模式,我們可以從以下幾個方面來簡單做下比較。

1、效能

效能方面client模式表現的稍好一些,它是直接連線MySQL執行命令;
proxy代理服務則將整個執行鏈路延長了,應用->代理服務->MySQL,可能導致效能有一些損耗,但兩者差距並不是非常大。

2、複雜度

client模式在開發使用通常引入一個jar可以;
proxy代理模式則需要搭建單獨的服務,有一定的維護成本,既然是服務那麼就要考慮高可用,畢竟應用的所有SQL都要透過它轉發至MySQL。

3、升級

client模式分庫分表一般是依賴基礎架構團隊的Jar包,一旦有版本升級或者Bug修改,所有應用到的專案都要跟著升級。小規模的團隊服務少升級問題不大,如果是大公司服務規模大,且涉及到跨多部門,那麼升級一次成本就比較高;

proxy模式在升級方面優勢很明顯,釋出新功能或者修復Bug,只要重新部署代理服務叢集即可,業務方是無感知的,但要保證釋出過程中服務的可用性。

4、治理、監控

client模式由於是內嵌在應用內,應用叢集部署不太方便統一處理;proxy模式在對SQL限流、讀寫許可權控制、監控、告警等服務治理方面更優雅一些。

結束語

本文主要是回顧一下分庫分表的一些基礎概念,為大家在後續ShardingSphere實踐中更好上手理解,內容裡很多概念一筆帶過沒詳細展開,接下來的篇幅會逐一解讀。

下一篇預告《分庫分表ShardingSphere的基礎知識點梳理》

歡迎關注 公眾號:程式設計師小富,我們們下期再見!

相關文章