特別說明: 本人平時混跡于 B 站,不咋回復這里的評論,有問題可以到 B 站視頻評論區留言找我
視頻地址: https://space.bilibili.com/31137138/favlist?fid=326428938
課件說明: 本次提供的課件是 Spring Cloud Netflix 版微服務架構指南,如果有興趣想要學習 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相關課程資源
案例代碼: https://github.com/topsale/hello-spring-cloud-netflix
概述
Mycat 是什么?從定義和分類來看,它是一個開源的分布式數據庫系統,是一個實現了 MySQL 協議的 Server,前端用戶可以把它看作是一個數據庫代理,用 MySQL 客戶端工具和命令行訪問,而其后端可以用 MySQL 原生(Native) 協議與多個 MySQL 服務器通信,也可以用 JDBC 協議與大多數主流數據庫服務器通信,其核心功能是分表分庫,即將一個大表水平分割為 N 個小表,存儲在后端 MySQL 服務器里或者其他數據庫里
什么是分庫分表(數據切分)
簡單來說,就是指通過某種特定的條件,將我們存放在同一個數據庫中的數據分散存放到多個數據庫(主機)上面,以達到分散單臺設備負載的效果
數據的切分(Sharding)根據其切分規則的類型,可以分為兩種切分模式。一種是按照不同的表(或者 Schema)來切分到不同的數據庫(主機)之上,這種切可以稱之為數據的垂直(縱向)切分;另外一種則是根據表中的數據的邏輯關系,將同一個表中的數據按照某種條件拆分到多臺數據庫(主機)上面,這種切分稱之為數據的水平(橫向)切分
垂直切分的最大特點就是規則簡單,實施也更為方便,尤其適合各業務之間的耦合度非常低,相互影響很小,業務邏輯非常清晰的系統。在這種系統中,可以很容易做到將不同業務模塊所使用的表分拆到不同的數據庫中。根據不同的表來進行拆分,對應用程序的影響也更小,拆分規則也會比較簡單清晰
水平切分于垂直切分相比,相對來說稍微復雜一些。因為要將同一個表中的不同數據拆分到不同的數據庫中,對于應用程序來說,拆分規則本身就較根據表名來拆分更為復雜,后期的數據維護也會更為復雜一些
垂直切分
一個數據庫由很多表的構成,每個表對應著不同的業務,垂直切分是指按照業務將表進行分類,分布到不同的數據庫上面,這樣也就將數據或者說壓力分擔到不同的庫上面,如下圖:
系統被切分成了,用戶,訂單交易,支付幾個模塊。一個架構設計較好的應用系統,其總體功能肯定是由很多個功能模塊所組成的,而每一個功能模塊所需要的數據對應到數據庫中就是一個或者多個表。而在架構設計中,各個功能模塊相互之間的交互點越統一越少,系統的耦合度就越低,系統各個模塊的維護性以及擴展性也就越好。這樣的系統,實現數據的垂直切分也就越容易
但是往往系統之有些表難以做到完全的獨立,存在這擴庫 join 的情況,對于這類的表,就需要去做平衡,是數據庫讓步業務,共用一個數據源,還是分成多個庫,業務之間通過接口來做調用。在系統初期,數據量比較少,或者資源有限的情況下,會選擇共用數據源,但是當數據發展到了一定的規模,負載很大的情況,就需要必須去做分割
一般來講業務存在著復雜 join
的場景是難以切分的,往往業務獨立的易于切分。 如何切分,切分到何種程度是考驗技術架構的一個難題
-
優點
- 拆分后業務清晰,拆分規則明確
- 系統之間整合或擴展容易
- 數據維護簡單
-
缺點
- 部分業務表無法
join
,只能通過接口方式解決,提高了系統復雜度 - 受每種業務不同的限制存在單庫性能瓶頸,不易數據擴展跟性能提高
- 事務處理復雜
- 部分業務表無法
由于垂直切分是按照業務的分類將表分散到不同的庫,所以有些業務表會過于龐大,存在單庫讀寫與存儲瓶頸,所以就需要水平拆分來做解決
水平切分
相對于垂直拆分,水平拆分不是將表做分類,而是按照某個字段的某種規則來分散到多個庫之中,每個表中包含一部分數據。簡單來說,我們可以將數據的水平切分理解為是按照數據行的切分,就是將表中的某些行切分到一個數據庫,而另外的某些行又切分到其他的數據庫中,如圖:
拆分數據就需要定義分片規則。關系型數據庫是行列的二維模型,拆分的第一原則是找到拆分維度。 比如:從會員的角度來分析,商戶訂單交易類系統中查詢會員某天某月某個訂單,那么就需要按照會員結合日期來拆分,不同的數據按照會員 ID
做分組,這樣所有的數據查詢 join
都會在單庫內解決;如果從商戶的角度來講,要查詢某個商家某天所有的訂單數,就需要按照商戶 ID
做拆分;但是如果系統既想按會員拆分,又想按商家數據,則會有一定的困難。如何找到合適的分片規則需要綜合考慮衡量。幾種典型的分片規則包括:
- 按照用戶
ID
求模,將數據分散到不同的數據庫,具有相同數據用戶的數據都被分散到一個庫中 - 按照日期,將不同月甚至日的數據分散到不同的庫中
- 按照某個特定的字段求摸,或者根據特定范圍段分散到不同的庫中
如圖,切分原則都是根據業務找到適合的切分規則分散到不同的庫,下面用用戶 ID
求模舉例
-
優點
- 拆分規則抽象好,
join
操作基本可以數據庫做 - 不存在單庫大數據,高并發的性能瓶頸
- 應用端改造較少
- 提高了系統的穩定性跟負載能力
- 拆分規則抽象好,
-
缺點
- 拆分規則難以抽象
- 分片事務一致性難以解決
- 數據多次擴展難度跟維護量極大
- 跨庫
join
性能較差
-
垂直與水平拆分的共同缺點
- 引入分布式事務的問題
- 跨節點
join
的問題 - 跨節點合并排序分頁問題
- 多數據源管理問題
數據源管理方案
- 客戶端模式,在每個應用程序模塊中配置管理自己需要的一個(或者多個)數據源,直接訪問各個數據庫,在模塊內完成數據的整合
- 通過中間代理層來統一管理所有的數據源,后端數據庫集群對前端應用程序透明
絕大部分人在面對上面這兩種解決思路的時候都會傾向于選擇第二種,尤其是系統不斷變得龐大復雜的時候。確實,這是一個非常正確的選擇,雖然短期內需要付出的成本可能會相對更大一些,但是對整個系統的擴展性來說,是非常有幫助的
數據庫中間件 MyCat
MyCat 是一個強大的數據庫中間件,不僅僅可以用作讀寫分離、以及分表分庫、容災備份,而且可以用于多租戶應用開發、云平臺基礎設施、讓你的架構具備很強的適應性和靈活性,借助于即將發布的 MyCat 智能優化模塊,系統的數據訪問瓶頸和熱點一目了然,根據這些統計分析數據,你可以自動或手工調整后端存儲,將不同的表映射到不同存儲引擎上,而整個應用的代碼一行也不用改變
應用場景
- 單純的讀寫分離,此時配置最為簡單,支持讀寫分離,主從切換
- 分表分庫,對于超過 1000 萬的表進行分片,最大支持 1000 億的單表分片
- 多租戶應用,每個應用一個庫,但應用程序只連接 MyCat,從而不改造程序本身,實現多租戶化
- 報表系統,借助于 MyCat 的分表能力,處理大規模報表的統計
- 替代 Hbase,分析大數據
- 作為海量數據實時查詢的一種簡單有效方案,比如 100 億條頻繁查詢的記錄需要在 3 秒內查詢出來結果,除了基于主鍵的查詢,還可能存在范圍查詢或其他屬性查詢,此時 MyCat 可能是最簡單有效的選擇
環境準備
部署方式全部基于 Docker
部署 3 臺 MySQL 容器
案例分片方案按照自定義數字范圍分片方式(auto-sharding-long
)進行數據庫分片,其規則要求需要 3 臺 MySQL,docker-compose.yml
配置如下
- mysql-1
version: '3.1'
services:
mysql-1:
image: mysql
container_name: mysql-1
environment:
MYSQL_ROOT_PASSWORD: 123456
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
ports:
- 3306:3306
volumes:
- ./data:/var/lib/mysql
- mysql-2
version: '3.1'
services:
mysql-2:
image: mysql
container_name: mysql-2
environment:
MYSQL_ROOT_PASSWORD: 123456
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
ports:
- 3307:3306
volumes:
- ./data:/var/lib/mysql
- mysql-3
version: '3.1'
services:
mysql-3:
image: mysql
container_name: mysql-3
environment:
MYSQL_ROOT_PASSWORD: 123456
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
ports:
- 3308:3306
volumes:
- ./data:/var/lib/mysql
部署 MyCat 數據庫中間件
- 克隆
git clone https://github.com/dekuan/docker.mycat.git
- 構建
cd docker.mycat
docker-compose build
- 啟動
# 注意:配置完成后再啟動
docker-compose up -d
配置 MyCat 數據庫分片
- 服務端用戶名密碼配置:
vi config/mycat/server.xml
,找到第 90 行,參考如下內容配置
<mycat:server xmlns:mycat="http://io.mycat/">
<!-- Mycat 數據庫用戶名 -->
<user name="root">
<!-- Mycat 數據庫密碼 -->
<property name="password">123456</property>
<!-- Mycat 數據庫名 -->
<property name="schemas">myshop</property>
<!-- 是否使用加密的密碼,0 表示不使用加密的密碼 -->
<property name="usingDecrypt">0</property>
</user>
</mycat:server>
- 數據節點、數據庫、分庫分表配置:
vi config/mycat/schema.xml
,參考如下內容配置
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="myshop" checkSQLschema="true" sqlMaxLimit="100">
<!-- 需要分片的表
在節點 dataNode1, dataNode2, dataNode3 上分片
分片規則是 auto-sharding-long 即連續分片規則之自定義數字范圍分片
對應的分片規則配置文件在 config/mycat/rule.xml
-->
<table
name="tb_admin"
primaryKey="id"
dataNode="dataNode1,dataNode2,dataNode3"
rule="auto-sharding-long"/>
</schema>
<!-- 數據節點 dataNode1,對應的主機 dataHost1, 對應是數據庫 myshop_1 -->
<dataNode name="dataNode1" dataHost="dataHost1" database="myshop_1" />
<dataNode name="dataNode2" dataHost="dataHost2" database="myshop_2" />
<dataNode name="dataNode3" dataHost="dataHost3" database="myshop_3" />
<!-- 主機 dataHost1 -->
<dataHost name="dataHost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- MySQL 數據庫的連接串 -->
<writeHost
host="192.168.141.206"
url="jdbc:mysql://192.168.141.206:3306?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"
user="root" password="123456">
<!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
</writeHost>
</dataHost>
<dataHost name="dataHost2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost
host="192.168.141.206"
url="jdbc:mysql://192.168.141.206:3307?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"
user="root" password="123456">
<!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
</writeHost>
</dataHost>
<dataHost name="dataHost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="-1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost
host="192.168.141.206"
url="jdbc:mysql://192.168.141.206:3308?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"
user="root" password="123456">
<!--<readHost host="192.168.1.104" url="192.168.1.104:3306" user="druid" password="druid" />-->
</writeHost>
</dataHost>
</mycat:schema>
- 分片規則配置:
vi config/mycat/rule.xml
,分別查看第 32 行和第 105 行
<!-- 第 32 行 -->
<tableRule name="auto-sharding-long">
<rule>
<!-- 指定分片表列名 -->
<columns>id</columns>
<!-- 指定分片函數與 function 對應 -->
<algorithm>rang-long</algorithm>
</rule>
</tableRule>
<!-- 第 105 行 -->
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<!-- 函數中的 mapFile 代表規則配置文件的路徑,config/mycat/autopartition-long.txt -->
<property name="mapFile">autopartition-long.txt</property>
</function>
- 自定義數字范圍分片規則:
vi config/mycat/autopartition-long.txt
# range start-end ,data node index
# K=1000,M=10000.
# ID 0-5000000 保存在 dataNode1
0-500M=0
# ID 5000000-10000000 保存在 dataNode2
500M-1000M=1
# ID 10000000-15000000 保存在 dataNode3
1000M-1500M=2
測試 MyCat 數據分片
分別創建數據庫
分別在 3 臺 MySQL 數據庫上創建 myshop_1
、myshop_2
、myshop_3
,按照剛才創建 MySQL 容器的序號創建
通過 MyCat 操作數據庫
使用 SQLyog 之類的客戶端工具連接 MyCat 數據庫,默認端口號為 8066
創建測試數據
- 建表語句
create table tb_admin (id int not null primary key,name varchar(100),sharding_id int not null);
- 測試數據
insert into tb_admin(id, name,sharding_id) values(1000000, 'lixiaohong', 0);
insert into tb_admin(id, name,sharding_id) values(6000000, 'lixiaolu', 1);
insert into tb_admin(id, name,sharding_id) values(7000000, 'pgone', 1);
insert into tb_admin(id, name,sharding_id) values(11000000, 'jianailiang', 2);
檢驗測試結果
按照上面的配置規則
-
id
為1000000
的數據應該寫入dataNode1
即myshop_1
.tb_admin
表中 -
id
為6000000
的數據應該寫入dataNode2
即myshop_2
.tb_admin
表中 -
id
為7000000
的數據應該寫入dataNode2
即myshop_2
.tb_admin
表中 -
id
為11000000
的數據應該寫入dataNode3
即myshop_3
.tb_admin
表中
至此這說明分片成功了
特別說明: 本人平時混跡于 B 站,不咋回復這里的評論,有問題可以到 B 站視頻評論區留言找我
視頻地址: https://space.bilibili.com/31137138/favlist?fid=326428938
課件說明: 本次提供的課件是 Spring Cloud Netflix 版微服務架構指南,如果有興趣想要學習 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相關課程資源
案例代碼: https://github.com/topsale/hello-spring-cloud-netflix