手把手0基礎項目實戰(二)——微服務架構下的數據庫分庫分表實戰

本文你將學到什麼?

本文是《手把手項目實戰系列》的第二篇文章。上一篇《手把手0基礎教你搭建一套可自動化構建的微服務框架(SpringBoot+Dubbo+Docker+Jenkins)》受到巨大好評,在這裏也深表感謝。應你們要求繼續完成後續章節的撰寫。上一篇的實戰過程介紹的「高喜商城」項目實際上是一個真實項目,它是一個標準的在線商城(爲了避嫌,「高喜商城」是我隨意起的一個假名字),這個項目的不少技術具備必定的普適性。所以我計劃將它實現的方方面面以項目實戰的形式介紹給你們,讓你們體驗一個真實線上項目的開發、運維、升級過程。前端

關注訂閱個人文章

相信不少同窗對「分庫分表」這一律念只知其一;不知其二,不用着急,本文的後續章節將會分紅知識點掃盲篇實戰動手篇兩部分。知識點掃盲篇將會從零開始,介紹分庫分表的基本知識,而後再帶領你們開始動手實踐。但願可以給你帶來完美的閱讀體驗。接下來我將用盡可能通俗易懂的語言介紹分庫分表的相關知識,不裝逼,作一個低調的程序猿。算法

預告一下,整個系列會介紹以下內容:sql

  • 《手把手0基礎項目實戰(一)——教你搭建一套可自動化構建的微服務框架(SpringBoot+Dubbo+Docker+Jenkins)》
  • 《手把手0基礎項目實戰(二)——微服務架構下的數據庫分庫分表實戰》
  • 《手把手0基礎項目實戰(三)——教你開發一套權限管理系統》
  • 《手把手0基礎項目實戰(四)——電商訂單系統架構設計與實戰(分佈式事務一致性保證)》
  • 《手把手0基礎項目實戰(五)——電商系統的緩存策略》
  • 《手把手0基礎項目實戰(六)——基於配置中心實現集羣配置的集中管理和熔斷機制》
  • 《手把手0基礎項目實戰(七)——電商系統的日誌監控方案》
  • 《手把手0基礎項目實戰(八)——基於JMeter的系統性能測試》

知識點掃盲篇

1. 什麼是「分庫分表」?

隨着大數據時代的到來,業務系統的數據量日益增大,數據存儲能力逐漸成爲影響系統性能的瓶頸。目前主流的關係型數據庫單表存儲上限爲1000萬條記錄,而這一存儲能力顯然已經沒法知足大數據背景下的業務系統存儲要求了。隨着微服務架構、分佈式存儲等概念的出現,數據存儲問題也漸漸迎來了起色。而數據分片是目前解決海量數據持久化存儲與高效查詢的一種重要手段。數據分庫分表的過程在系統設計階段完成,要求系統設計人員根據系統預期的業務量,將將來可能出現瓶頸的數據庫、數據表按照必定規則拆分紅多個庫、多張表。這些數據庫和數據表須要部署在不一樣的服務器上,從而將數據讀寫壓力分攤至集羣中的各個節點,提高數據庫總體處理能力,避免出現讀寫瓶頸的現象。數據庫

目前數據分片的方式一共有兩種:離散分片和連續分片。編程

離散分片是按照數據的某一字段哈希取模後進行分片存儲。只要哈希算法選擇得當,數據就會均勻地分佈在不一樣的分片中,從而將讀寫壓力平均分配給全部分片,總體上提高數據的讀寫能力。然而,離散存儲要求數據之間有較強的獨立性,但實際業務系統並不是如此,不一樣分片之間的數據每每存在必定的關聯性,所以在某些場景下須要跨分片鏈接查詢。因爲目前全部的關係型數據庫出於安全性考慮,均不支持跨庫鏈接。所以,跨庫操做須要由數據分庫分表中間件來完成,這極大影響數據的查詢效率。此外,當數據存儲能力出現瓶頸須要擴容時,離散分片規則須要將全部數據從新進行哈希取模運算,這無疑成爲限制系統可擴展性的一個重要因素。雖然,一致性哈希能在必定程度上減小系統擴容時的數據遷移,但數據遷移問題仍然不可避免。對於一個已經上線運行的系統而言,系統中止對外服務進行數據遷移的代價太大。緩存

第二種數據分片的方式即爲連續分片,它能解決系統擴容時產生的數據遷移問題。這種方式要求數據按照時間或連續自增主鍵連續存儲。從而一段時間內的數據或相鄰主鍵的數據會被存儲在同一個分片中。當須要增長分片時,不會影響現有的分片。所以,連續分片能解決擴容所帶來的數據遷移問題。可是,數據的存儲時間和讀寫頻率每每呈正比,也就是大量的讀寫每每都集中在最新存儲的那一部分數據,這就會致使熱點問題,並不能起到分攤讀寫壓力的初衷。安全

