一種簡單易懂的 MyBatis 分庫分表方案

數據庫分庫分表除了使用中間件來代理請求分發以外,另一種常見的方法就是在客戶端層面來分庫分表 —— 經過適當地包裝客戶端代碼使得分庫分表的數據庫訪問操做代碼編寫起來也很方便。本文的分庫分表方案基於 MyBatis 框架,可是又不一樣於市面上經常使用的方案,它們通常都是經過編寫複雜的 MyBatis 插件來重寫 SQL 語句,這樣的插件代碼會巨複雜無比,可能最終只有插件的原做者本身能夠徹底吃透相關代碼,給項目的維護性帶來必定問題。本文的方案很是簡單易懂,並且也不失使用上的便捷性。它的設計哲學來源於 Python —— Explicit is better than Implicit,也就是顯式優於隱式,它不會將分庫分表的過程隱藏起來。mysql

不少分庫分表的設計在實現上會盡可能將分庫分表的邏輯隱藏起來,其實這是毫無必要的。使用者必須知道背後確實進行了分庫分表,不然他怎麼會沒法進行全局的索引查找?他怎麼會沒法隨意進行多表的 join 操做。若是你真的將它當成單表來用,到上線時必然會出大問題。git

項目名稱叫:shardino,項目地址:https://github.com/pyloque/shardinogithub

接下來咱們來看看在本文的方案之下,數據庫操做代碼的形式是怎樣的算法

帖子表一共分出來 64 個表,不一樣的記錄會各自分發到其中一個表,能夠是按 hash 分發,也能夠按照日期分發,分發邏輯由用戶代碼本身來決定。在不一樣的環境中能夠將分表數量設置爲不一樣的值,好比在單元測試下分表設爲 4 個,而線上可能須要設置爲 64 個。spring

帖子表又會被分配到多個庫,這裏就直接取模分配。假設有 4 個帖子庫,帖子表總共分出來 64 個表,分別是 post_0、post_一、post_2 一直到 post_63。那麼 post_0、post_四、post_8 等分配到 0 號庫,post_一、post_五、post_9 等分配到 1 號庫,post_二、post_六、post_10 等分配到 2 號庫,post_三、post_五、post_11 等分配到 4 號庫。sql

從配置文件中構建 MySQLGroupStore 數據庫組對象,這個對象是咱們執行 MySQL 操做的入口,經過它能夠找到具體的物理的 MySQL 主從數據源。docker

配置文件 application.properties 以下數據庫

這裏的數據庫組是由多個對等的 Master-Slaves 對構成,每一個 Master-Slaves 是由一個主庫和多個不一樣權重的從庫構成,Master-Slaves 對的數量就是分庫的數量。springboot

mysqlgroup 還有一個特殊的配置選項 slaveEnabled 來控制是否須要從庫,從而關閉讀寫分離,默認是關閉的,這樣就不會去構建從庫實例相關對象。session

post_k 這張表後綴 k 咱們稱之爲 partition number,也就是後續代碼中處處在用的 partition 變量,代表當前的記錄被分配到對應物理數據表的序號。咱們須要根據記錄的內容計算出 partition number,再根據 partition number 決定出這條記錄所在的物理表屬於那個物理數據庫,而後對這個物理數據庫進行相應的讀寫操做。

在本例中,帖子表按照 userId 字段 hash 出 64 張表,平均分配到 2 對物理庫中,每一個物理庫包含一個主庫和2個從庫。

有了 MySQLGroupStore 實例,咱們就能夠盡情操縱全部數據庫了。

 

從上面的代碼中能夠看出全部的讀寫、建立、刪除表操做的第一步都是計算出 partition number,而後根據它來選出目標主從庫再進一步對目標的數據表進行操做。這裏我默認開啓了autocommit,因此不須要顯式來 session.commit() 了。

在對數據表的操做過程當中,又須要將具體的 partition number 傳遞過去,如此 MyBatis 才能知道具體操做的是哪一個分表。

在每一條數據庫操做中都必須帶上 partition 參數,你可能會以爲這有點繁瑣。可是這也很直觀,它明確地告訴咱們目前正在操做的是哪個具體的分表。

在 MyBatis 的註解 Mapper 類中,若是方法含有多個參數,須要使用 @Param 註解進行名稱標註,這樣才能夠在 SQL 語句中直接使用相應的註解名稱。不然你得使用默認的變量佔位符名稱 param0、param1 來表示,這就很不直觀。

咱們將分表的 hash 算法寫在實體類 Post 中,這裏使用 CRC32 算法進行 hash。

 

代碼中的 partitionFor 方法的參數 num 就是一共要分多少表。若是是按日期來分表,這個參數可能就不須要,直接返回日期的整數就行好比 20190304。

還有最後一個問題是多個帶權重的從庫是如何作到機率分配的。這裏就要使用到 spring-jdbc 自帶的 AbstractRoutingDataSource —— 帶路由功能的數據源。它能夠包含多個子數據源,而後根據必定的策略算法動態挑選出一個數據源來,這裏就是使用權重隨機。

可是有個問題,我這裏只須要這一個類,可是須要引入整個 spring-boot-jdbc-starter 包,有點拖泥帶水的感受。我研究了一下 AbstractRoutingDataSource 類的代碼,發現它的實現很是簡單,若是就仿照它本身實現了一個簡單版的,這樣就不須要引入整個包代碼了。

還需進一步深刻理解其實現代碼的能夠將 shardino 代碼倉庫拉到本地跑一跑

裏面有單元測試能夠運行起來,運行以前須要確保本機安裝了 docker 環境

這條指令會啓動2對主從庫,各1主兩從。

在本例中雖然用到了 springboot ,其實也只是用了它方便的依賴注入和單元測試功能,shardino 徹底能夠脫離 springboot 而獨立存在。

shardino 並非一個完美的開源庫,它只是一份實現代碼的樣板,若是讀者使用的是其它數據庫或者 MySQL 的其它版本,那就須要本身微調一下代碼來適配了。

歡迎工做一到五年的Java工程師朋友們加入個人我的粉絲羣Java填坑之路:789337293 羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!

相關文章
相關標籤/搜索