手把手帶你用數據庫中間件Mycat+SpringBoot完成分庫分表

一、背景


隨着時間和業務的發展,數據庫中的數據量增長是不可控的,庫和表中的數據會越來越大,隨之帶來的是更高的磁盤、IO、系統開銷,甚至性能上的瓶頸,而一臺服務的資源終究是有限的,因此需要對數據庫和表進行拆分,從而更好的提供數據服務。

當用戶表達到千萬級別,在做很多操作的時候都會很吃力,所以當數據增長到1000萬以上就需要分庫分表來緩解單庫(表)的壓力。

 

二、什麼是分庫分表[1]


簡單來說,就是指通過某種特定的條件,將我們存放在同一個數據庫中的數據分散存放到多個數據庫(主機)上面,以達到分散單臺設備負載的效果。

數據的切分(Sharding)根據其切分規則的類型,可以分爲兩種切分模式。一種是按照不同的表(或者Schema)來切分到不同的數據庫(主機)之上,這種切可以稱之爲數據的垂直(縱向)切分;另外一種則是根據表中的數據的邏輯關係,將同一個表中的數據按照某種條件拆分到多臺數據庫(主機)上面,這種切分稱之爲數據的水平(橫向)切分。

垂直切分的最大特點就是規則簡單,實施也更爲方便,尤其適合各業務之間的耦合度非常低,相互影響很小,業務邏輯非常清晰的系統。在這種系統中,可以很容易做到將不同業務模塊所使用的表分拆到不同的數據庫中。根據不同的表來進行拆分,對應用程序的影響也更小,拆分規則也會比較簡單清晰。

水平切分於垂直切分相比,相對來說稍微複雜一些。因爲要將同一個表中的不同數據拆分到不同的數據庫中,對於應用程序來說,拆分規則本身就較根據表名來拆分更爲複雜,後期的數據維護也會更爲複雜一些。

 

三、垂直切分 [1]


一個數據庫由很多表構成,每個表對應着不同的業務,垂直切分是指按照業務將表進行分類,分佈到不同的數據庫上面,這樣也就將數據或者說壓力分擔到不同的庫上面,如下圖:

系統被切分成了,用戶,訂單交易,支付幾個模塊。
一個架構設計較好的應用系統,其總體功能肯定是由很多個功能模塊所組成的,而每一個功能模塊所需要的數據對應到數據庫中就是一個或者多個表。而在架構設計中,各個功能模塊相互之間的交互點越統一越少,系統的耦合度就越低,系統各個模塊的維護性以及擴展性也就越好。這樣的系統,實現數據的垂直切分也就越容易。

但是往往系統之間有些表難以做到完全的獨立,存在着跨庫 join 的情況,對於這類的表,就需要去做平衡,是數據庫讓步業務,共用一個數據源,還是分成多個庫,業務之間通過接口來做調用。在系統初期,數據量比較少,或者資源有限的情況下,會選擇共用數據源,但是當數據發展到了一定的規模,負載很大的情況,就必須去做分割。

一般來講業務存在着複雜 join 的場景是難以切分的,往往業務獨立的易於切分。如何切分,切分到何種程度是考驗技術架構的一個難題。
下面來分析下垂直切分的優缺點:

優點

拆分後業務清晰,拆分規則明確;

系統之間整合或擴展容易;

數據維護簡單。

缺點

部分業務表無法 join,只能通過接口方式解決,提高了系統複雜度;

受每種業務不同的限制存在單庫性能瓶頸,不易數據擴展跟性能提高;

事務處理複雜。

由於垂直切分是按照業務的分類將表分散到不同的庫,所以有些業務表會過於龐大,存在單庫讀寫與存儲瓶頸,所以就需要水平拆分來做解決。

 

四、水平切分 [1]


