shardingsphere之sharding-jdbc分庫分表學習筆記
引言
隨着業務數據量的變大,單庫單表已經不能滿足需求了。當單表數據量超過五百萬行,查詢性能急劇下降。分庫分表迫在眉睫,尋找一個簡單實用的解決方案相信是很多小夥伴的想法。
我在看了好多的博客之後遇到了開源數據庫中間件mycat和shardingsphere(前身是sharding-jdbc),經過一番比較之後,我選了京東開源的shardingsphere作爲我的解決方案。
寫這篇文章的目的有兩個,一來是幫助剛入門學習shardingsphere的童鞋快速上手,減少時間成本,先看下怎麼用再去看官方文檔可以達到事半功倍的效果;二來是記錄自己在學習過程中遇到的問題,方便以後在項目中的使用。
小插曲
其實一開始我選的是mycat作爲解決方案,當時看到文檔齊全,又有官方羣啥的,以爲很適合我。後面二月初研究了幾天文檔然後自己學着搭建,結果沒成功。
在官方羣問問題被索要紅包,以前是問個問題要發五十塊的紅包,現在要發二十塊。前期學習成本太大,畢竟能不能用到項目還是未知數;而且羣主整天在吹自己如何牛逼,販賣中年焦慮(知乎可查),到後面一查mycat幾乎不更新了,issues基本沒人理,本身bug還是蠻多的,感覺在走向衰退和滅完,最後選擇了shardingsphere。
重要提示
用於演示的代碼和重要的參考鏈接已經放到文章的末尾,有需要的童鞋可直接下載查看。
sharding-jdbc簡介
概念
太多的理論知識我就不贅述了,麻煩自己到官網去看。
特點
一款簡單容易上手的數據庫中間件,很好的幫助我們處理分庫分表的問題,不需要對現有的業務代碼太多的修改,減少時間成本。
使用情況
從零開始整合sharding-jdbc
整合前的思考
首先你要對業務需要用到的表有一個清晰的認識。哪些表不需要拆分,哪些表需要拆分,表跟表之間是否存在關聯。通過閱讀官網和我的理解,我覺得主要分爲這幾種表:
- 單庫單表
這種表數據量不大,小於十萬這樣,而且跟其他表沒有關聯。這樣的表不需要拆分,放在一個默認庫中即可。比如:配置表,地區編碼表。
- 廣播表
這種表數據量不大,沒有必要拆分;但是跟其他表有關聯關係。在每個庫都保存一個完整表,當讀取數據的時候隨機路由到任一庫,當寫入數據時每個庫下的表都寫入。
- 邏輯表
數據量較大需要拆分的表。比如說訂單數據根據主鍵尾數拆分爲10張表,分別是t_order_0到t_order_9,他們的邏輯表名爲t_order。
- 綁定表
按我的理解就是父子表,常見的就是訂單表和訂單詳情表,通過訂單id關聯。這種類型的表數據量大也是需要拆分的。
場景模擬
爲了加深對sharding-jdbc的理解,我在這裏模擬了一個場景,基本涵蓋了常見的情況,順便把實現步驟和使用過程的問題也提一提。
搭建項目
1. 建庫建表
按照前面表的關係圖,我們可以劃分一個默認庫(存放單庫單表和廣播表)和三個庫(存放邏輯表);再額外建一個庫存放所有的表便於代碼生成,如下所示。sql文件放在git地址的sql目錄下。
用於代碼生成的庫表
generator
--area
--config
--factory
--warehouse
--code_relate
--customer
--indent_detail
--indent
--task_upload
--task
業務需要的庫表
data_source
--area
--config
--factory
--warehouse
data_source0
--code_relate0
--code_relate1
--customer0
--customer1
--factory
--indent_detail0
--indent_detail1
--indent0
--indent1
--task_upload0
--task_upload1
--task0
--task1
--warehouse
data_source1
--code_relate0
--code_relate1
--customer0
--customer1
--factory
--indent_detail0
--indent_detail1
--indent0
--indent1
--task_upload0
--task_upload1
--task0
--task1
--warehouse
data_source2
--code_relate0
--code_relate1
--customer0
--customer1
--factory
--indent_detail0
--indent_detail1
--indent0
--indent1
--task_upload0
--task_upload1
--task0
--task1
--warehouse
2.在pom.xml加入依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc-spring-boot-starter.version}</version>
</dependency>
<!-- 使用XA事務時,需要引入此依賴, 4.1.x發佈 -->
<!-- <dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${sharding-transaction-xa-core.version}</version>
</dependency> -->
<!-- 使用編排治理 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-orchestration-spring-boot-starter</artifactId>
<version>${sharding-jdbc-orchestration-spring-boot-starter.version}</version>
</dependency>
<!-- 引入zookeeper註冊中心依賴 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-orchestration-reg-zookeeper-curator</artifactId>
<version>${sharding-orchestration-reg-zookeeper-curator.version}</version>
</dependency>
3.編寫yml配置文件
yml配置文件
spring:
shardingsphere:
props:
sql: #sql打印
show: true
#executor: #工作線程數量,默認值: CPU核數
# size: 4
orchestration: ###數據庫治理功能 配置了zk但是看不到節點
name: spring_boot_ds_sharding #治理實例名稱
overwrite: true #本地配置是否覆蓋註冊中心配置。如果可覆蓋,每次啓動都以本地配置爲準
registry:
type: zookeeper #配置中心類型。如:zookeeper
namespace: orchestration-spring-boot-sharding-test #註冊中心的命名空間
server-lists: localhost:2181 #連接註冊中心服務器的列表。包括IP地址和端口號。多個地址用逗號分隔。如: host1:2181,host2:2181
digest: admin #連接註冊中心的權限令牌。缺省爲不需要權限驗證
operation-timeout-milliseconds: 500 #操作超時的毫秒數,默認500毫秒
max-retries: 3 #連接失敗後的最大重試次數,默認3次
retry-interval-milliseconds: 500 #重試間隔毫秒數,默認500毫秒
time-to-live-seconds: 60 #臨時節點存活秒數,默認60秒
props: #配置中心其它屬性
author: huangjg
blog-url: https://www.flyxiaopang.top/
datasource: #數據源配置
names: db,db0,db1,db2 #數據庫別名
db: #數據源具體配置,這個可作爲默認庫
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/data_source?characterEncoding=utf-8
username: root
password: root
db0: #數據源具體配置
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/data_source0?characterEncoding=utf-8
username: root
password: root
db1: #數據源具體配置
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/data_source1?characterEncoding=utf-8
username: root
password: root
db2: #數據源具體配置
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/data_source2?characterEncoding=utf-8
username: root
password: root
sharding: ##分庫分表規則
default-data-source-name: db #默認數據源,放置不需要分片的表和廣播表
broadcast-tables: factory,warehouse #廣播表,每個庫都有獨立的表
binding-tables: indent,indent_detail,task_upload,code_relate ##綁定表配置
default-database-strategy: #默認的分庫規則,如果邏輯表沒單獨配置則使用這個
inline:
sharding-column: customer_id #默認按照customer_id分庫,避免跨庫查詢
algorithm-expression: db$->{customer_id % 3}
tables: #邏輯表配置
config: ###單庫單表,使用UUID作爲主鍵
actual-data-nodes: db.config
key-generator:
column: code
type: UUID
customer:
actual-data-nodes: db$->{0..2}.customer$->{0..1} #具體的數據節點
table-strategy: ##分表策略
inline:
sharding-column: customer_name #根據hash值取模確定落在哪張表
algorithm-expression: customer$->{Math.abs(customer_name.hashCode() % 2)}
key-generator: #配置主鍵生成策略,默認使用SNOWFLAKE
column: customer_id
type: SNOWFLAKE
props:
worker:
id: 20200422
indent:
actual-data-nodes: db$->{0..2}.indent$->{0..1}
table-strategy:
inline:
sharding-column: indent_id
algorithm-expression: indent$->{indent_id % 2}
key-generator:
column: indent_id
type: SNOWFLAKE
indent_detail:
actual-data-nodes: db$->{0..2}.indent_detail$->{0..1}
table-strategy:
inline:
sharding-column: indent_id
algorithm-expression: indent_detail$->{indent_id % 2}
key-generator:
column: detail_id
type: SNOWFLAKE
task:
actual-data-nodes: db$->{0..2}.task$->{0..1} #具體的數據節點
database-strategy: #分庫規則
inline:
sharding-column: task_id
algorithm-expression: db$->{task_id % 3}
table-strategy:
inline:
sharding-column: task_id
algorithm-expression: task$->{task_id % 2}
task_upload:
actual-data-nodes: db$->{0..2}.task_upload$->{0..1} #具體的數據節點
database-strategy: #分庫規則
inline:
sharding-column: task_id
algorithm-expression: db$->{task_id % 3}
table-strategy:
inline:
sharding-column: stack_code
algorithm-expression: task_upload$->{Math.abs(stack_code.hashCode() % 2)}
key-generator:
column: upload_id
type: SNOWFLAKE
code_relate:
actual-data-nodes: db$->{0..2}.code_relate$->{0..1} #具體的數據節點
database-strategy: #分庫規則
inline:
sharding-column: task_id
algorithm-expression: db$->{task_id % 3}
table-strategy:
inline:
sharding-column: stack_code
algorithm-expression: code_relate$->{Math.abs(stack_code.hashCode() % 2)}
key-generator:
column: relate_id
type: SNOWFLAKE
注意事項
(1)分片鍵分爲分庫鍵和分表鍵。
(2)主鍵生成默認使用SNOWFLAKE算法,使用UUID主鍵的話需要配置。
(3)如果分片鍵的值爲long型,分片規則爲分片字段取模即可;如果是String型,分片規則爲分片字段的哈希值取模再求絕對值,因爲哈希值取模之後也許會出現負數。
(4)邏輯表和綁定表配置建議,儘可能的讓同一類型的數據落在同一個庫中。比如用戶的信息和他產生的訂單以及訂單詳情,可以通過consumer_id作爲分庫鍵,indent_id作爲分表鍵存放,這樣如果查詢命中分片鍵的話可以提高查詢效率(少查了不必要的表)。
(5)綁定表建表的時候,子表最好增加分庫鍵字段便於新增數據時確定落到哪個庫中。比如用戶表、訂單表和訂單詳情表,consumer_id作爲分庫鍵,訂單表需要有這個字段,訂單詳情表也需要這個字段,否則訂單詳情新增數據的時候會在每個庫都新增數據,很明顯是不合理的情況。
4.基礎CRUD代碼生成
工具代碼
通過配置下方的文件連接generator庫可以快速生成基礎的CRUD代碼
src/test/java/com/project/generator/MybatisGenerator.java
注意事項
(1)框架版本的選擇,目前下方這個組合是正常的,其他的版本組合啓動時可能會爆異常
mybatis-plus-boot-starter 3.1.0
sharding-jdbc-spring-boot-starter
3.1.0 spring-boot 2.0.6.RELEASE
其他版本組合時異常信息
The bean ‘dataSource’, defined in class path resource
[io/shardingsphere/shardingjdbc/spring/boot/SpringBootConfiguration.class],
could not be registered. A bean with that name has already been
defined in class path resource
[com/alibaba/druid/spring/boot/autoconfigure/DruidDataSourceAutoConfigure.class]
and overriding is disabled.
(2)實體主鍵類型的選擇
- 如果主鍵是long型的話,可以這麼配置,個人建議選擇type = IdType.ID_WORKER這樣更直白明瞭。否則會報錯。
/**
* id
*/
@TableId(value = "id", type = IdType.ID_WORKER)
private Long id;
或者
/**
* id
*/
@TableId(value = "id", type = IdType.NONE)
private Long id;
使用type= ID.AUTO的異常信息
Caused by: java.sql.SQLException: Field ‘id’ doesn’t have a default
value at
com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965) at
com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3978) at
com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914) at
com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530) at
com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683) at
com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2495) at
com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1903)
at
com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2124)
at
com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1801)
… 89 common frames omitted
- 如果主鍵是String型的話,可以這麼配置。因爲默認的是SNOWFLAKE生成,否則會插入一個long型的主鍵值導致報錯。
/**
* 編號
*/
@TableId(value = "code", type = IdType.AUTO)
private String code;
或者
/**
* 編號
*/
@TableId(value = "code", type = IdType.UUID)
private String code;
5.項目運行
測試代碼已經寫到裏面了,通過發起請求和觀察控制檯的sql你會發現邏輯SQL和真實SQL,從而發現他的查詢規則:
1、如果表沒配置規則,那麼直接到默認庫去訪問
2、如果訪問的是廣播表,那麼讀的時候是隨機路由到一個庫,寫的時候是全部庫都寫數據。
3、邏輯表查詢,查詢字段命中了分庫鍵,那麼路由到指定庫下的所有表查詢;命中了分表鍵,到所有庫下指定表查詢。如果都沒命中,那麼將發生笛卡爾積,進行全路由所有的庫和表都查詢一遍,效率不高。所以合理的配置分片規則是很重要的。
分佈式事務
sharding-jdbc的XA分佈式事務要到4.1.x版本才發佈,不過可以在主版本測試,詳情請看
ShardingTransactionType cannot be resolved to a type
彈性伸縮
這個也是在4.1.x發佈,詳情請看
彈性伸縮(Alpha)
配置zookeeper
目前我將zookeeper跑起來的時候不懂如何跟項目對接起來,如果有成功的同學麻煩將方法告知下。
結語
官網的文檔比較詳細和社區都是很活躍的,這些可以減少我們的學習成本,快速用於項目。如果在學習的過程中遇到問題可以多看看官方文檔或者直接到github上面提issues,官方人員會很快給予答覆的。