天天看點

一種簡單實用、支援動态擴縮容的分庫分表方案

在網際網路的業務中,mysql使用很廣泛,且是最容易産生性能瓶頸的服務元件,一般稍微有點業務量的toC業務,都需要在系統設計階段考慮好擴縮容方案,但又不能過度設計造成資源浪費,是以需要有一個靈活的分庫分表方案,以适應不同時期的業務需求,本文以激勵廣告項目的項目為例,介紹一種簡單實用的分庫分表方案。

為什麼分庫分表

資料庫很容易成為系統性能的一個瓶頸,單機存儲容量、IO、CPU處理能力都有限,當單表的資料量達到1000W或100G以後,庫表的增删改查操作面臨着性能大幅下降的問題。存儲容量現在一般容易解決,主要是IO瓶頸和CPU瓶頸,最終都會導緻資料庫的活躍連接配接數增加,進而逼近甚至達到資料庫可承載活躍連接配接數的門檻值。從業務方來看,就是資料庫可用連接配接少,甚至無連接配接可用。

1、IO瓶頸

第一種:磁盤讀IO瓶頸,熱點資料太多,資料庫緩存放不下,每次查詢時會産生大量的IO,降低查詢速度 -> 分庫和垂直分表。

第二種:網絡IO瓶頸,請求的資料太多,網絡帶寬不夠 -> 分庫。

2、CPU瓶頸

第一種:SQL問題,如SQL中包含join,group by,order by,非索引字段條件查詢等,增加CPU運算的操作 -> SQL優化,建立合适的索引,在業務Service層進行業務計算。

第二種:單表資料量太大,查詢時掃描的行太多,SQL效率低,CPU率先出現瓶頸 -> 水準分表。

分庫分表方案

1、水準分庫

概念:以某個字段為依據,按照一定政策(hash、range等),将一個庫中的資料拆分到多個庫中。結果:

  • 每個庫的結構都一樣;
  • 每個庫的資料都不一樣,沒有交集;
  • 所有庫的并集是全量資料;

場景:系統絕對并發量上來了,分表難以根本上解決問題,并且還沒有明顯的業務歸屬來垂直分庫。

一種簡單實用、支援動态擴縮容的分庫分表方案

2、水準分表

概念:以字段為依據,按照一定政策(hash、range等),将一個表中的資料拆分到多個表中。

  • 每個表的結構都一樣;
  • 每個表的資料都不一樣,沒有交集;
  • 所有表的并集是全量資料;

場景:系統絕對并發量并沒有上來,隻是單表的資料量太多,影響了SQL效率,加重了CPU負擔,以至于成為瓶頸。

一種簡單實用、支援動态擴縮容的分庫分表方案

3、垂直分庫

概念:以表為依據,按照業務歸屬不同,将不同的表拆分到不同的庫中。

  • 每個庫的結構都不一樣;
  • 每個庫的資料也不一樣,沒有交集;
  • 所有庫的并集是全量資料;

場景:系統絕對并發量上來了,并且可以抽象出單獨的業務子產品。

分析:到這一步,基本上就可以服務化了。例如,随着業務的發展一些公用的配置表、字典表等越來越多,這時可以将這些表拆到單獨的庫中,甚至可以服務化。再有,随着業務的發展孵化出了一套業務模式,這時可以将相關的表拆到單獨的庫中,甚至可以服務化。

一種簡單實用、支援動态擴縮容的分庫分表方案

4、垂直分表

概念:以字段為依據,按照字段的活躍性,将表中字段拆到不同的表(主表和擴充表)中。

  • 每個表的結構都不一樣;
  • 每個表的資料也不一樣,一般來說,每個表的字段至少有一列交集,一般是主鍵,用于關聯資料;
  • 所有表的并集是全量資料;

場景:系統絕對并發量并沒有上來,表的記錄并不多,但是字段多,并且熱點資料和非熱點資料在一起,單行資料所需的存儲空間較大。以至于資料庫緩存的資料行減少,查詢時會去讀磁盤資料産生大量的随機讀IO,産生IO瓶頸。

