ShardingSphere-初見

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

ShardingJDBC-官網-概述

概述

官網://shardingsphere.apache.org/index_zh.html
下載地址://shardingsphere.apache.org/document/current/cn/downloads/
快速入門://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>

Maven版本管理

定義配置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的事務管理

官方地址://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/transaction/function/base-transaction-seata/

//shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/usage/transaction/

本地事務

在不開啟任何分散式事務管理器的前提下,讓每個數據節點各自管理自己的事務。 它們之間沒有協調以及通訊的能力,也並不互相知曉其他數據節點事務的成功與否。 本地事務在性能方面無任何損耗,但在強一致性以及最終一致性方面則力不從心。

倆階段提交

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就好了(優雅)

感謝閱讀!