2. 數據庫擴展的幾種方式

數據庫擴展一共有四種分配方式,分別是:垂直分庫、垂直分表、水平分表、水平數據分片。每一種策略都有各自的適用場景。服務器

  1. 垂直分庫架構

    垂直分庫便是將一個完整的數據庫根據業務功能拆分紅多個獨立的數據庫,這些數據庫能夠運行在不一樣的服務器上,從而提高數據庫總體的數據讀寫性能。這種方式在微服務架構中很是經常使用。微服務架構的核心思想是將一個完整的應用按照業務功能拆分紅多個可獨立運行的子系統,這些子系統稱爲「微服務」,各個服務之間經過RPC接口通訊,這樣的結構使得系統耦合度更低、更易於擴展。垂直分庫的理念與微服務的理念不謀而合,能夠將本來完整的數據按照微服務拆分系統的方式,拆分紅多個獨立的數據庫,使得每一個微服務系統都有各自獨立的數據庫,從而能夠避免單個數據庫節點壓力過大,影響系統的總體性能,以下圖所示。 框架

  2. 垂直分表

    垂直分表若是一張表的字段很是多,那麼頗有可能會引發數據的跨頁存儲,這會形成數據庫額外的性能開銷,而垂直分表能夠解決這個問題。垂直分表就是將一張表中不經常使用的字段拆分到另外一張表中,從而保證第一章表中的字段較少,避免出現數據庫跨頁存儲的問題,從而提高查詢效率。而另外一張表中的數據經過外鍵與第一張表進行關聯,以下圖所示。

  3. 水平分表

    若是一張表中的記錄數過多(超過1000萬條記錄),那麼會對數據庫的讀寫性能產生較大的影響,雖然此時仍然可以正確地讀寫,但讀寫的速度已經到了業務沒法忍受的地步,此時就須要使用水平分表來解決這個問題。水平分表是將一張含有不少記錄數的表水平切分,拆分紅幾張結構相同的表。舉個例子,假設一張訂單表目前存儲了2000萬條訂單的數據,致使數據讀寫效率極低。此時能夠採用水平分表的方式,將訂單表拆分紅100張結構相同的訂單表,分別叫作order_一、order_2……、order_100。而後能夠根據訂單所屬用戶的id進行哈希取模後均勻地存儲在這100張表中,從而每張表中只存儲了20萬條訂單記錄,極大提高了訂單的讀寫效率,以下圖所示。 固然,若是拆分出來的表都存儲在同一個數據庫節點上,那麼當請求量過大的時候,畢竟單臺服務器的處理能力是有限的,數據庫仍然會成爲系統的瓶頸,因此爲了解決這個問題,就出現了水平數據分片的解決方案。

  4. 水平分庫分表

    水平數據分片與數據分片區別在於:水平數據分片首先將數據表進行水平拆分,而後按照某一分片規則存儲在多臺數據庫服務器上。從而將單庫的壓力分攤到了多庫上,從而避免由於數據庫硬件資源有限致使的數據庫性能瓶頸,以下圖所示。

3. 分庫分表的幾種方式

目前經常使用的數據分片策略有兩種,分別是連續分片和離散分片。

  1. 離散分片

    離散分片是指將數據打散以後均勻地存儲在邏輯表的各個分片中,從而使的對同一張邏輯表的數據讀取操做均勻地落在不一樣庫的不一樣表上,從而提升讀寫速度。離散分片通常以哈希取模的方式實現。好比:一張邏輯表有4個分片,那麼在讀寫數據的時候,中間件首先會取得分片字段的哈希值,而後再模以4,從而計算出該條記錄所在的分片。在這種方法中,只要哈希算法選的好,那麼數據分片將會比較均勻,從而數據讀寫就會比較均勻地落在各個分片上,從而就有較高的讀寫效率。可是,這種方式也存在一個最大的缺陷——數據庫擴容成本較高。採用這種方式,若是須要再增長分片,原先的分片算法將失效,而且全部記錄都須要從新計算所在分片的位置。對於一個已經上線的系統來講,行級別的數據遷移成本至關高,並且因爲數據遷移期間系統仍在運行,仍有新數據產生,從而沒法保證遷移過程數據的一致性。若是爲了不這個問題而停機遷移,那必然會對業務形成巨大影響。固然,若是爲了不數據遷移,在一開始的時候就分片較多的分片,那須要承擔較高的費用,這對於中小公司來講是沒法承受的。

  2. 連續分片

    連續分片指的是按照某一種分片規則,將某一個區間內的數據存儲在同一個分片上。好比按照時間分片,每月生成一張物理表。那麼在讀寫數據時,直接根據當前時間就能夠找到數據所在的分片。再好比能夠按照記錄ID分片,這種分片方式要求ID須要連續遞增。因爲Mysql數據庫單表支持最大的記錄數約爲1000萬,所以咱們能夠根據記錄的ID,使得每一個分片存儲1000萬條記錄,當目前的記錄數即將到達存儲上限時,咱們只需增長分片便可,原有的數據無需遷移。連續分片的一個最大好處就是方便擴容,由於它不須要任何的數據遷移。可是,連續分片有個最大的缺點就是熱點問題。連續分片使得新插入的數據集中在同一個分片上,而每每新插入的數據讀寫頻率較高,所以,讀寫操做都會集中在最新的分片上,從而沒法體現數據分片的優點。