分析:可以用清單頁和詳情頁來幫助了解。垂直分表的拆分原則是将熱點資料(可能會備援經常一起查詢的資料)放在一起作為主表,非熱點資料放在一起作為擴充表。這樣更多的熱點資料就能被緩存下來,進而減少了随機讀IO。拆了之後,要想獲得全部資料就需要關聯兩個表來取資料。但記住,千萬别用join,因為join不僅會增加CPU負擔并且會講兩個表耦合在一起(必須在一個資料庫執行個體上)。關聯資料,應該在業務Service層做文章,分别擷取主表和擴充表資料然後用關聯字段關聯得到全部資料。

一種簡單實用、支援動态擴縮容的分庫分表方案

分庫分表帶來的問題

1、聯合查詢困難

聯合查詢不僅困難,而且可以說是不可能,因為兩個相關聯的表可能會分布在不同的資料庫,不同的伺服器中。

2、需要支援事務

分庫分表後,就需要支援分布式事務了。資料庫本身為我們提供了事務管理功能,但是分庫分表之後就不适用了。如果我們自己程式設計協調事務,代碼方面就又開始了麻煩。

3、跨庫join困難

分庫分表後表之間的關聯操作将受到限制,我們無法join位于不同分庫的表,也無法join分表粒度不同的表, 結果原本一次查詢能夠完成的業務,可能需要多次查詢才能完成。 我們可以使用全局表,所有庫都拷貝一份。

4、結果合并麻煩

比如我們購買了商品,訂單表可能進行了拆分等等,此時結果合并就比較困難。

5、擴容縮容需要遷移

對于水準分庫和水準分表,如果進行擴容或縮容,由于資料拆分條件發生變化,通常需要進行資料遷移,而資料遷移又會影響到線上業務的使用。

分庫分表實踐

本文主要讨論業務量越來越大需要擴容或大促後需要縮容的場景下,如何對資料進行水準分庫、水準分表,才能适應業務在不同時期的性能要求,并最大程度降低切換的代價。

下面以激勵廣告背景流水資料的分庫分表方案為例進行說明。

1、分庫方案

激勵廣告是在福利任務中心給使用者提供一種看廣告領紅包的場景,背景需要記錄使用者觀看廣告記錄及紅包發放記錄。

根據産品需求預估,一期需要db存儲容量在215G左右。同時,根據産品規劃,後續半年會再上2個以上同樣量級的流量入口,這樣的話,在後續1年内,至少需要215G*3=645G的存儲容量。

首先假設不進行分庫,mysql執行個體最大支援擴容到2400G的存儲空間,則能支撐11個與目前流量類似的業務場景,在1年内是夠用的,但長期來看預留白間還是不夠,将來再進行分庫的話,涉及大量資料遷移工作,是以我們分庫才能解決短期和長期的容量問題。

分庫就要考慮分多少個庫,首先從業務量級來考慮,單個mysql執行個體最大能支援每天2750w UV的業務量級(11個目前的流量的業務),假設再分為10個庫,就可以最大支援每天2.75億UV的業務量級,這個業務量級可以說是網際網路業務的天花闆了,恐怕到這個量級之前服務就要重構了,是以分10個庫是完全夠用的。

其次,從mysql執行個體本身的性能上來看,10個超大型執行個體可以支援12T的容量和23萬的qps,這個量級也足夠支撐絕大部分網際網路業務了。

另一方面,産品上要求登入使用者觀看激勵廣告,背景服務都使用person_id作為主鍵存儲資料,而person_id都是純數字且是離散分布的,可以使用person_id的尾号來分庫,這樣也友善RD後期定位問題,不用計算hash。

2、分表方案

由于目前業務特征是激勵廣告任務是按自然天劃分的,是以同一個庫的資料,再按照天進行分表,這樣就可以解決單表資料量過大的問題,又不影響線上服務,後期進行備份歸檔時也友善操作。

3、擴縮容方案

如何合理地分庫分表不難,難的是如何才能最大限度減少擴容縮容帶來的遷移問題。

