有關於其他擴展可以看這篇文章:MySql分區、分表和分庫

概述
官網:http://shardingsphere.apache.org/index_zh.html
下載地址:https://shardingsphere.apache.org/document/current/cn/downloads/
快速入門:https://shardingsphere.apache.org/document/current/cn/quick-start/shardingsphere-jdbc-quick-start/
以下來自官網的原話:
Apache ShardingSphere 是一套開源的分布式數據庫解決方案組成的生態圈,它由 JDBC、Proxy 和 Sidecar(規划中)這 3 款既能夠獨立部署,又支持混合部署配合使用的產品組成。 它們均提供標准化的數據水平擴展、分布式事務和分布式治理等功能,可適用於如 Java 同構、異構語言、雲原生等各種多樣化的應用場景。
Apache ShardingSphere 旨在充分合理地在分布式的場景下利用關系型數據庫的計算和存儲能力,而並非實現一個全新的關系型數據庫。 關系型數據庫當今依然占有巨大市場份額,是企業核心系統的基石,未來也難於撼動,我們更加注重在原有基礎上提供增量,而非顛覆。
Apache ShardingSphere 5.x 版本開始致力於可插拔架構,項目的功能組件能夠靈活的以可插拔的方式進行擴展。 目前,數據分片、讀寫分離、數據加密、影子庫壓測等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 與協議的支持,均通過插件的方式織入項目。 開發者能夠像使用積木一樣定制屬於自己的獨特系統。Apache ShardingSphere 目前已提供數十個 SPI 作為系統的擴展點,仍在不斷增加中。
ShardingSphere 已於2020年4月16日成為 Apache 軟件基金會的頂級項目。
認識shardingjdbc

定位為輕量級 Java 框架,在 Java 的 JDBC 層提供的額外服務。 它使用客戶端直連數據庫,以 jar 包形式提供服務,無需額外部署和依賴,可理解為增強版的 JDBC 驅動,完全兼容 JDBC 和各種 ORM 框架。
適用於任何基於 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
支持任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
支持任意實現 JDBC 規范的數據庫,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 標准的數據庫。
shardingjdbc功能架構圖

認識Sharding-Proxy

定位為透明化的數據庫代理端,提供封裝了數據庫二進制協議的服務端版本,用於完成對異構語言的支持。 目前提供 MySQL 和 PostgreSQL 版本,它可以使用任何兼容 MySQL/PostgreSQL 協議的訪問客戶端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作數據,對 DBA 更加友好。
- 向應用程序完全透明,可直接當做 MySQL/PostgreSQL 使用。
- 適用於任何兼容 MySQL/PostgreSQL 協議的的客戶端。
三個組件的比較
Sharding-Jdbc | Sharding-Proxy | Sharding-Sidecar | |
---|---|---|---|
數據庫 | 任意 | MYSQL | MYSQL |
連接消耗數 | 高 | 低 | 低 |
異構語言 | 僅Java | 任意 | 任意 |
性能 | 損耗低 | 損耗高 | 損耗低 |
中心化 | 是 | 否 | 是 |
靜態入口 | 無 | 有 | 無 |
ShardingJdbc混合架構

ShardingSphere-JDBC 采用無中心化架構,適用於 Java 開發的高性能的輕量級 OLTP(連接事務處理) 應用;ShardingSphere-Proxy 提供靜態入口以及異構語言的支持,適用於 OLAP(連接數據分析) 應用以及對分片數據庫進行管理和運維的場景。
Apache ShardingSphere 是多接入端共同組成的生態圈。 通過混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,並采用同一注冊中心統一配置分片策略,能夠靈活的搭建適用於各種場景的應用系統,使得架構師更加自由地調整適合與當前業務的最佳系統架構。
ShardingShpere的功能清單
- 功能列表
- 數據分片
- 分庫 & 分表
- 讀寫分離
- 分片策略定制化
- 無中心化分布式主鍵
- 分布式事務
- 標准化事務接口
- XA 強一致事務
- 柔性事務
- 數據庫治理
- 分布式治理
- 彈性伸縮
- 可視化鏈路追蹤
- 數據加密
ShardingSphere數據分片內核剖析
ShardingSphere 的 3 個產品的數據分片主要流程是完全一致的。 核心由 SQL 解析 => 執行器優化 => SQL 路由 => SQL 改寫 => SQL 執行 => 結果歸並的流程組成。