4. 引入分庫分表中間件後面臨的問題

  1. 跨庫操做

    在關係型數據庫中,多張表之間每每存在關聯,咱們在開發過程當中須要使用JOIN操做進行多表鏈接。可是當咱們使用了分庫分表模式後,因爲數據庫廠商處於安全考慮,不容許跨庫JOIN操做,從而若是須要鏈接的兩張表被分到不一樣的庫中後,就沒法使用SQL提供的JOIN關鍵字來實現錶鏈接,咱們可能須要在業務系統層面,經過屢次SQL查詢,完成數據的組裝和拼接。這一方面會增長業務系統的複雜度,另外一方面會增長業務系統的負載。 所以,當咱們使用分庫分表模式時,須要根據具體的業務場景,合理地設置分片策略、設置分片字段,這將會在本文的後續章節中介紹。

  2. 分佈式事務

    咱們知道,數據庫提供了事務的功能,以保證數據一致性。然而,這種事務只是針對單數據庫而言的,數據庫廠商並未提供跨庫事務。所以,當咱們使用了分庫分表以後,就須要咱們在業務系統層面實現分佈式事務。關於分佈式事務的詳細內容,能夠參考筆者的另外一篇文章《經常使用的分佈式事務解決方案》

5. 現有分庫分表中間件的橫向對比

  • Cobar實現數據庫的透明分庫,讓開發人員可以在無感知的狀況下操縱數據庫集羣,從而簡化數據庫的編程模型。然而Cobar僅實現了分庫功能,並未實現分表功能。分庫能夠解決單庫IO、CPU、內存的瓶頸,但沒法解決單表數據量過大的問題。此外,Cobar是一個獨立運行的系統,它處在應用系統與數據庫系統之間,所以增長了額外的部署複雜度,增長了運維成本。

  • 爲了解決上述問題,Cobar還推出了一個Cobar-Client項目,它只是一個安裝在應用程序的Jar包,並非一個獨立運行的系統,必定程度上下降了系統的複雜度。但和Cobar同樣,仍然只支持分庫,並不支持分表,也不支持讀寫分離。

  • MyCat是基於Cobar二次開發的數據庫中間件,和Cobar相比,它增長了讀寫分離的功能,並修復了Cobar的一些bug。可是,MyCat和Cobar同樣,都是一套須要獨立部署的系統,所以會增長部署的複雜度,提升了後期系統運維的成本。


實戰篇

1. 爲什麼要進行分庫分表?

高喜商城已經上線了一段時間,用戶量超預期增加,業務層採用基於Dubbo的微服務架構,並結合了Docker+Jenkins實現了自動化部署,具有靈活的擴展能力,可以輕鬆支撐目前的業務量。然而,數據庫層面卻出現了瓶頸。因爲1.0版本採用單庫單表設計,雖然使用Mysql讀寫分離實現了一主多備架構,必定程度上分攤了數據庫的讀寫壓力。但按照目前的業務發展速度,不少業務表將會面臨單表過長的問題。目前Mysql數據庫在保證讀寫性能的前提下,單表最大支持1000W條數據。當單表超過1000W條數據後,雖然仍然能夠存儲數據,但讀寫性能大幅降低。所以,爲了知足極速增加的業務需求,須要使用數據庫中間件實現數據分庫分表存儲。分庫能將讀寫壓力分攤至不一樣節點,從而緩解讀寫壓力;而分表可以避免單表過長的問題。此外,大多數分庫分表中間件都會提供讀寫分離的功能,從而進一步緩解數據庫的讀寫壓力,提高讀寫性能。

綜上所述,對數據庫進行分庫分表迫在眉睫!

2. 高喜商城1.0數據庫架構介紹

高喜商城1.0的架構以下圖所示:

該架構的業務層採用微服務架構,全部將整個應用分紅四個業務系統:用戶系統、產品系統、訂單系統和數據分析系統。關於微服務架構這裏不作過多介紹,詳細內容請閱讀《手把手0基礎項目實戰(一)——教你搭建一套可自動化構建的微服務框架(SpringBoot+Dubbo+Docker+Jenkins)》,這裏主要介紹數據庫架構。

在高喜商城1.0版本中,雖然業務層採用微服務架構,業務層被拆分紅多個相互獨立的子系統,但在數據庫層,整個系統的全部表均在同一個數據庫中存儲。此外,採用數據庫的主從複製實現了讀寫分離,數據庫有一個主庫和兩個從庫組成了一個數據庫集羣。它是一個對等集羣,每一個庫中存儲的數據是一致的。

在加入了讀寫分離後,一方面提高了數據庫的讀寫性能;另外一方面,實現了數據庫的高可用。當某一個節點發生故障時,仍然有其餘兩個節點提供服務。

這種架構存在以下幾個缺點:

  1. 沒有垂直分庫:全部業務系統的表均存儲在同一個庫中,相互之間沒有任何隔離,從而致使一個業務系統能夠直接讀寫其餘業務系統的數據,這違背了微服務的理念。
  2. 存在單表過長的問題:系統通過一段時間運營後,有些表的數據量較大,單表數據量可能會超過1000W。這將會極大影響該表的讀寫性能。

針對上述問題,對數據庫進行分庫分表迫在眉睫。

3. 高喜商城2.0數據庫架構的演進

高喜商城2.0數據庫架構以下圖所示:

在2.0架構中,首先對數據庫進行了垂直拆分,每一個子系統均擁有本身獨立的數據庫,不一樣系統的數據庫相互隔離,沒法互相訪問。這樣保證了各個業務系統的純粹性,不一樣業務系統之間若是須要數據交互,那麼就經過業務系統提供了RPC接口訪問,而非經過數據庫訪問,從而符合微服務的設計理念。

上圖對用戶系統的數據庫架構作了詳細介紹,其餘系統的數據庫架構和用戶系統相似,都採用了分庫分表+讀寫分離的架構。

在用戶系統中,數據庫一共被分紅N個主庫和N個從庫,每一個庫中的表又被拆分紅多張。以上圖爲例,用戶系統的數據庫一共被分紅兩個物理庫,分別是db_0和db_1。此外,爲了實現讀寫分離,每一個物理庫均擁有一個從庫,主從數據庫的數據保持一致。從而,用戶系統的物理庫一共被分紅四個,分別是:db_0_master、db_1_master、db_0_slave、db_1_slave。

每一個庫中的表table被水平拆分紅兩張,分別是table_0、table_1。從而,本來一張table表被水平拆分紅了四張,分別是:db_0_master_table_0、db_0_master_table_一、db_1_master_table_0、db_1_master_table_1。與此同時,從庫中也有四張這樣的table表,而且和主庫的數據保持一致,所以,通過水平拆分後,一共有8張table表。

上述table表只是舉一個例子,實際每一個系統均包含有多張表,每張表的拆分規則和拆分數量要根據該表具體的業務量來決定。具體的拆分過程將在下面介紹。

4. 高喜商城1.0數據庫表結構設計

下面將會詳細介紹高喜商城數據表的設計。這些設計在在線商城系統中是通用的,具有必定的借鑑意義,所以下面將會詳細介紹。

4.1 用戶系統數據表

用戶系統的數據表一共由如上六張表構成,下面對這六張表的做用以及相互之間的關係做簡單介紹。

  • sys_user:用戶表。
    • 存儲用戶的基本信息。
  • sys_role:角色表。
    • 存儲本系統中全部的角色,如:超級管理員、普通用戶、企業用戶等等。
    • 用戶和角色之間是多對一的聚合關係,即一個用戶只能擁有一種角色,而一種角色卻能夠屬於多個用戶。因爲角色能夠脫離用戶單獨存在,所以他們之間是一種弱依賴關係——聚合關係。
  • sys_permission:權限表。
    • 存儲本系統的權限信息,如:建立角色、刪除角色、建立菜單、刪除菜單、修改用戶信息等等。
    • 角色和權限是多對多的聚合關係,即一種角色能夠擁有多種權限,而一種權限也能夠屬於多種角色。而且因爲權限能夠脫離角色單獨存在,所以他們之間是弱依賴關係——聚合關係。
    • 更多關於本系統權限管理功能的設計,請關注後面即將推出的《手把手0基礎教你實現一套權限管理系統》。
  • sys_menu:菜單表。
    • 存儲本系統的菜單信息。
    • 因爲須要實現角色看到不一樣的菜單,所以須要創建這張菜單表,存儲本系統全部的菜單信息。
    • 角色和菜單是多對多的聚合關係,即一種角色能夠擁有多個菜單,而一個菜單也能夠屬於多種角色。而且因爲菜單能夠脫離於角色單獨存在,所以他們之間是弱依賴關係——聚合關係。
  • location:用戶地址信息表。
    • 存儲用戶的地址信息。
    • 用戶下單以後須要填寫收貨地址,所以須要這張表存儲用戶的地址信息。
    • 用戶和地址之間是一對多的組合關係,即一個用戶能夠擁有多個收貨地址,而且一個收貨地址只能屬於一個用戶。此外,因爲收穫地址不能脫離於用戶單獨存在,所以他們之間是強依賴關係——組合關係。
  • receipt:發票表。
    • 用戶在下單時能夠填寫發票信息,所以須要這張表來存儲這些發票信息。
    • 用戶和發票之間是一對多的組合關係,即一個用戶能夠擁有多個發票信息,而一個具體的發票信息只能屬於一個用戶。此外,因爲發票不能脫離於用戶單獨存在,所以他們之間是強依賴關係——組合關係。

