分庫分表
背景描述
在系統搭建剛開始的時候我們一般使用的是單機數據庫或主從架構,但是隨着業務的發展,數據量越來越大。我們會遇到下面的一系列問題:
- 用戶請求量大
單臺服務器TPS、內存、IO都是有上限的,我們需要將請求分散到多個服務器去
- 單庫數據量太大
單個數據庫處理能力有限;單庫所在的服務器磁盤空間、IO都是有限制的
- 單表數據量太大
單表數據量很大了之後,查詢、插入、更新操作都會變慢,加字段、索引都會產生高負載。
解決方案:
一般我們採用分庫分表來解決以上的問題。而我們分庫分表又可以分爲垂直拆分和水平拆分。
-
垂直拆分
1.1 垂直分庫
按照業務切割,如將電商的數據庫分爲:用戶庫、商品庫、訂單庫
1.2 垂直分表
將一個表進行切分,如將商品表分爲:商品名稱表、商品屬性表
-
水平拆分
2.1 水平分庫
將單張表的數據切分到多個服務器上去,每個服務器具有相同的庫與表結構,只是數據不同。
2.2 水平分表
將單張表的數據切分到多個表裏面去。消除單表數據量太大引發的性能低的問題。
分片規則
在我們分庫分表的時候需要按照一定規則進行劃分。我們一般劃分的方法有如下幾個:
- Range
- 時間:按照年、月、日去切分
- 地域:按照省或者市劃分
- 大小:如從0到一千萬的數據,每100萬放一個表,0-100萬、100-200萬等。
- Hash
- 用戶ID取模
- 一致性Hash
實例:
- 用戶表
劃分方法1:按用戶id進行hash來分片。
但是這樣的話如果是用戶需要通過手機號登錄,就要查詢多個庫。
劃分方法2:此時可以存儲一個用戶ID和手機號的關係表。這個關係表用手機號來分片。
- 訂單表
劃分方法1:按照用戶id爲分片鍵。這樣每個用戶的訂單都在一個庫。
但是這樣的話如果是商家端想查看買了自家公司產品的訂單,就需要查詢多個庫。
劃分方法2:我們可以採用空間換時間,將訂單再按照商家ID進行分片,再存一份數據。
ShardingSphere
Apache ShardingSphere是一款開源的分佈式數據庫中間件組成的生態圈。它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(規劃中)這3款相互獨立的產品組成。 他們均提供標準化的數據分片、分佈式事務和數據庫治理功能,可適用於如Java同構、異構語言、容器、雲原生等各種多樣化的應用場景。
- Sharding JDBC:被定位爲輕量級Java框架,在Java的JDBC層提供額外服務,以jar包形式使用
- Sharding Proxy:被定位爲數據庫代理端,提供封裝了數據庫二進制協議的服務端版本,用於完成對異構語言的支持。
- Sharding Sidecar:被定位爲Kubernates或Mesos的雲原生數據庫代理,以DaemonSet的形式代理所有對數據庫的訪問。
Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar三者區別如下:
Sharding-JDBC
Sharding-JDBC定位爲輕量級Java框架,在Java的JDBC層提供的額外服務。 它使用客戶端直連數據庫,以jar包形式提供服務,無需額外部署和依賴,可理解爲增強版的JDBC驅動,完全兼容JDBC和各種ORM框架的使用。
- 適用於任何基於Java的ORM框架,如:JPA、Hibernate、Mybatis、Spring JDBC Template等
- 適用於基於任何第三方的數據庫連接池,如:DBCP、C3P0、BoneCP、Druid、HikariCP等
- 支持任意的實現JDBC規範的數據庫,目前支持:MySQL、Oracle、SQLServer和PostgreSQL
Sharding-JDBC主要功能:
-
數據分片
- 分庫分表
- 讀寫分離
- 分片策略
- 分佈式主鍵
-
分佈式事務
- 標準的事務接口
- XA強一致性事務
- 柔性事務
-
數據庫治理
- 配置動態化
- 編排和治理
- 數據脫敏
- 可視化鏈路追蹤
核心概念
- 表概念
- 真實表
數據庫中真實存在的物理表。例如b_order0、b_order1
- 邏輯表
數據分片後,同一類表結構的名稱。例如b_order
- 數據節點
在分片之後,由數據源和數據表組成。例如ds0.b_order1
- 綁定表
指的是分片規則一致的關係表(主表、子表),例如訂單表和訂單明細表,均按照order_id進行分片,則此兩個表可以設置爲綁定表關係,綁定表之間的連接查詢不會出現笛卡爾積關聯,提升查詢效率。
如order1、order2、order_detail1、order_detail2四個表
沒配置綁定表的話,關聯查詢時需要4個sql
select * from order1 left join order_detail1 on ....
select * from order1 left join order_detail2 on ....
select * from order2 left join order_detail1 on ....
select * from order2 left join order_detail2 on ....
配置了綁定表後:
select * from order1 left join order_detail1 on ....
select * from order2 left join order_detail2 on ....
- 廣播表
在使用中,有些表沒必要做分片,例如字典表、省份信息等,因爲他們數據量不大,而且這種表可能需要跟一些大表關聯查詢。廣播表會在不同的數據節點上進行存儲,存儲的表結構和數據完全相同。
2. 分片算法
由於分片算法和業務實現緊密相關,因此並未提供內置的分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供接口讓開發者自行實現分片算法。目前提供4種分片算法:
- 精確分片算法PreciseShardingAlgorithm
用於處理使用單一鍵作爲分片鍵的=與in進行分片的場景
- 範圍分片算法RangeShardingAlgorithm
用於處理使用單一鍵作爲分片鍵的BETWEEN AND、<、>、>=、<=進行分片的場景
- 複合分片算法ComplexKeysSharingAlgorithm
用於處理使用多鍵作爲分片鍵進行分片的場景
- Hint分片算法HintShardingAlgorithm
用於處理使用Hint行分片的場景。
- 分片策略
分片策略包含分片鍵和分片算法。目前提供5種分片策略:
- 標準分片策略StandardShardingStrategy
只支持單分片鍵,提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。
PreciseShardingAlgorithm是必選的,RangeShardingAlgorithm是可選的。但是SQL中使用了範圍操作,如果不配置RangeShardingAlgorithm會採用全庫路由掃描,效率低。
- 複合分片策略ComplexShardingStrategy
支持多分片鍵。提供對SQL語句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。由於多分片鍵之間的關係複雜,因此並未進行過多的封裝,而是直接將分片鍵值組合以及分片操作符透傳至分片算法,完全由應用開發者實現,提供最大的靈活度。
- 行表達式分片策略InlineShardingStrategy
只支持單分片鍵。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發。如: t_user_$->{u_id % 8}表示t_user表根據u_id模8,而分成8張表,表名稱爲t_user_0到t_user_7。
- Hint分片策略HintShardingStrategy
通過Hint指定分片值而非從SQL中提取分片值的方式進行分片的策略。
- 不分片策略NoneShardingStrategy
不分片的策略。
SQL使用規範
支持項:
- 路由至單數據節點時,目前MySQL數據庫100%全兼容,其他數據庫完善中
- 路由至多數據節點時,全面支持DQL、DML、DDL、DCL、TCL。支持分頁、去重、排序、分組、聚合、關聯查詢。不支持CASE WHEN、HAVING、UNION。支持的最複雜的查詢示例:
SELECT select_expr [, select_expr ...]
FROM table_reference [, table_reference ...]
[WHERE predicates]
[GROUP BY {col_name | position} [ASC | DESC], ...]
[ORDER BY {col_name | position} [ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
- 支持分頁子查詢,但是隻支持嵌套一層的子查詢。
- 當分片鍵處於運算表達式或函數中的SQL時,將採用全路由的形式獲取結果。
不支持項:
INSERT INTO tbl_name (col1, col2, …) VALUES(1+2, ?, …) //VALUES語句不支持運算表達式
INSERT INTO tbl_name (col1, col2, …) SELECT col1, col2, … FROM tbl_name
WHERE col3 = ? //INSERT .. SELECT
SELECT COUNT(col1) as count_alias FROM tbl_name GROUP BY col1 HAVING
count_alias > ? //HAVING
SELECT * FROM tbl_name1 UNION SELECT * FROM tbl_name2 //UNION
SELECT * FROM tbl_name1 UNION ALL SELECT * FROM tbl_name2 //UNION ALL
SELECT * FROM ds.tbl_name1 //包含schema
SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name //同時使用普通聚合函數和DISTINCT
SELECT * FROM tbl_name WHERE to_date(create_time, ‘yyyy-mm-dd’) = ? //會導致全路由
分頁查詢:
查詢偏移量大的分頁會導致數據庫獲取數據性能低下,如下面這個SQL
SELECT * FROM b_order ORDER BY id LIMIT 1000000, 10
如果是分爲2個庫的情況,爲了保證數據正確性,SQL會被改寫爲:
SELECT * FROM b_order ORDER BY id LIMIT 0, 1000010
並且兩個庫都會執行這條SQL,然後取10條數據出來。
ShardingSphere對分頁查詢進行了2個優化:
- 流式處理+歸併排序的方式避免內存的過量佔用。
由於SQL改寫不可避免的佔用了額外的帶寬,但並不會導致內存暴漲。與直覺不同,大多數人認爲ShardingSphere會將1,000,010*2記錄全部加載至內存,進而佔用大量內存而導致內存溢出。
由於每個結果集的記錄是有序的,因此ShardingSphere每次比較僅獲取各個分片的當前結果集記錄10條,駐留在內存中的記錄僅爲當前路由到的分片的結果集的當前遊標指向,即只有20條。對於本身即有序的待排序對象,歸併排序的時間複雜度僅爲O(n),性能損耗很小。
- 對落至單節點的查詢進行進一步的優化
落至單分片查詢的請求並不需要改寫SQL也可以保證記錄的正確性,因此在此種情況下,ShardingSphere並未進行SQL改寫,從而達到節省帶寬的目的。
其他功能
- Inline行表達式
InlineShardingStrategy:採用Inline行表達式進行分片的配置。Inline是可以簡化數據節點和分片算法配置信息。主要是解決配置簡化、配置一體化。
語法格式:
行表達式的使用非常直觀,只需要在配置中使用${expression }或$->{ expression }標識行表達式即可。例如:
${begin..end} # ..表示範圍區間
${[unit1, unit2, unit_x]} # [,,]表示枚舉值
行表達式中如果出現多個${}或$->{}表達式,整個表達式結果會將每個子表達式結果進行笛卡爾(積)組合。例如,以下行表達式:
${['online', 'offline']}_table${1..3}
$->{['online', 'offline']}_table$->{1..3}
最終會解析成:
online_table1, online_table2, online_table3,
offline_table1, offline_table2, offline_table3
對於均勻分佈的分片還是好寫,那麼不均勻的呢?
db0
├── b_order0
└── b_order1
db1
├── b_order2
├── b_order3
└── b_order4
可以寫成這樣:
db0.b_order${0..1},db1.b_order${2..4}