手把手帶你用資料庫中介軟體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>
<!-- 覺得文章對你有幫助的話,可以加一下我的企鵝技術交流圈:519752913,我會在裡面不定時分享乾貨資源,希望能夠對你有幫助 -->
<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