然后一些准備工作,Linux安裝MySQL,MySql完成主從復制。
MySql完成主從復制
主從復制(也稱 AB 復制)允許將來自一個MySQL數據庫服務器(主服務器)的數據復制到一個或多個MySQL數據庫服務器(從服務器)。
復制是異步的 從站不需要永久連接以接收來自主站的更新。
優點
- 橫向擴展解決方案 :在多個從站之間分配負載以提高性能。在此環境中,所有寫入和更新都必須在主服務器上進行。但是,讀取可以在一個或多個從設備上進行。該模型可以提高寫入性能(因為主設備專用於更新),同時顯着提高了越來越多的從設備的讀取速度。
- 數據安全性 :因為數據被復制到從站,並且從站可以暫停復制過程,所以可以在從站上運行備份服務而不會破壞相應的主數據。
- 分析 :可以在主服務器上創建實時數據,而信息分析可以在從服務器上進行,而不會影響主服務器的性能。
- 遠程數據分發 :您可以使用復制為遠程站點創建數據的本地副本,而無需永久訪問主服務器。
Replication 的原理

前提是作為主服務器角色的數據庫服務器必須開啟二進制日志
主服務器上面的任何修改都會通過自己的 I/O tread(I/O 線程)保存在二進制日志 Binary log 里面。
- 從服務器上面也啟動一個 I/O thread,通過配置好的用戶名和密碼, 連接到主服務器上面請求讀取二進制日志,然后把讀取到的二進制日志寫到本地的一個Realy log(中繼日志)里面。
- 從服務器上面同時開啟一個 SQL thread 定時檢查 Realy log(這個文件也是二進制的),如果發現有更新立即把更新的內容在本機的數據庫上面執行一遍。每個從服務器都會收到主服務器二進制日志的全部內容的副本。
- 從服務器設備負責決定應該執行二進制日志中的哪些語句。除非另行指定,否則主從二進制日志中的所有事件都在從站上執行。如果需要,您可以將從服務器配置為僅處理一些特定數據庫或表的事件。
具體的配置
Master節點配置/etc/my.cnf
> vim /etc/my.cnf
[mysqld]
## 同一局域網內注意要唯一
server-id=100
## 開啟二進制日志功能,可以隨便取(關鍵)
log-bin=mysql-bin
## 復制過濾:不需要備份的數據庫,不輸出(mysql庫一般不同步)
binlog-ignore-db=mysql
## 為每個session 分配的內存,在事務過程中用來存儲二進制日志的緩存
binlog_cache_size=1M
## 主從復制的格式(mixed,statement,row,默認格式是statement)
binlog_format=mixed
Slave節點配置/etc/my.cnf
(slave節點執行)
> vim /etc/my.cnf
[mysqld]
## 設置server_id,注意要唯一
server-id=102
## 開啟二進制日志功能,以備Slave作為其它Slave的Master時使用
log-bin=mysql-slave-bin
## relay_log配置中繼日志
relay_log=edu-mysql-relay-bin
##復制過濾:不需要備份的數據庫,不輸出(mysql庫一般不同步)
binlog-ignore-db=mysql
## 如果需要同步函數或者存儲過程
log_bin_trust_function_creators=true
## 為每個session 分配的內存,在事務過程中用來存儲二進制日志的緩存
binlog_cache_size=1M
## 主從復制的格式(mixed,statement,row,默認格式是statement)
binlog_format=mixed
## 跳過主從復制中遇到的所有錯誤或指定類型的錯誤,避免slave端復制中斷。
## 如:1062錯誤是指一些主鍵重復,1032錯誤是因為主從數據庫數據不一致
slave_skip_errors=1062
在master服務器授權slave服務器可以同步權限(master節點執行)
mysql > mysql -uroot -pmaster的密碼
# 授予slave服務器可以同步master服務
mysql > grant replication slave, replication client on *.* to 'root'@'slave服務的ip' identified by 'slave服務器的密碼';
mysql > flush privileges;
# 查看MySQL現在有哪些用戶及對應的IP權限(可以不執行,只是一個查看)
mysql > select user,host from mysql.user;
實戰
步驟
- 新建一個springboot工程
- 引入相關sharding依賴、ssm依賴、數據庫驅動
- 定義配置application.yml
- 定義entity、mapper、controller
- 訪問測試查看效果
引入相關sharding依賴、ssm依賴、數據庫驅動
<properties>
<java.version>1.8</java.version>
<sharding-sphere.version>4.0.0-RC1</sharding-sphere.version>
</properties>
<!-- 依賴web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 依賴mybatis和mysql驅動 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--依賴lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--依賴sharding-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-core-common</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
<!--依賴數據源druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
定義配置application.yml
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 參數配置,顯示sql
props:
sql:
show: true
# 配置數據源
datasource:
# 給每個數據源取別名,下面的ds1,ds2,ds3任意取名字
names: ds1,ds2,ds3
# 給master-ds1每個數據源配置數據庫連接信息
ds1:
# 配置druid數據源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.115.94.78:3306/ljt-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds2-slave
ds2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://114.215.145.201:3306/ljt-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds3-slave
ds3:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://114.215.145.201:3306/ljt-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置默認數據源ds1
sharding:
# 默認數據源,主要用於寫,注意一定要配置讀寫分離 ,注意:如果不配置,那么就會把三個節點都當做從slave節點,新增,修改和刪除會出錯。
default-data-source-name: ds1
# 配置數據源的讀寫分離,但是數據庫一定要做主從復制
masterslave:
# 配置主從名稱,可以任意取名字
name: ms
# 配置主庫master,負責數據的寫入
master-data-source-name: ds1
# 配置從庫slave節點
slave-data-source-names: ds2,ds3
# 配置slave節點的負載均衡均衡策略,采用輪詢機制
load-balance-algorithm-type: round_robin
# 整合mybatis的配置XXXXX
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.ljt.shardingjdbc.entity
定義mapper、controller,entity
//entity
@Data
public class User {
// 主鍵
private Integer id;
// 昵稱
private String nickname;
// 密碼
private String password;
// 性
private Integer sex;
// 性
private String birthday;
}
//mapper
public interface UserMapper {
@Insert("insert into ksd_user(nickname,password,sex,birthday) values(#{nickname},#{password},#{sex},#{birthday})")
void addUser(User user);
@Select("select * from ksd_user")
List<User> findUsers();
}
//controller
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/save")
public String insert() {
User user = new User();
user.setNickname("zhangsan"+ new Random().nextInt());
user.setPassword("1234567");
user.setSex(1);
user.setBirthday("1988-12-03");
userMapper.addUser(user);
return "success";
}
@GetMapping("/listuser")
public List<User> listuser() {
return userMapper.findUsers();
}
}
Props的其他相關配置
acceptor.size: # accept連接的線程數量,默認為cpu核數2倍
executor.size: #工作線程數量最大,默認值: 無限制
max.connections.size.per.query: # 每個查詢可以打開的最大連接數量,默認為1
check.table.metadata.enabled: #是否在啟動時檢查分表元數據一致性,默認值: false
proxy.frontend.flush.threshold: # proxy的服務時候,對於單個大查詢,每多少個網絡包返回一次
proxy.transaction.type: # 默認LOCAL,proxy的事務模型 允許LOCAL,XA,BASE三個值,LOCAL無分布式事務,XA則是采用atomikos實現的分布式事務 BASE目前尚未實現
proxy.opentracing.enabled: # 是否啟用opentracing
proxy.backend.use.nio: # 是否采用netty的NIO機制連接后端數據庫,默認False ,使用epoll機制
proxy.backend.max.connections: # 使用NIO而非epoll的話,proxy后台連接每個netty客戶端允許的最大連接數量(注意不是數據庫連接限制) 默認為8
proxy.backend.connection.timeout.seconds: #使用nio而非epoll的話,proxy后台連接的超時時間,默認60s
MySql分區、分表和分庫的原理
有關於其他擴展可以看這篇文章:MySql分區、分表和分庫
ShardingJdbc的分庫和分表
分庫分表的方式:水平拆分,垂直拆分。
水平拆分:統一個表的數據拆到不同的庫不同的表中。可以根據時間、地區、或某個業務鍵維度,也可以通過hash進行拆分,最后通過路由訪問到具體的數據。拆分后的每個表結構保持一致。
垂直拆分:就是把一個有很多字段的表給拆分成多個表,或者是多個庫上去。每個庫表的結構都不一樣,每個庫表都包含部分字段。一般來說,可以根據業務維度進行拆分,如訂單表可以拆分為訂單、訂單支持、訂單地址、訂單商品、訂單擴展等表;也可以,根據數據冷熱程度拆分,20%的熱點字段拆到一個表,80%的冷字段拆到另外一個表。
邏輯表
邏輯表是指:水平拆分的數據庫或者數據表的相同路基和數據結構表的總稱。比如用戶數據根據用戶id%2拆分為2個表,分別是:user0和user1。他們的邏輯表名是:user。
在shardingjdbc中的定義方式如下:
spring:
shardingsphere:
sharding:
tables:
# ksd_user 邏輯表名
user:
分庫分表數據節點 - actual-data-nodes
tables:
# user 邏輯表名
user:
# 數據節點:多數據源$->{0..N}.邏輯表名$->{0..N} 相同表
actual-data-nodes: ds$->{0..2}.user$->{0..1}
# 數據節點:多數據源$->{0..N}.邏輯表名$->{0..N} 不同表
actual-data-nodes: ds0.user$->{0..1},ds1.user$->{2..4}
# 指定單數據源的配置方式
actual-data-nodes: ds0.user$->{0..4}
# 全部手動指定
actual-data-nodes: ds0.user0,ds1.user0,ds0.user1,ds1.user1,
數據分片是最小單元。由數據源名稱和數據表組成,比如:ds0.user0。
分庫分表的分片策略
數據源分片分為兩種:
- 數據源分片
- 表分片
這兩個是不同維度的分片規則,但是它們額能用的分片策略和規則是一樣的。它們由兩部分構成:
- 分片鍵
- 分片算法
第一種:none
對應NoneShardingStragey,不分片策略,SQL會被發給所有節點去執行,這個規則沒有子項目可以配置。
第二種:inline 行表達時分片策略(核心,必須要掌握)
對應InlineShardingStragey。使用Groovy的表達時,提供對SQL語句種的=和in的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開放,如:user${分片鍵(數據表字段)userid % 5} 表示user表根據某字段(userid)模 5。從而分為5張表,表名稱為:user0到user4 。如果庫也是如此。
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 參數配置,顯示sql
props:
sql:
show: true
sharding:
# 默認數據源,主要用於寫,注意一定要配置讀寫分離 ,注意:如果不配置,那么就會把三個節點都當做從slave節點,新增,修改和刪除會出錯。
default-data-source-name: ds0
# 配置分表的規則
tables:
# user 邏輯表名
user:
# 數據節點:數據源$->{0..N}.邏輯表名$->{0..N}
actual-data-nodes: ds$->{0..1}.user$->{0..1}
# 拆分庫策略,也就是什么樣子的數據放入放到哪個數據庫中。
database-strategy:
inline:
sharding-column: sex # 分片字段(分片鍵)
algorithm-expression: ds$->{sex % 2} # 分片算法表達式
# 拆分表策略,也就是什么樣子的數據放入放到哪個數據表中。
table-strategy:
inline:
sharding-column: age # 分片字段(分片鍵)
algorithm-expression: user$->{age % 2} # 分片算法表達式
algorithm-expression行表達式:
- ${begin..end} 表示區間范圍。
- ${[unit1,unit2,….,unitn]} 表示枚舉值。
- 行表達式種如果出現連續多個\({expresssion}或\)->{expression}表達式,整個表達時最終的結果將會根據每個子表達式的結果進行笛卡爾組合。
例子
- 准備兩個數據庫sharding-db。名字相同,兩個數據源ds0和ds1
- 每個數據庫下方user0和user1即可。
- 數據庫規則,性別為偶數的放入ds0庫,奇數的放入ds1庫。
- 數據表規則:年齡為偶數的放入user0庫,奇數的放入user1庫。
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 參數配置,顯示sql
props:
sql:
show: true
# 配置數據源
datasource:
# 給每個數據源取別名,下面的ds1,ds1任意取名字
names: ds0,ds1
# 給master-ds1每個數據源配置數據庫連接信息
ds0:
# 配置druid數據源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.115.94.78:3306/sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds1-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://114.215.145.201:3306/sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置默認數據源ds0
sharding:
# 默認數據源,主要用於寫,注意一定要配置讀寫分離 ,注意:如果不配置,那么就會把三個節點都當做從slave節點,新增,修改和刪除會出錯。
default-data-source-name: ds0
# 配置分表的規則
tables:
# ksd_user 邏輯表名
user:
# 數據節點:數據源$->{0..N}.邏輯表名$->{0..N}
actual-data-nodes: ds$->{0..1}.user$->{0..1}
# 拆分庫策略,也就是什么樣子的數據放入放到哪個數據庫中。
database-strategy:
inline:
sharding-column: sex # 分片字段(分片鍵)
algorithm-expression: ds$->{sex % 2} # 分片算法表達式
# 拆分表策略,也就是什么樣子的數據放入放到哪個數據表中。
table-strategy:
inline:
sharding-column: age # 分片字段(分片鍵)
algorithm-expression: user$->{age % 2} # 分片算法表達式
# 整合mybatis的配置XXXXX
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.ljt.shardingjdbc.entity
第三種:根據實時間日期 - 按照標准規則分庫分表
標准分片 - Standard(了解)
- 對應StrandardShardingStrategy.提供對SQL語句中的=,in和惡between and 的分片操作支持。
- StrandardShardingStrategy只支持但分片鍵。提供PreciseShardingAlgorithm和RangeShardingAlgorithm兩個分片算法。
- PreciseShardingAlgorithm是必選的呃,用於處理=和IN的分片
- 和RangeShardingAlgorithm是可選的,是用於處理Betwwen and分片,如果不配置和RangeShardingAlgorithm,SQL的Between AND 將按照全庫路由處理。
定義分片的日期規則配置
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 參數配置,顯示sql
props:
sql:
show: true
# 配置數據源
datasource:
# 給每個數據源取別名,下面的ds1,ds1任意取名字
names: ds0,ds1
# 給master-ds1每個數據源配置數據庫連接信息
ds0:
# 配置druid數據源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.115.94.78:3306/sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds1-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://114.215.145.201:3306/sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置默認數據源ds0
sharding:
# 默認數據源,主要用於寫,注意一定要配置讀寫分離 ,注意:如果不配置,那么就會把三個節點都當做從slave節點,新增,修改和刪除會出錯。
default-data-source-name: ds0
# 配置分表的規則
tables:
# ksd_user 邏輯表名
user:
# 數據節點:數據源$->{0..N}.邏輯表名$->{0..N}
actual-data-nodes: ds$->{0..1}.user$->{0..1}
# 拆分庫策略,也就是什么樣子的數據放入放到哪個數據庫中。
database-strategy:
standard:
shardingColumn: birthday
preciseAlgorithmClassName: com.xuexiangban.shardingjdbc.algorithm.BirthdayAlgorithm
table-strategy:
inline:
sharding-column: age # 分片字段(分片鍵)
algorithm-expression: user$->{age % 2} # 分片算法表達式
# 整合mybatis的配置XXXXX
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.ljt.shardingjdbc.entity
定義分片的日期規則
public class BirthdayAlgorithm implements PreciseShardingAlgorithm<Date> {
List<Date> dateList = new ArrayList<>();
{
Calendar calendar1 = Calendar.getInstance();
calendar1.set(2020, 1, 1, 0, 0, 0);
Calendar calendar2 = Calendar.getInstance();
calendar2.set(2021, 1, 1, 0, 0, 0);
Calendar calendar3 = Calendar.getInstance();
calendar3.set(2022, 1, 1, 0, 0, 0);
dateList.add(calendar1.getTime());
dateList.add(calendar2.getTime());
dateList.add(calendar3.getTime());
}
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {
// 獲取屬性數據庫的值
Date date = preciseShardingValue.getValue();
// 獲取數據源的名稱信息列表
Iterator<String> iterator = collection.iterator();
String target = null;
for (Date s : dateList) {
target = iterator.next();
// 如果數據晚於指定的日期直接返回
if (date.before(s)) {
break;
}
}
return target;
}
}
第四種:ShardingSphere - 符合分片策略(了解)
- 對應接口:HintShardingStrategy。通過Hint而非SQL解析的方式分片的策略。
- 對於分片字段非SQL決定,而是由其他外置條件決定的場景,克使用SQL hint靈活的注入分片字段。例如:按照用戶登錄的時間,主鍵等進行分庫,而數據庫中並無此字段。SQL hint支持通過Java API和SQL注解兩種方式使用。讓后分庫分表更加靈活。
第五種:ShardingSphere - hint分片策略(了解)
- 對應ComplexShardingStrategy。符合分片策略提供對SQL語句中的-,in和between and的分片操作支持。
- ComplexShardingStrategy支持多分片鍵,由於多分片鍵之間的關系復雜,因此並未進行過多的封裝,而是直接將分片鍵組合以及分片操作符透傳至分片算法,完全由開發者自己實現,提供最大的靈活度。
ShardingSphere - 分布式主鍵配置
ShardingSphere提供靈活的配置分布式主鍵生成策略方式。在分片規則配置模塊克配置每個表的主鍵生成策略。默認使用雪花算法。(snowflake)生成64bit的長整型數據。支持兩種方式配置
- SNOWFLAKE
- UUID
spring:
shardingsphere:
sharding:
tables:
# user 邏輯表名
user:
key-generator:
# 主鍵的列明,
column: userid
type: SNOWFLAKE
ShardingSphere - 分庫分表 - 年月案例
策略類
public class YearMonthShardingAlgorithm implements PreciseShardingAlgorithm<String> {
private static final String SPLITTER = "_";
@Override
public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) {
String tbName = shardingValue.getLogicTableName() + "_" + shardingValue.getValue();
System.out.println("Sharding input:" + shardingValue.getValue() + ", output:{}" + tbName);
return tbName;
}
}
entity
@Data
public class Order {
// 主鍵
private Long orderid;
// 訂單編號
private String ordernumber;
// 用戶ID
private Long userid;
// 產品id
private Long productid;
// 創建時間
private Date createTime;
}
Mapper
@Mapper
@Repository
public interface UserOrderMapper {
@Insert("insert into user_order(ordernumber,userid,create_time,yearmonth) values(#{ordernumber},#{userid},#{createTime},#{yearmonth})")
@Options(useGeneratedKeys = true,keyColumn = "orderid",keyProperty = "orderid")
void addUserOrder(UserOrder userOrder);
}
配置
server:
port: 8085
spring:
main:
allow-bean-definition-overriding: true
shardingsphere:
# 參數配置,顯示sql
props:
sql:
show: true
# 配置數據源
datasource:
# 給每個數據源取別名,下面的ds1,ds1任意取名字
names: ds0,ds1
# 給master-ds1每個數據源配置數據庫連接信息
ds0:
# 配置druid數據源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.115.94.78:3306/order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置ds1-slave
ds1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://114.215.145.201:3306/order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
username: root
password: root
maxPoolSize: 100
minPoolSize: 5
# 配置默認數據源ds0
sharding:
# 默認數據源,主要用於寫,注意一定要配置讀寫分離 ,注意:如果不配置,那么就會把三個節點都當做從slave節點,新增,修改和刪除會出錯。
default-data-source-name: ds0
# 配置分表的規則
tables:
# ksd_user 邏輯表名
ksd_user:
key-generator:
column: id
type: SNOWFLAKE
# 數據節點:數據源$->{0..N}.邏輯表名$->{0..N}
actual-data-nodes: ds$->{0..1}.user$->{0..1}
# 拆分庫策略,也就是什么樣子的數據放入放到哪個數據庫中。
database-strategy:
standard:
shardingColumn: birthday
preciseAlgorithmClassName: com.ljt.shardingjdbc.algorithm.BirthdayAlgorithm
table-strategy:
inline:
sharding-column: age # 分片字段(分片鍵)
algorithm-expression: ksd_user$->{age % 2} # 分片算法表達式
ksd_order:
# 數據節點:數據源$->{0..N}.邏輯表名$->{0..N}
actual-data-nodes: ds0.order$->{0..1}
key-generator:
column: orderid
type: SNOWFLAKE
# 拆分庫策略,也就是什么樣子的數據放入放到哪個數據庫中。
table-strategy:
inline:
sharding-column: orderid # 分片字段(分片鍵)
algorithm-expression: order$->{orderid % 2} # 分片算法表達式
ksd_user_order:
# 數據節點:數據源$->{0..N}.邏輯表名$->{0..N}
actual-data-nodes: ds0.user_order_$->{2021..2022}${(1..3).collect{t ->t.toString().padLeft(2,'0')} }
key-generator:
column: orderid
type: SNOWFLAKE
# 拆分庫策略,也就是什么樣子的數據放入放到哪個數據庫中。
table-strategy:
# inline:
# shardingColumn: yearmonth
# algorithmExpression: ksd_user_order_$->{yearmonth}
standard:
shardingColumn: yearmonth
preciseAlgorithmClassName: com.ljt.shardingjdbc.algorithm.YearMonthShardingAlgorithm
# 整合mybatis的配置XXXXX
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.ljt.shardingjdbc.entity
test
@SpringBootTest
class ShardingJdbcApplicationTests {
@Autowired
private UserOrderService userOrderService;
@Test
void contextLoads() throws Exception {
User user = new User();
user.setNickname("zhangsan" + new Random().nextInt());
user.setPassword("1234567");
user.setSex(1);
user.setAge(2);
user.setBirthday(new Date());
Order order = new Order();
order.setCreateTime(new Date());
order.setOrdernumber("133455678");
order.setProductid(1234L);
userOrderService.saveUserOrder(user, order);
}
@Autowired
private UserOrderMapper userOrderMapper;
@Test
public void orderyearMaster() {
UserOrder userOrder = new UserOrder();
userOrder.setOrderid(10000L);
userOrder.setCreateTime(new Date());
userOrder.setOrdernumber("133455678");
userOrder.setYearmonth("202103");
userOrder.setUserid(1L);
userOrderMapper.addUserOrder(userOrder);
}
}
ShardingJdbc的事務管理
本地事務
在不開啟任何分布式事務管理器的前提下,讓每個數據節點各自管理自己的事務。 它們之間沒有協調以及通信的能力,也並不互相知曉其他數據節點事務的成功與否。 本地事務在性能方面無任何損耗,但在強一致性以及最終一致性方面則力不從心。
倆階段提交
XA協議最早的分布式事務模型是由X/Open國際聯盟提出的X/Open Distributed Transaction Processing(DTP)模型,簡稱XA協議。
基於XA協議實現的分布式事務對業務侵入很小。 它最大的優勢就是對使用方透明,用戶可以像使用本地事務一樣使用基於XA協議的分布式事務。 XA協議能夠嚴格保障事務ACID特性。
嚴格保障事務ACID特性是一把雙刃劍。 事務執行在過程中需要將所需資源全部鎖定,它更加適用於執行時間確定的短事務。 對於長事務來說,整個事務進行期間對數據的獨占,將導致對熱點數據依賴的業務系統並發性能衰退明顯。 因此,在高並發的性能至上場景中,基於XA協議的分布式事務並不是最佳選擇。
柔性事務
如果將實現了ACID的事務要素的事務稱為剛性事務的話,那么基於BASE事務要素的事務則稱為柔性事務。 BASE是基本可用、柔性狀態和最終一致性這三個要素的縮寫。
基本可用(Basically Available)保證分布式事務參與方不一定同時在線。
柔性狀態(Soft state)則允許系統狀態更新有一定的延時,這個延時對客戶來說不一定能夠察覺。
而最終一致性(Eventually consistent)通常是通過消息傳遞的方式保證系統的最終一致性。
在ACID事務中對隔離性的要求很高,在事務執行過程中,必須將所有的資源鎖定。 柔性事務的理念則是通過業務邏輯將互斥鎖操作從資源層面上移至業務層面。通過放寬對強一致性要求,來換取系統吞吐量的提升。
基於ACID的強一致性事務和基於BASE的最終一致性事務都不是最好的,只有在最適合的場景中才能發揮它們的最大長處。 可通過下表詳細對比它們之間的區別,以幫助開發者進行技術選型。