相對於垂直拆分,水平拆分不是將表做分類,而是按照某個字段的某種規則來分散到多個庫之中,每個表中包含一部分數據。簡單來說,我們可以將數據的水平切分理解爲是按照數據行的切分,就是將表中的某些行切分到一個數據庫,而另外的某些行又切分到其他的數據庫中,如圖

拆分數據就需要定義分片規則。關係型數據庫是行列的二維模型,拆分的第一原則是找到拆分維度。比如:
從會員的角度來分析,商戶訂單交易類系統中查詢會員某天某月某個訂單,那麼就需要按照會員結合日期來拆分,不同的數據按照會員 ID 做分組,這樣所有的數據查詢 join 都會在單庫內解決;如果從商戶的角度來講,要查詢某個商家某天所有的訂單數,就需要按照商戶 ID 做拆分;但是如果系統既想按會員拆分,又想按商家數據,則會有一定的困難。如何找到合適的分片規則需要綜合考慮衡。
幾種典型的分片規則包括:

按照用戶 ID 求模,將數據分散到不同的數據庫,具有相同數據用戶的數據都被分散到一個庫中;

按照日期,將不同月甚至日的數據分散到不同的庫中;

按照某個特定的字段求摸,或者根據特定範圍段分散到不同的庫中。

如圖,切分原則都是根據業務找到適合的切分規則分散到不同的庫,下面用用戶 ID 求模舉例:

既然數據做了拆分有優點也就有缺點。


優點

拆分規則抽象好,join 操作基本可以同一數據庫做;

不存在單庫大數據,高併發的性能瓶頸;

應用端改造較少;

提高了系統的穩定性跟負載能力。

缺點

拆分規則難以抽象;

分片事務一致性難以解決;

數據多次擴展難度跟維護量極大;

跨庫 join 性能較差

 

五、什麼是Mycat


它是一個開源的分佈式數據庫系統,是一個實現了 MySQL 協議的Server,前端用戶可以把它看作是一個數據庫代理,用 MySQL 客戶端工具和命令行訪問,而其後端可以用MySQL 原生(Native)協議與多個 MySQL 服務器通信,也可以用 JDBC 協議與大多數主流數據庫服務器通信,其核心功能是分表分庫,即將一個大表水平分割爲 N 個小表,存儲在後端 MySQL 服務器裏或者其他數據庫裏。

常見應用場景:

  • 單純的讀寫分離,此時配置最爲簡單,支持讀寫分離,主從切換;
  • 分表分庫,對於超過 1000 萬的表進行分片,最大支持 1000 億的單表分片;
  • 多租戶應用,每個應用一個庫,但應用程序只連接 Mycat,從而不改造程序本身,實現多租戶化;
  • 報表系統,藉助於 Mycat 的分表能力,處理大規模報表的統計;
  • 替代 Hbase,分析大數據;

作爲海量數據實時查詢的一種簡單有效方案,比如 100 億條頻繁查詢的記錄需要在 3 秒內查詢出來結果,除了基於主鍵的查詢,還可能存在範圍查詢或其他屬性查詢,此時 Mycat 可能是最簡單有效的選擇

 

六、SpringBoot+Mycat+MySQL實現分表分庫案例


關於分庫分表,Mycat已經幫我們在內部實現了路由的功能,我們只需要在Mycat中配置以下切分規則即可,對於開發者來說,我們就可以把Mycat看做是一個數據庫,接下來我們開始搭建環境:

步驟一:

Mycat是使用java寫的數據庫中間件,所以在運行Mycat前要準備要jdk環境,要求是jdk1.7以上的環境。所以需要在系統中配置JAVA_HOME的環境變量.

步驟二:

從官網下載Mycat,http://dl.mycat.io/1.6-RELEASE/   我們是基於CentOS7來搭建Mycat環境的,所以下載版本:
Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz

步驟三:

將下載好的安裝包上傳到服務器上並解壓.解壓之後目錄結構如下:

步驟四:

配置切分規則:
將如下配置複製粘貼覆蓋mycat/conf/schema.xml的內容。

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

   <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
           <table name="user" primaryKey="id" dataNode="dn01,dn02" rule="rule1" />  
   </schema>
   
   <!-- 設置dataNode 對應的數據庫,及 mycat 連接的地址dataHost -->  
   <dataNode name="dn01" dataHost="dh01" database="db01" />  
   <dataNode name="dn02" dataHost="dh01" database="db02" />   
   
   <!-- mycat 邏輯主機dataHost對應的物理主機.其中也設置對應的mysql登陸信息 -->  
   <dataHost name="dh01" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native">  
           <heartbeat>select user()</heartbeat>  
           <writeHost host="server1" url="127.0.0.1:3306" user="root" password="WolfCode_2017"/>  
   </dataHost> 
</mycat:schema>

<schema>:表示的是在mycat中的邏輯庫配置,邏輯庫名稱爲:TESTDB

<table>:表示在mycat中的邏輯表配置,邏輯表名稱爲:user,映射到兩個數據庫節點dataNode中,切分規則爲:rule1(在rule.xml配置)

<dataNode>:表示數據庫節點,這個節點不一定是單節點,可以配置成讀寫分離.

<dataHost>:真實的數據庫的地址配置

<heartbeat>:用戶心跳檢測

<writeHost>:寫庫的配置

將如下配置複製粘貼覆蓋mycat/conf/rule.xml的內容。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
   <tableRule name="rule1">
       <rule>
           <columns>id</columns>
           <algorithm>mod-long</algorithm>
       </rule>
   </tableRule>
   <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
       <!-- how many data nodes -->
       <property name="count">2</property>
   </function>
</mycat:rule>

這裏定義的是切分規則,是按照id列進行切分,切分規則是採取取模的方式,
<property name="count">2</property>:這裏配置了我們有拆分了多個庫(表),需要和前面配置
<table name="user" primaryKey="id" dataNode="dn01,dn02" rule="rule1" />中的dataNode個數一致,否則會出錯.

步驟五:

在數據庫中創建兩個數據庫db01,db02.
每個庫中執行如下建表語句:

CREATE TABLE `user` (
 `id` bigint(20) NOT NULL,
 `name` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

步驟六:

啓動mycat,執行mycat/bin/startup_nowrap.sh

步驟七:

項目已經上傳到github
https://github.com/javalanxiongwei/springboot-mycat


搭建SpringBoot環境,執行插入語句。
application.properties配置如下

#配置數據源
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
#這裏配置的是Mycat中server.xml中配置賬號密碼,不是數據庫的密碼。
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
#mycat的邏輯庫 端口也是mycat的
spring.datasource.druid.url=jdbc:mysql://192.168.142.129:8066/TESTDB

UserMapper.java代碼如下:

@Mapper
public interface UserMapper {
   @Insert("insert into user(id,name) value (#{id},#{name})")
   int insert(User user);
   @Select("select * from user")
   List<User> selectAll();
}

UserController.java代碼如下:

@RestController
@RequestMapping("/user")
public class UserController {
   @Autowired
   private UserMapper userMapper;

   @RequestMapping("/save")
   public String save(User user){
       userMapper.insert(user);
       return "保存成功";
   }

   @RequestMapping("/list")
   public List<User> list(){
       return userMapper.selectAll();
   }
}

 

步驟八:

測試:
在地址欄輸入:
http://localhost:8080/user/save?id=1&name=tom
http://localhost:8080/user/save?id=2&name=jack
查看數據庫發現:
id爲1的數據插入到數據庫db02中的user表。
id爲2的數據插入到數據庫db01中的user表。
在地址欄輸入:
http://localhost:8080/user/list
是可以看到剛剛插入的兩條記錄.

好到這一步我們就已經完成了分表分庫了.


 

參考mycat-definitive-guide.pdf。 ↩ ↩ ↩


作者:叩丁狼教育 藍雄威

鏈接:https://www.jianshu.com/p/f81422b1c915

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章