資料庫分庫分表策略的具體實現方案【從MySQL開始】
一、MySQL擴充套件具體的實現方式
隨著業務規模的不斷擴大,需要選擇合適的方案去應對資料規模的增長,以應對逐漸增長的訪問壓力和資料量。
關於資料庫的擴充套件主要包括:業務拆分、主從複製,資料庫分庫與分表。這篇文章主要講述資料庫分庫與分表
(1)業務拆分
在 大型網站應用之海量資料和高併發解決方案總結一二 一篇文章中也具體講述了為什麼要對業務進行拆分。
業務起步初始,為了加快應用上線和快速迭代,很多應用都採用集中式的架構。隨著業務系統的擴大,系統變得越來越複雜,越來越難以維護,開發效率變得越來越低,並且對資源的消耗也變得越來越大,通過硬體提高系統性能的方式帶來的成本也越來越高。
因此,在選型初期,一個優良的架構設計是後期系統進行擴充套件的重要保障。
例如:電商平臺,包含了使用者、商品、評價、訂單等幾大模組,最簡單的做法就是在一個數據庫中分別建立users、shops、comment、order四張表。
但是,隨著業務規模的增大,訪問量的增大,我們不得不對業務進行拆分。每一個模組都使用單獨的資料庫來進行儲存,不同的業務訪問不同的資料庫,將原本對一個數據庫的依賴拆分為對4個數據庫的依賴,這樣的話就變成了4個數據庫同時承擔壓力,系統的吞吐量自然就提高了。
(2)主從複製
上圖是網上的一張關於MySQL的Master和Slave之間資料同步的過程圖。
主要講述了MySQL主從複製的原理:資料複製的實際就是Slave從Master獲取Binary log檔案,然後再本地映象的執行日誌中記錄的操作。由於主從複製的過程是非同步的,因此Slave和Master之間的資料有可能存在延遲的現象,此時只能保證資料最終的一致性。
(3)資料庫分庫與分表
我們知道每臺機器無論配置多麼好它都有自身的物理上限,所以當我們應用已經能觸及或遠遠超出單臺機器的某個上限的時候,我們惟有尋找別的機器的幫助或者繼續升級的我們的硬體,但常見的方案還是通過新增更多的機器來共同承擔壓力。
我們還得考慮當我們的業務邏輯不斷增長,我們的機器能不能通過線性增長就能滿足需求?因此,使用資料庫的分庫分表,能夠立竿見影的提升系統的效能,關於為什麼要使用資料庫的分庫分表的其他原因這裡不再贅述,主要講具體的實現策略。請看下邊章節。
二、分表實現策略
關鍵字:使用者ID、表容量
對於大部分資料庫的設計和業務的操作基本都與使用者的ID相關,因此使用使用者ID是最常用的分庫的路由策略。使用者的ID可以作為貫穿整個系統用的重要欄位。因此,使用使用者的ID我們不僅可以方便我們的查詢,還可以將資料平均的分配到不同的資料庫中。(當然,還可以根據類別等進行分表操作,分表的路由策略還有很多方式)
說到這裡順便給大家推薦一個Java架構方面的交流學習群:964357187,裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。還能領取免費的學習資源和前輩的面試經驗和麵試題,相信對於已經工作和遇到技術瓶頸的碼友,在這個群裡會有你需要的內容。
接著上述電商平臺假設,訂單表order存放使用者的訂單資料,sql指令碼如下(只是為了演示,省略部分細節):
CREATE TABLE `order` (
`order_id` bigint(32) primary key auto_increment,
`user_id` bigint(32),
...
)
當資料比較大的時候,對資料進行分表操作,首先要確定需要將資料平均分配到多少張表中,也就是:表容量。
這裡假設有100張表進行儲存,則我們在進行儲存資料的時候,首先對使用者ID進行取模操作,根據 user_id%100 獲取對應的表進行儲存查詢操作,示意圖如下:
例如,user_id = 101 那麼,我們在獲取值的時候的操作,可以通過下邊的sql語句:
select * from order_1 where user_id= 101
其中,order_1是根據 101%100 計算所得,表示分表之後的第一章order表。
注意:
在實際的開發中,如果你使用MyBatis做持久層的話,MyBatis已經提供了很好得支援資料庫分表的功能,例如上述sql用MyBatis實現的話應該是:
介面定義:
/**
* 獲取使用者相關的訂單詳細資訊
* @param tableNum 具體某一個表的編號
* @param userId 使用者ID
* @return 訂單列表
*/
public List<Order> getOrder(@Param("tableNum") int tableNum,@Param("userId") int userId);
xml配置對映檔案:
<select id="getOrder" resultMap="BaseResultMap">
select * from order_${tableNum}
where user_id = #{userId}
</select>
其中${tableNum} 含義是直接讓引數加入到sql中,這是MyBatis支援的特性。
注意:
另外,在實際的開發中,我們的使用者ID更多的可能是通過UUID生成的,這樣的話,我們可以首先將UUID進行hash獲取到整數值,然後在進行取模操作。
三、分庫實現策略
資料庫分表能夠解決單表資料量很大的時候資料查詢的效率問題,但是無法給資料庫的併發操作帶來效率上的提高,因為分表的實質還是在一個數據庫上進行的操作,很容易受資料庫IO效能的限制。
因此,如何將資料庫IO效能的問題平均分配出來,很顯然將資料進行分庫操作可以很好地解決單臺數據庫的效能問題。
分庫策略與分表策略的實現很相似,最簡單的都是可以通過取模的方式進行路由。
還是上例,將使用者ID進行取模操作,這樣的話獲取到具體的某一個數據庫,同樣關鍵字有:
使用者ID、庫容量
路由的示意圖如下:
上圖中庫容量為100。
同樣,如果使用者ID為UUID請先hash然後在進行取模。
四、分庫與分表實現策略
上述的配置中,資料庫分表可以解決單表海量資料的查詢效能問題,分庫可以解決單臺數據庫的併發訪問壓力問題。
有時候,我們需要同時考慮這兩個問題,因此,我們既需要對單表進行分表操作,還需要進行分庫操作,以便同時擴充套件系統的併發處理能力和提升單表的查詢效能,就是我們使用到的分庫分表。
分庫分表的策略相對於前邊兩種複雜一些,一種常見的路由策略如下:
1、中間變數 = user_id%(庫數量*每個庫的表數量);
2、庫序號 = 取整(中間變數/每個庫的表數量);
3、表序號 = 中間變數%每個庫的表數量;
例如:資料庫有256 個,每一個庫中有1024個數據表,使用者的user_id=262145,按照上述的路由策略,可得:
1、中間變數 = 262145%(256*1024)= 1;
2、庫序號 = 取整(1/1024)= 0;
3、表序號 = 1%1024 = 1;
這樣的話,對於user_id=262145,將被路由到第0個數據庫的第1個表中。
示意圖如下:
五、分庫分表總結
關於分庫分表策略的選擇有很多種,上文中根據使用者ID應該是比較簡單的一種。其他方式比如使用號段進行分割槽或者直接使用hash進行路由等。有興趣的可以自行查詢學習。
關於上文中提到的,如果使用者的ID是通過UUID的方式生成的話,我們需要單獨的進行一次hash操作,然後在進行取模操作等,其實hash本身就是一種分庫分表的策略,使用hash進行路由策略的時候,我們需要知道的是,也就是hash路由策略的優缺點,優點是:資料分佈均勻;缺點是:資料遷移的時候麻煩,不能按照機器效能分攤資料。
上述的分庫和分表操作,查詢效能和併發能力都得到了提高,但是還有一些需要注意的就是,例如:原本跨表的事物變成了分散式事物;由於記錄被切分到不同的資料庫和不同的資料表中,難以進行多表關聯查詢,並且不能不指定路由欄位對資料進行查詢。分庫分表之後,如果我們需要對系統進行進一步的擴陣容(路由策略變更),將變得非常不方便,需要我們重新進行資料遷移。
六、總結
上述中,我們學到了如何進行資料庫的讀寫分離和分庫分表,那麼,是不是可以實現一個可擴充套件、高效能、高併發的網站那?很顯然還不可以!一個大型的網站使用到的技術遠不止這些,可以說,這些都是其中的最基礎的一個環節,因為還有很多具體的細節我們沒有掌握到,比如:資料庫的叢集控制,叢集的負載均衡,災難恢復,故障自動切換,事務管理等等技術。因此,還有很多需要去學習去研究的地方,大家也可以進我的私人技術交流群【Java高階網際網路架構:964357187】與更多的阿里京東大佬交流、切磋技術。
總之:
路漫漫其修遠兮,吾將上下而求索。
前方道路美好而光明,2019年新