導入分布式事務的依賴
<!--依賴sharding-->
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-transaction-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
@Service
public class UserOrderService {
@Autowired
private UserMapper userMapper;
@Autowired
private OrderMapper orderMapper;
@ShardingTransactionType(TransactionType.XA)
@Transactional(rollbackFor = Exception.class)
public int saveUserOrder(User user, Order order) {
userMapper.addUser(user);
order.setUserid(user.getId());
orderMapper.addOrder(order);
//int a = 1/0; //測試回滾,統一提交的話,將這行注釋掉就行
return 1;
}
}
@SpringBootTest
class ShardingJdbcApplicationTests {
@Autowired
private UserOrderService userOrderService;
@Test
void contextLoads() throws Exception {
User user = new User();
user.setNickname("zhangsan" + new Random().nextInt());
user.setPassword("1234567");
user.setSex(1);
user.setAge(2);
user.setBirthday(new Date());
Order order = new Order();
order.setCreateTime(new Date());
order.setOrdernumber("133455678");
order.setProductid(1234L);
userOrderService.saveUserOrder(user, order);
}
}
事務的幾種類型比較
本地事務
- 完全支持非跨庫事務,例如:僅分表,或分庫但是路由的結果在單庫中。
- 完全支持因邏輯異常導致的跨庫事務。例如:同一事務中,跨兩個庫更新。更新完畢后,拋出空指針,則兩個庫的內容都能回滾。
- 不支持因網絡、硬件異常導致的跨庫事務。例如:同一事務中,跨兩個庫更新,更新完畢后、未提交之前,第一個庫宕機,則只有第二個庫數據提交。
兩階段XA事務
- 支持數據分片后的跨庫XA事務
- 兩階段提交保證操作的原子性和數據的強一致性
- 服務宕機重啟后,提交/回滾中的事務可自動恢復
- SPI機制整合主流的XA事務管理器,默認Atomikos,可以選擇使用Narayana和Bitronix
- 同時支持XA和非XA的連接池
- 提供spring-boot和namespace的接入端
不支持:
- 服務宕機后,在其它機器上恢復提交/回滾中的數據
Seata柔性事務
- 完全支持跨庫分布式事務
- 支持RC隔離級別
- 通過undo快照進行事務回滾
- 支持服務宕機后的,自動恢復提交中的事務
依賴:
- 需要額外部署Seata-server服務進行分支事務的協調
- ShardingSphere和Seata會對SQL進行重復解析
規范
基礎規范
- 表必須有主鍵,建議使用整型作為主鍵
- 禁止使用外鍵,表之間的關聯性和完整性通過應用層來控制
- 表在設計之初,應該考慮到大致的數據級,若表記錄小於1000W,盡量使用單表,不建議分表。
- 建議將大字段,訪問頻率低,或者不需要作為篩選條件的字段拆分到拓展表中,(做好表垂直拆分)
- 控制單實例表的總數,單個表分表數控制在1024以內。
列設計規范
- 正確區分tinyint、int、bigint的范圍
- 使用varchar(20)存儲手機號,不要使用整數
- 使用int存儲ipv4 不要使用char(15)
- 涉及金額使用decimal/varchar,並制定精度
- 不要設計為null的字段,而是用空字符,因為null需要更多的空間,並且使得索引和統計變得更復雜。
索引規范
- 唯一索引使用uniq_[字段名]來命名
- 非唯一索引使用idx_[字段名]來命名
- 不建議在頻繁更新的字段上建立索引
- 非必要不要進行JOIN,如果要進行join查詢,被join的字段必須類型相同,並建立索引。
- 單張表的索引數量建議控制在5個以內,索引過多,不僅會導致插入更新性能下降,還可能導致MYSQL的索引出錯和性能下降
- 組合索引字段數量不建議超過5個,理解組合索引的最左匹配原則,避免重復建設索引。比如你建立了
(x,y,z) 相當於你建立了(x),(x,y),(x,y,z)
SQL規范
- 禁止使用selet ,只獲取必要字段,select 會增加cpu/i0/內存、帶寬的消耗。
- insert 必須指定字段,禁止使用insert into Table values().指定字段插入,在表結果變更時,能保證對應應用程序無影響。
- 隱私類型轉換會使索引失效,導致全表掃描。(比如:手機號碼搜索時未轉換成字符串)
- 禁止在where后面查詢列使用內置函數或者表達式,導致不能命中索引,導致全表掃描
- 禁止負向查詢(!=,not like ,no in等)以及%開頭的模糊查詢,造成不能命中索引,導致全表掃描
- 避免直接返回大結果集造成內存溢出,可采用分段和游標方式。
- 返回結果集時盡量使用limit分頁顯示。
- 盡量在order by/group by的列上創建索引。
- 大表掃描盡量放在鏡像庫上去做
- 禁止大表join查詢和子查詢
- 盡量避免數據庫內置函數作為查詢條件
- 應用程序盡量捕獲SQL異常
表的垂直拆分
垂直拆分:業務模塊拆分、商品庫,用戶庫,訂單庫
水平拆分:對表進行水平拆分(也就是我們說的:分表)
表進行垂直拆分:表的字段過多,字段使用的頻率不一。(可以拆分兩個表建立1:1關系)
- 將一個屬性過多的表,一行數據較大的表,將不同的屬性分割到不同的數據庫表中。以降低單庫表的大小。
特點:
- 每個表的結構不一致
- 每個表的數量都是全量
- 表和表之間一定會有一列會進行關聯,一般都是主鍵
原則:
- 將長度較短,訪問頻率較高的字段放在一個表中,主表
- 將長度較長、訪問頻率比較低的字段放一個表中
- 將經常訪問字段放一個表中。
- 所有表的並集是全量數據。
如何平滑添加字段
場景:在開發時,有時需要給表加字段,在大數據量且分表的情況下,怎么樣平滑添加。
1:直接alter table add column,數據量大時不建議,(會產生寫鎖)
alter table ksd_user add column api_pay_no varchar(32) not null comment '用戶擴展訂單號'alter table ksd_user add column api_pay_no varchar(32) not null unique comment '用戶擴展訂單號'
2:提前預留字段(不優雅:造成空間浪費,預留多少很難控制,拓展性差)
3:新增一張表,(增加字段),遷移原表數據,在重新命名新表作為原表。
4:放入extinfo(無法使用索引)
5:提前設計,使用key/value方法存儲,新增字段時 ,直接加一個key就好了(優雅)
感謝閱讀!