通常的作法,分庫的擴容或縮容是相應地增加、減少db數量,必定會改變路由規則,需要對全量資料按照新的路由規則重新計算,再導入到新的db中,這種方法需要專門開發遷移腳本才行,并且通常需要新舊db雙寫來達到平滑遷移目的,對原有項目侵入較大,遷移工作量也較大。

為了減少遷移工作量,可以把分好的10個db作為不同的邏輯db放到一個執行個體上,而項目中會配置10個不同的db連接配接,後續需要擴容時,可以先擴容執行個體,這個階段的擴容對業務是沒有影響的。後續如果執行個體擴容到極限,則可以增加新的執行個體,比如擴容一倍,則增加1個執行個體,将其中5個db遷移到新的執行個體上,由于是整個db遷移,交給dba來操作就行了,項目隻用修改其中5個db連接配接的配置即可。

類似地,若業務量變小需要縮容時,可将某些執行個體上的db遷移到其他執行個體上,修改db連接配接配置即可,也比較友善。

4、總結

通過上面的分析,總結下來,該方案有以下幾個主要步驟:

A) 分庫分表數量一步到位

也就是在第一次分庫分表時,就一次給他分個夠。保證在整個項目的生命周期中,這麼多的庫表是足夠使用的,後續擴容縮容不需要調整分庫分表的路由規則,并且保證每個庫裡面有多少個表也是固定的,遷移時可以以庫為機關進行遷移。

上面分庫方案中的推導過程是正向來算的,其實應該是反向計算,先确定業務的終極目标或5年規劃是達到多大的業務量級,比如像上面激勵廣告的場景,假設最終要支援2.75億的UV,大約需要23T的容量,由于我們擴容的最終方案是每個db占用一個超大型執行個體,是以按照超大型執行個體最大支援2400G的容量來計算,需要10個超大型執行個體,我們就分為10個庫。

至于分表,由于本例是按自然日的流水型資料,是以可以按天分表,相對比較簡單

假設這個流水資料使用者需要經常查詢,且要查詢90天内的資料,那麼就不能再按照日期進行劃分了。比如還是上面的例子,最終每個庫是要存儲2.3T或2750w記錄,而單表最大支援1000W或100G,按照2.3T容量來劃分的話,需要分成24個表,是以整個分庫分表過程就變為:先基于person_id%10得到分庫,再基于person_id%10%24得到分表,如下所示:

一種簡單實用、支援動态擴縮容的分庫分表方案

B) 以半年内的規劃确定mysql執行個體大小

由于項目一期需要215G的存儲空間,半年内需要645G的存儲空間,是以一期申請一個中型執行個體即可滿足短期需求,也不會造成浪費,且留有一定的更新空間。

此處建議mysql執行個體以中型或中小型為标準來申請,若半年内的需求超過了1個中型mysql執行個體的容量,則建議申請多個中型執行個體(而不是直接申請大型或超大型),這樣預留了mysql執行個體更新空間,要知道mysql執行個體更新對業務是透明的,避免了擴縮容的工作量。

C) db為邏輯庫,并配置化

上面分庫得到的db都是邏輯庫,這些db可以在一個mysql執行個體上,也可以在多個mysql執行個體上,需要将每個db連接配接配置化,這樣後續擴縮容時不用修改代碼,配合dba遷移db後修改配置即完成擴縮容。

D) 執行擴容縮容

執行擴容縮容的過程,就是不斷在邏輯db和 mysql執行個體之間做遷移,然後服務配合改一下配置即可。

若服務不支援停服,若要想達到對使用者無感覺,難免要修改代碼來相容,一個可行的辦法是先将新db設為舊db的備庫,然後用灰階放量的方式逐漸對不同的使用者分别進行遷移,隻有命中遷移的使用者才切換到新db,最終全量後更新db連接配接配置即可。

若服務支援短暫停服,就比較簡單了,先由dba将原db資料遷移到新db(或者将新db設為舊db的備庫);然後停服,并遷移停服前的增量資料(新db更改為主庫);接着變更db連接配接配置,重新開機服務保證配置生效即可,整個過程應該是分鐘級的