到此爲止,用戶系統的每一張表及表於表之間的關係都已詳細介紹完畢。經過這些表以及表之間的關係咱們就能看出用戶系統的業務需求。

  • 每個用戶都有且僅有一種肯定的角色,該角色對應了若干個菜單和若干種權限。當用戶登陸系統的時候,用戶系統就能夠根據數據庫中存儲的這些用戶信息,知道該用戶可以看到哪些菜單,而後將這些菜單顯示在用戶的界面上。此外,當用戶操做這個系統時,前端就會調用相應的後臺接口,每次調用任何接口時,用戶系統都會根據用戶的權限信息檢測該用戶是否具備操做該接口的權限,若是沒有權限則拒絕執行,從而保證系統的安全性。
  • 一個用戶在下單的時候能夠要求開具發票,那麼這些發票信息將會被存儲在receipt表中,當用戶再次下單的時候,咱們就會查詢receipt表,將該用戶全部的發票信息展現給他,供用戶選擇。
  • 一個用戶在下單的時候須要填寫收貨地址,那麼這些收穫地址就會被存儲在location表中,當用戶再次下單時,無需再次輸入收穫地址,咱們的系統會查詢location表,讓用戶直接選擇。

4.2 產品系統數據表

產品系統的數據表一共由如上四張表構成,下面對這四張表的做用以及相互之間的關係做簡單介紹。

  • product:產品表。
    • 存儲本系統全部的產品信息。
  • prod_image:產品圖片表。
    • 存儲本系統全部的產品圖片URL。
    • 產品和圖片之間是一對多組合關係,即一個產品可以擁有多張圖片,而一張圖片只能屬於某一個產品,而且圖片不能脫離於產品單獨存在,所以他們之間是強依賴關係——組合關係。
  • brand:品牌表。
    • 存儲本系統中全部的品牌信息。
    • 產品和品牌是多對一的聚合關係,即一個產品只屬於一種品牌,而一種品牌能夠包含多個產品。而且品牌能夠獨立於產品單獨存在,所以他們之間是弱依賴關係——聚合關係。
  • category:類別表。
    • 每件產品都必須屬於一個類別,所以經過類別表來存儲全部的類別信息。
    • 產品和類別之間是多對一的聚合關係,即一件產品只能屬於一種類別,而一種類別卻能夠包含多件產品。而且類別能夠獨立於產品存在,所以他們之弱依賴關係——聚合關係。

4.3 訂單系統數據表

訂單系統的數據表一共由如上三張表構成,下面對這三張表的做用以及相互之間的關係做簡單介紹。

  • orders:訂單表。
    • 存儲本系統全部用戶的訂單信息。
  • orders_product:訂單中的產品表。
    • 每條訂單中通常都包含多件產品,這種映射關係就存儲在這張表中。
    • 這張表是訂單和產品之間的關聯表。
    • 訂單和產品之間是多對多的聚合關係,即一條訂單中能夠包含多件產品,而且一件產品也能夠屬於多條訂單。此外,因爲產品能夠獨立於訂單而存在,所以他們之間是弱依賴關係——聚合關係。
    • 訂單和訂單產品表是一對多組合關係。由於,一條訂單中每每包含多個產品,而一條訂單產品映射只能屬於某一條具體的訂單。而且訂單產品不能獨立於訂單而存在,所以他們之間是強依賴關係——組合關係。
  • order_state_time:訂單中各類狀態發生時間表。
    • 一條訂單有多種狀態,如:已下單、未支付、已支付、發貨中、已收穫等等。爲了可以詳細記錄訂單每一個狀態的發生時間,所以須要這張order_state_time表。
    • 訂單和訂單狀態之間是一對多的組合關係,即一條訂單能夠包含多種訂單狀態時間,而一種訂單狀態時間只能屬於某一條具體的訂單。而且訂單狀態時間不能獨立於訂單而存在,所以他們之間是強依賴關係——組合關係。

到此爲止,一個在線商城中最核心的三大系統的數據表關係已經梳理清楚了,下面將會根據具體的業務指標,對這些數據庫和數據表進行合理的分庫分表。

5. 高喜商城2.0分庫分表方案

在對高喜商城開始分庫分表以前,咱們先要搞清楚,究竟爲什麼要分庫?爲什麼要分表?爲什麼要讀寫分離?

  • 分庫的目的:將對同一個庫的讀寫壓力分攤到多個庫上,不一樣庫分佈在不一樣的服務器上,從而緩解每一個庫上的讀寫壓力,避免因服務器硬件資源(如IO、內存、CPU)致使的瓶頸。
  • 分表的目的:將本來一張表中的數據水平拆分至多張表中,從而避免單表過長,提高讀寫性能。
  • 讀寫分離的目的:將一個物理庫複製多份,主庫負責寫操做,從庫負責讀操做。從而避免少許的寫操做的表鎖或行鎖阻塞了大量的讀操做,經過下降數據的一致性來提高讀操做的性能。

5.1 用戶系統的分庫分表方案

系統的分庫分表策略必定是基於具體的業務指標和實際的業務需求,在正式進行分庫分表策略的設計以前,必定要作好這兩部分數據的採集。如今高喜商城的業務指標和業務需求以下面兩張表格所示:

高喜商城將來五年的業務指標:

表名 將來五年數據量 關鍵字段
sys_user 1000W uid, username, password, email, phone, role_id
location 5000W uid, location
receipt 5000W uid, 發票相關字段
sys_role 100 role_id, role_name
sys_permission 1000 pms_id, permission
sys_role_permission 100*1000 role_id, pms_id
sys_menu 200 menu_id, menu
sys_role_menu 200*1000 role_id, menu_id

高喜商城的業務需求:

表名 業務需求 涉及字段
sys_user 1.用戶登陸(用戶名登陸) username, password
2.用戶登陸(郵箱登陸) email, password
3.用戶登陸(短信驗證碼登陸) phone
4.根據uid查詢用戶信息 uid
5.管理員按照某些條件分頁查詢用戶 任何字段都有可能使用
表名 業務需求 涉及字段
location 根據uid查詢收貨地址 uid
表名 業務需求 涉及字段
receipt 根據uid查詢發票信息 uid

高喜商城將來五年預計將會擁有1000萬用戶,從而用戶表將會有1000萬條數據。因爲目前Mysql單表支持最大長度爲1000萬,所以爲了保險起見,咱們須要將用戶表水平拆分紅兩張。此外,爲了防止用戶表讀寫壓力過大,咱們乾脆將這兩張用戶表放入兩個物理庫中。而且爲了保證用戶表的高可用,咱們對這兩個數據庫採用主從複製技術,一主一叢,其結構以下圖所示:

從高喜商城將來五年的業務量表中可知,系統的角色、權限、菜單數量較少,沒有必要分庫分表。在用戶查詢的過程當中須要鏈接用戶表、角色表、權限表和菜單表,若是將這些無需拆分的表存儲在某一個數據庫中,那麼用戶表將沒法和他們進行跨庫鏈接,從而須要在完成用戶信息查詢後,在業務層再次根據uid分別查詢角色信息、權限信息、菜單信息,這無心增長了業務層的實現複雜度。爲了解決這個問題,咱們能夠對角色表、權限表和菜單表進行冗餘,即將這些表冗餘地存儲在sys_user的全部物理庫中,從而任何一個物理庫的用戶查詢操做均可以直接經過錶鏈接的方式完成角色信息、權限信息和菜單信息的查詢。其結構以下圖所示:

此外,用戶和收穫地址、用戶和發票信息之間都是一對多的組合關係,若是每一個用戶平均擁有5個收貨地址和5種發票信息,那麼對於1000萬用戶而言,一共會建立5000萬條收穫信息和5000萬條發票信息。所以,收穫地址和發票信息各需6張表來存儲。而且,因爲這兩種信息都是經過uid來查詢,而且查詢條件只有uid這一項,所以uid毫無爭議地成爲分片字段,而且這6張表只能分佈在6個物理庫中。此外,爲了實現數據庫的高可用性,須要對這6個庫提供主從複製功能。最終,收貨地址表和發票信息表的結構以下圖所示:

高喜商城用戶系統的數據庫分庫分表方案就介紹到這,下面介紹產品系統的分庫分表方案。

5.2 訂單系統的分庫分表方案

和用戶系統的分庫分表方案設計過程同樣,在方案設計以前,首先要肯定系統的業務指標和業務需求。

高喜商城將來五年的業務指標:

表名 將來五年數據量 關鍵字段
orders 2000W order_id, buyer_id, seller_id
orders_product 5*2000W order_id, prodcut_id
order_state_time 10*2000W order_id, state, time
  • 根據運營同窗的估算,高喜商城將來五年的訂單量最多將會達到2000W條,而且平均每條訂單中包含5件商品,所以orders_product表中的數據量將會達到10000W;而且每條訂單都有10種狀態,所以order_state_time表的數據量將會達到20000W。
  • 基於上述數據,orders須要水平拆分紅4張物理表,orders_product須要水平拆分紅20張物理表,order_state_time須要水平拆分紅40張物理表。
  • 那麼這寫物理表究竟該分配給多少個物理庫中?這須要由業務需求來決定。

高喜商城的業務需求:

表名 業務需求 涉及字段
orders 1.根據buyer_id分頁查詢某一用戶的訂單 buyer_id
2.根據order_id查詢訂單 order_id
3.根據seller_id分頁查詢某一商家的訂單 seller_id
表名 業務需求 涉及字段
orders_product 根據order_id查詢產品列表 order_id
表名 業務需求 涉及字段
order_state_time 1.根據order_id和state篩選某一狀態下的訂單 order_id, state
2.修改指定訂單的狀態 order_id, state

訂單系統的核心業務需求如上述三張表所示。orders表和orders_product表、order_state_time表之間都是一對多的組合關係,在查詢過程當中須要進行錶鏈接操做。所以,咱們必需要指定合理的分庫分表方案,可以使得同一訂單的產品信息、訂單狀態信息都落在同一個物理庫中,從而可以直接使用SQL語句進行鏈接操做。若是分庫分表方案不合理,那麼同一訂單的產品信息和訂單狀態信息會散落在不一樣的物理庫中,因爲Mysql並不支持跨庫鏈接,所以這三張表的鏈接須要拆分紅三次數據庫查詢,並在業務層完成數據的鏈接,這無心增長了業務層的複雜度。下面詳細介紹訂單系統的分庫分表方案。

經過分析上述三張業務需求表可知,訂單系統核心操做所涉及到的字段無非就是三個:order_id、buyer_id、seller_id。當查詢指定訂單的時候須要使用order_id做爲查詢條件,當查詢某一買家全部訂單的時候須要使用buyer_id做爲查詢條件,當查詢某一賣家全部訂單的時候須要使用seller_id做爲查詢條件。因而可知,分片字段須要從這三個字段中選擇。那麼究竟應該如何選擇呢?咱們分別來看以下三種方案:

  1. 將order_id做爲分片字段 若是將order_id做爲分片字段,那麼根據order_id查詢指定訂單的時候能夠直接定位到指定的物理表,然而在根據buyer_id和seller_id查詢訂單的時候,因爲沒法定位到具體的表,所以就須要全庫表查詢,這顯然是低效的。

  2. 將buyer_id做爲分片字段 此時,查詢指定買家的訂單信息能夠直接定位到指定的物理表,可是當須要根據order_id查詢具體訂單信息、查詢賣家訂單信息時就顯得提襟見肘了。

  3. 將seller_id做爲分片字段 此時,查詢指定賣家的全部訂單信息能夠定位到指定的物理表,但查詢買家訂單、根據訂單編號查詢訂單時就須要全庫表查詢了。

綜上所述,若是隻將這三個字段中的某一個做爲分片字段,顯然沒法知足全部的業務場景,一定會存在全庫表查詢,這就會致使查詢效率低下。那麼,有沒有什麼方案可以避免全庫表查詢呢?固然有!

首先,咱們來解決跨庫鏈接的問題。

解決跨庫鏈接問題的根本方法就是避免跨庫鏈接,讓須要鏈接的表存儲在同一個物理庫中。在訂單系統中,orders表要分別和orders_product表、order_state_time表產生鏈接,而且都是以order_id做爲鏈接字段。可是,若是咱們以order_id做爲這三張表的分片字段,那麼當根據buyer_id、seller_id查詢時,都須要全庫表操做。因此,咱們須要分別以buyer_id和seller_id做爲分片字段。聽上去很神奇,具體怎麼實施呢?

在訂單關係中,買家(buyer_id)和賣家(seller_id)是多對多的聚合關係,對於多對多關係,咱們可使用表冗餘來實現不一樣緯度的查詢。

此時,咱們須要將訂單表(orders)一分爲二,分別是買家訂單表(orders_buyer)賣家訂單表(orders_seller),這兩張表的數據是徹底一致的。在買家訂單表中,以buyer_id做爲分片字段;在賣家訂單表中,以seller_id做爲分片字段。那麼當須要查詢指定買家的訂單時,根據買家id(buyer_id)就能夠肯定該買家訂單數據所在的物理表;當須要查詢指定賣家的訂單時,根據賣家id(seller_id)就能夠肯定該賣家訂單數據所在的物理表。

此時已經避免了上述兩個業務場景的全庫表查詢,那麼還有兩種業務場景的全庫表查詢問題如何解決呢?

  1. 根據order_id查詢訂單
  2. orders表要分別和orders_product表、order_state_time表的鏈接操做
  • 對於第一個問題,買家(buyer_id)和訂單(order_id)之間是一對多組合關係。對於一對多組合關係,咱們能夠創建「多」——>「一」的映射。在這裏,咱們須要創建order_id——>buyer_id的映射關係。那麼當須要根據order_id查詢訂單的時候,首先須要查詢這個映射關係,找到order_id對應的buyer_id,因爲buyer_id是分片字段,所以能夠直接計算出數據所在的物理表,從而完成根據order_id查詢訂單的需求。
  • 對於第二個問題,咱們可使用字段冗餘的方法來解決。在建立買家訂單表和賣家訂單表的同時,再分別建立以下四個表:
    • 買家訂單產品表(orders_product_buyer)
      • (order_id, product_id, buyer_id)
      • 加入buyer_id字段,並以buyer_id做爲分片字段
    • 賣家訂單產品表(orders_product_seller)
      • (order_id, product_id, seller_id)
      • 加入seller_id字段,並以seller_id做爲分片字段
    • 買家訂單狀態表(order_state_time_buyer)
      • (order_id, state, time, buyer_id)
      • 加入buyer_id字段,並以buyer_id做爲分片字段
    • 賣家訂單狀態表(order_state_time_seller)
      • (order_id, state, time, seller_id)
      • 加入seller_id字段,並以seller_id做爲分片字段

此時,訂單系統的數據庫架構以下圖所示:

採用了上述方案後,全部的全庫表查詢問題都獲得瞭解決,但不要止步於此,還能夠進一步優化。

上述方案中,咱們使用了一張映射表來維護order_id和buyer_id之間的映射關係,當須要根據order_id查詢指定訂單的時候,先要查詢映射表,找到該訂單對應的buyer_id,而後再根據buyer_id計算分片,找到相應的物理表。

這個過程經歷了兩次地址查詢,還須要額外的策略存儲映射表。那麼,有沒有什麼方法可以解決這兩個問題呢?固然是有的,此時就要介紹個人黑科技了。

x%N的結果實際上是由x二進制的末尾logN位決定的

舉個例子,13534443 % 8,實際上是由13534443的二進制表示法的最後log8位決定的。

  • 13534443的二進制是:110011101000010011101011
  • log8=3

所以,13534443 % 8的結果由011決定。也就是說,只要末尾三位都是011的數字,對8取模的結果都是同樣的。

基於這個結論,咱們只要保證buyer_id和order_id的最後logN位一致,就無需再使用額外的映射表來存儲這二者的映射關係。order_id和buyer_id的生成方式以下:

  1. 在建立訂單時,首先獲取買家的uid
  2. 獲取uid二進制表示法的最後logN位,用lastN表示
  3. 將UUID+lastN做爲order_id

此時同一個買家的buyer_id%N的結果和order_id%N的結果一致。在根據order_id查詢訂單的時候直接經過order_id%N計算出訂單所在的物理庫便可。

5.3 產品系統的分庫分表方案

高喜商城將來五年的業務指標:

表名 將來五年數據量 關鍵字段
product 100W product_id
prod_image 10*100W product_id
brand 1000
category 100

整體而言,產品系統的數據量相對較小。運營同窗規劃,將來五年,高喜商城的產品數量最多爲100W,因爲每件產品最多容許擁有10張圖片,所以prod_image表的數量預計爲1000W,所以須要對prod_image表進行拆分;而產品的品牌、產品的類別數量較小,不須要考慮分庫分表。

產品系統的數據庫分庫分表方案以下圖所示:

因爲prod_image表的數據量將會達到1000W,所以爲了不單表數據超過1000W,將該表根據prod_id拆分紅兩張物理表。

此外,在產品系統中,product、brand、category數據量均不會超過1000W,所以無需分庫分表。

關注訂閱個人文章
相關文章
相關標籤/搜索