sharding-Sphere


Sharding-Sphere

簡介

1、ShardingSphere是一套開源的分布式數據庫中間件的解決方案

2、它由三個產品組成:Sharding-JDBCSharding-Proxy和Sharding-Sidecar。

3、是關系型數據庫中間件,合理在分布式環境下使用關系型數據庫操作

Sharding-JDBC

輕量級Java框架,可理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM框架(如:JPA、Hibernate、mybatis、Spring JDBC Template或直接使用JDBC),

支持任何第三方的數據庫連接池(如:DBCP、C3P0、BoneCP、Druid、HikariCP等),支持仍以實現JDBC規范的數據庫(目前支持MySQL、Oracle、SQLServer、

PostgreSQL以及任何遵循SQL92標准的數據庫)。

ShardingSphere-Proxy

定位為透明化的數據庫代理端,提供封裝了數據庫二進制協議的服務端版本,用於完成對異構語言的支持。 目前提供 MySQL 和 PostgreSQL 版本,它可以

使用任何兼容 MySQL/PostgreSQL 協議的訪問客戶端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作數據,對 DBA 更加友好。

  • 向應用程序完全透明,可直接當做 MySQL/PostgreSQL 使用。
  • 適用於任何兼容 MySQL/PostgreSQL 協議的的客戶端。

分庫分表介紹

數據庫的數據量是不可控的,隨着時間和業務發展,造成表中數據越來越多,如果再去對數據庫表CRUD操作時,造成性能問題

解決方案

  1. 從硬件上解決,增加硬盤、內存。(治標不治本)
  2. 分庫分表

為了解決由於數據量過大而造成數據庫性能降低問題。

image-20210315143819536

分庫分表方式

分庫分表有兩種方式:垂直切分水平切分

垂直切分:垂直分表和垂直分庫

水平切分:水平分表和水平分庫

垂直切分

垂直分表

操作數據庫中某張表,把這張表中一部分字段數據存到一張新表里面,再把這張表另一部分字段數據存到另外一張表里面

image-20210315151129285
垂直分庫

把單一數據庫按照業務進行划分,專庫專表。

image-20210315151648357

水平切分

水平分表
image-20210315153624671
水平分庫
image-20210315153010023

這種方式帶來了一些問題,如何將數據插入這兩個庫?可以采用根據id取余的方式來插入數據庫,比如,這里有兩個庫,用id%數據庫個數,如果余數為0,就在A庫,余數為1,就在B庫

分庫分表的應用和問題

應用

  1. 在數據庫設計時候考慮垂直分庫和垂直分表
  2. 隨着數據庫數據量增加,不要馬上考慮做水平拆分,首先考慮緩存處理、讀寫分離,使用索引等方式,如果這些方式都不能根本解決問題了,再考慮做水平拆分

分庫分表帶來的問題

  1. 跨節點連接查詢問題(分頁、排序)
  2. 多數據源管理問題

Sharding-JDBC

簡介

定位為輕量級 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 標准的數據庫。
ShardingSphere-JDBC Architecture

Sharding-JDBC實現水平分表

搭建環境

  1. 創建shardingjdbcdemo項目(SpringBoot2.2.1)

  2. 引入依賴

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    
  3. 創建數據庫

    按照水平分表的放肆,創建數據庫和數據庫表。

    • 創建數據庫course_db

    • 在數據庫中創建兩張表course_1和course_2

    • 約定規則,課程id是偶數把數據添加到course_1,奇數添加到course_2

      
      CREATE TABLE course_1(
      		cid BIGINT(20) PRIMARY KEY,
      		cname VARCHAR(50) NOT NULL,
      		user_id BIGINT(20) NOT NULL,
      		cstatus VARCHAR(10) NOT NULL
      )
      
      CREATE TABLE course_2(
      		cid BIGINT(20) PRIMARY KEY,
      		cname VARCHAR(50) NOT NULL,
      		user_id BIGINT(20) NOT NULL,
      		cstatus VARCHAR(10) NOT NULL
      )
      
  4. 編寫代碼

    1. 實體類

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class Course {
          private Long cid;
          private String cname;
          private Long userId;
          private String cstatus;
      }
      
    2. Mapper

      @Mapper
      public interface CourseMapper extends BaseMapper<Course> {
      }
      
    3. 啟動類

      @SpringBootApplication
      @MapperScan("om.atguigu.shardingjdbcdemo.mapper")
      public class ShardingjdbcdemoApplication {
          public static void main(String[] args) {
              SpringApplication.run(ShardingjdbcdemoApplication.class, args);
          }
      
      }
      
  5. 配置Sharding-JDBC分片策略

    • application.yml配置文件中進行配置

    ​ 注: course是表名前綴

    # 配置分片策略
    spring:
      shardingsphere:
        datasource:
          #配置數據源名字
          names: ds1
        # 配置數據源具體內容
          ds1:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.128:3306/course_db
            username: root
            password: 123456
      # 指定course表分布情況,配置表在哪個數據庫里面,表名稱都是什么
        sharding:
          tables:
            course:
              actual-data-nodes: ds1.course_$->{1..2}
              #指定course表里面主鍵生成策略
              key-generator:
                column: cid
                # SNOWFLAKE 雪花算法生成的id
                type: SNOWFLAKE
              # 指定分片策略, 約定cid值偶數添加到course_1表中,奇數添加到course_2表中
              table-strategy:
                #inline 的方式不支持范圍查詢 
                inline:
                  sharding-column: cid
                  algorithm-expression: course_$->{cid % 2 + 1}
        # 打開SQL輸出日志
        props:
          sql:
            show: true
    
  6. 測試

    @RunWith(SpringRunner.class)
    @SpringBootTest
    class ShardingjdbcdemoApplicationTests {
    
        @Autowired
        private CourseMapper courseMapper;
    
        @Test
        void addCourse() {
            Course course = new Course();
            course.setCname("Java");
            course.setUserID(100L);
            course.setCstatus("NOrmal");
            int insert = courseMapper.insert(course);
    
        }
    }
    

    ​ 會出現如下錯誤:

    image-20210316165430895

    這個錯誤是由於我們有兩張表,只有一個實體類,雖然字段一樣,但是不能映射,只要添加上面的配置紅框的配置即可。

    spring: 
    	main:
        	allow-bean-definition-overriding: true
    

    再次測試:

    2021-03-16 16:57:48.550  INFO 17616 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds1 ::: INSERT INTO course_2   (cname, user_id, cstatus, cid) VALUES (?, ?, ?, ?) ::: [Java, 100, NOrmal, 578627173078794241]
    

    可以看到cid為奇數,在course_2表

    image-20210316170109565

    成功插入到2號表

Sharding-JDBC實現水平分庫

需求分析

創建兩個數據庫

image-20210319010233748

約定分片規則:

數據庫規則

  1. userid為偶數的數據添加到edu_db_1數據庫中
  2. 奇數數據添加到edu_db_2數據庫中

表規則:

  1. cid為偶數數據添加到course_1表
  2. 奇數數據添加到course_2表中

創建數據庫

image-20210319011132277

// 建表SQL
CREATE TABLE course_1(
		cid BIGINT(20) PRIMARY KEY,
		cname VARCHAR(50) NOT NULL,
		user_id BIGINT(20) NOT NUll,
		cstatus VARCHAR(10) NOT NULL
)

image-20210319011240297

配置數據庫分片規則

# 配置分片策略
spring:
  shardingsphere:
    datasource:
      #配置數據源名字
      names: ds1,ds2
      # 配置數據源具體內容
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.245.130:3306/edu_db_1
        username: root
        password: 123456
      # 第二個數據源
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.245.130:3306/edu_db_2
        username: root
        password: 123456
    # 指定數據庫分布情況,配置表在哪個數據庫里面,表名稱都是什么
    # m1 m2 course_1 course_2
    sharding:
      tables:
        course:
          actual-data-nodes: ds$->{1..2}.course_$->{1..2}
          #指定course表里面主鍵生成策略
          key-generator:
            column: cid
            # SNOWFLAKE 雪花算法生成的id
            type: SNOWFLAKE
          # 指定表分片策略, 約定cid值偶數添加到course_1表中,奇數添加到course_2表中
          table-strategy:
            inline:
              sharding-column: cid
              algorithm-expression: course_$->{cid % 2 + 1}
          #指定庫分片策略
          # 1. userid為偶數的數據添加到edu_db_1數據庫中
          # 2. 奇數數據添加到edu_db_2數據庫中
          database-strategy:
            standard:
              inline:
                sharding-column: user_id
                algorithm-expression: ds$->{user_id % 2 + 1}
      #指定庫分片策略
      # 1. userid為偶數的數據添加到edu_db_1數據庫中
      # 2. 奇數數據添加到edu_db_2數據庫中
    #      default-database-strategy:
    #        inline:
    #          sharding-column: user_id
    #          algorithm-expression: ds$->{user_id % 2 + 1}

    # 打開SQL輸出日志
    props:
      sql:
        show: true
  main:
    allow-bean-definition-overriding: true

編寫測試代碼

@Test
void addCourseDb() {
    Course course = new Course();
    course.setCname("JavaDemo");
    course.setUserId(100L);
    course.setCstatus("Normal");
    int insert = courseMapper.insert(course);
}

根據分片規則,user_id為偶數是在ds1(edu_db_1)庫,cid偶數是在course_1表,我們來看看結果:

image-20210319021417393

cid是奇數,插入了course_2表。user_id是偶數,插入了ds1庫

image-20210319021607621

image-20210319021728688

image-20210319021806002

@Test
public void findCourseDb() {
    QueryWrapper<Course> wrapper = new QueryWrapper<>();
    wrapper.eq("user_id", 100L);
    wrapper.eq("cid",579491782740410369L);
    Course course = courseMapper.selectOne(wrapper);
    System.out.println("-------------------------->" + course);
}

image-20210319022236817

Sharding-JDBC實現垂直分庫

需求分析

垂直分庫就是專庫專表,如下

image-20210319165031604

創建數據庫和表

image-20210319165419464

編寫代碼

  1. 創建user實體類和UserMapper

    @Data
    public class User {
        private Long userId;
        private String username;
        private String ustatus;
    }
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    }
    
  2. 配置垂直分庫策略

    # 配置分片策略
    spring:
      shardingsphere:
        datasource:
          #配置數據源名字
          names: ds1,ds2,ds3
          # 配置數據源具體內容
          ds1:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.130:3306/edu_db_1
            username: root
            password: 123456
          # 第二個數據源
          ds2:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.130:3306/edu_db_2
            username: root
            password: 123456
          # 第三個數據源
          ds3:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.130:3306/user_db
            username: root
            password: 123456
        # 指定數據庫分布情況,配置表在哪個數據庫里面,表名稱都是什么
        # m1 m2 course_1 course_2
        sharding:
          tables:
            course:
              actual-data-nodes: ds$->{1..2}.course_$->{1..2}
              #指定course表里面主鍵生成策略
              key-generator:
                column: cid
                # SNOWFLAKE 雪花算法生成的id
                type: SNOWFLAKE
              # 指定表分片策略, 約定cid值偶數添加到course_1表中,奇數添加到course_2表中
              table-strategy:
                inline:
                  sharding-column: cid
                  algorithm-expression: course_$->{cid % 2 + 1}
              #指定庫分片策略
              # 1. userid為偶數的數據添加到edu_db_1數據庫中
              # 2. 奇數數據添加到edu_db_2數據庫中
              database-strategy:
                inline:
                  sharding-column: user_id
                  algorithm-expression: ds$->{user_id % 2 + 1}
            # 配置user_db數據庫里面t_user 專庫專表
            t_user:
              actual-data-nodes: ds$->{3}.t_user
                #指定course表里面主鍵生成策略
              key-generator:
                column: user_id
                # SNOWFLAKE 雪花算法生成的id
                type: SNOWFLAKE
                # 指定表分片策略, 約定cid值偶數添加到course_1表中,奇數添加到course_2表中
              table-strategy:
                inline:
                  sharding-column: user_id
                  algorithm-expression: t_user
          #指定庫分片策略
          # 1. userid為偶數的數據添加到edu_db_1數據庫中
          # 2. 奇數數據添加到edu_db_2數據庫中
        #      default-database-strategy:
        #        inline:
        #          sharding-column: user_id
        #          algorithm-expression: ds$->{user_id % 2 + 1}
    
        # 打開SQL輸出日志
        props:
          sql:
            show: true
      main:
        allow-bean-definition-overriding: true
    
  3. 編寫測試代碼

    @Test
    public void addUserDb() {
        User user = new User();
        user.setUsername("lin");
        user.setUstatus("a");
        userMapper.insert(user);
    }
    
    @Test
    public void findUserDb() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", 580532807156105217L);
        User user = userMapper.selectOne(wrapper);
        System.out.println("-------------------------->" + user);
    }
    

    image-20210322011401653

​ 查詢:

image-20210322011546372

Sharding-JDBC操作公共表

什么是公共表?

  1. 存儲固定數據的表,表數據很少發生變化,查詢時經常要進行關聯。
  2. 在每個數據庫中都創建出相同結構公共表。
  3. 操作公共表時,同時操作添加了公共表的數據庫中的公共表,添加記錄時,同時添加,刪除時,同時刪除。

實現

  1. 在多個數據庫中創建相同結構公共表。前面我們有三個庫,現在我們在這三個庫中創建表。

    建表SQL

    CREATE TABLE t_udict(
    	dictid BIGINT(20) PRIMARY KEY,
    	ustatus VARCHAR(100) NOT NULL,
    	uvalue VARCHAR(100) NOT NULL
    )
    
  2. 在application.yml中配置

    # 配置分片策略
    spring:
      shardingsphere:
        sharding:
          broadcast-tables: t_udict
          tables:
    		t_udict:
              key-generator:
                column: dictid
                type: SNOWFLAKE 
    
  3. 測試代碼

    • 創建實體類和mapper

      @Data
      @TableName(value = "u_udict")
      public class Udict {
          private Long dictid;
          private String ustatus;
          private String uvalue;
      }
      
      @Mapper
      public interface UdictMapper extends BaseMapper<Udict> {
      }
      
    • 測試代碼

      @Test
      public void addUserDb() {
          User user = new User();
          user.setUsername("lin");
          user.setUstatus("a");
          userMapper.insert(user);
      }
      

      image-20210322180140934

      @Test
      void deleteDict() {
          QueryWrapper<Udict> wrapper = new QueryWrapper<>();
          wrapper.eq("dictid", 580819000607375361L);
          udictMapper.delete(wrapper);
      }
      

      image-20210322180911862

Sharding-JDBC實現讀寫分離

什么是讀寫分離

為了確保數據庫產品的穩定性,很多數據庫擁有雙擊熱備功能。也就是,第一台數據庫服務器,是對外提供增刪改業務的生產服務器;第二胎數據庫服務器,主要進行讀的操作。

原理:讓主數據庫(master)處理事務性增、改、刪操作,而從數據庫(slave)處理select查詢操作

image-20210322181527756

讀寫分離原理

主從復制:當主服務器有寫入(insert/update/delete)語句時候,從服務器自動獲取。

讀寫分離:insert/update/delete語句操作一台服務器,select操作另一個服務器

image-20210322181947647

Sharding-JDBC讀寫分離則是根據SQL語義的分析,將讀操作和寫操作分別路由至主庫與從庫,它提供透明化讀寫分離,讓使用方法盡量像使用一個數據庫一樣使用主從數據庫集群。

主從復制配置

mysql主從

docker搭建主從

Sharding-JDBC操作主從

  1. 在配置文件中配置主從分離

    # 配置分片策略
    spring:
      shardingsphere:
        datasource:
          #配置數據源名字
          names: ds1,ds2,ds3,s0
          # 配置數據源具體內容
          ds1:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.131:3306/edu_db_1
            username: root
            password: 123456
          # 第二個數據源
          ds2:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.131:3306/edu_db_2
            username: root
            password: 123456
          # 第三個數據源
          ds3:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.131:3306/user_db
            username: root
            password: 123456
          # 從服務器
          s0:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.245.131:3307/user_db
            username: root
            password: 123456
        # 指定數據庫分布情況,配置表在哪個數據庫里面,表名稱都是什么
        # m1 m2 course_1 course_2
        sharding:
          tables:
            course:
              actual-data-nodes: ds$->{1..2}.course_$->{1..2}
              #指定course表里面主鍵生成策略
              key-generator:
                column: cid
                # SNOWFLAKE 雪花算法生成的id
                type: SNOWFLAKE
              # 指定表分片策略, 約定cid值偶數添加到course_1表中,奇數添加到course_2表中
              table-strategy:
                inline:
                  sharding-column: cid
                  algorithm-expression: course_$->{cid % 2 + 1}
              #指定庫分片策略
              # 1. userid為偶數的數據添加到edu_db_1數據庫中
              # 2. 奇數數據添加到edu_db_2數據庫中
              database-strategy:
                inline:
                  sharding-column: user_id
                  algorithm-expression: ds$->{user_id % 2 + 1}
            # 配置user_db數據庫里面t_user 專庫專表
            t_user:
              #actual-data-nodes: ds$->{3}.t_user
              actual-data-nodes: ds0.t_user
                #指定course表里面主鍵生成策略
              key-generator:
                column: user_id
                # SNOWFLAKE 雪花算法生成的id
                type: SNOWFLAKE
                # 指定表分片策略, 約定cid值偶數添加到course_1表中,奇數添加到course_2表中
              table-strategy:
                inline:
                  sharding-column: user_id
                  algorithm-expression: t_user
            t_udict:
              key-generator:
                column: dictid
                type: SNOWFLAKE
    
          #配置公共表
          broadcast-tables: t_udict
          # 從服務器相關配置
          master-slave-rules:
            ds3:
              master-data-source-name: ds3
              slave-data-source-names: s0
          #指定庫分片策略
          # 1. userid為偶數的數據添加到edu_db_1數據庫中
          # 2. 奇數數據添加到edu_db_2數據庫中
    
        #      default-database-strategy:
        #        inline:
        #          sharding-column: user_id
        #          algorithm-expression: ds$->{user_id % 2 + 1}
    
        # 打開SQL輸出日志
        props:
          sql:
            show: true
      main:
        allow-bean-definition-overriding: true
    
  2. 編寫測試代碼

    @Test
    public void addUserDb() {
        User user = new User();
        user.setUsername("lucymary");
        user.setUstatus("a");
        userMapper.insert(user);
    }
    

    image-20210323151612525

    @Test
    public void findUserDb() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", 465508031619137537L);
        User user = userMapper.selectOne(wrapper);
        System.out.println("-------------------------->" + user);
    }
    

    image-20210323151738849

Sharding-JDBC實現范圍查詢

配置yaml

spring:
  shardingsphere:
    sharding:
      tables:
        course:
          table-strategy:
            standard:
              sharding-column: cid
              range-algorithm-class-name: com.atguigu.shardingjdbcdemo.algorithm.RangeTableShardingAlgorithm
              precise-algorithm-class-name: com.atguigu.shardingjdbcdemo.algorithm.PreciseTableShardingAlgorithm

算法實現

public class PreciseDSShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
        String logicTableName = shardingValue.getLogicTableName();
        String cid = shardingValue.getColumnName();
        Long cidValue = shardingValue.getValue();
        //實現ds$->{cid%2+1}
        BigInteger bigIntegerB = BigInteger.valueOf(cidValue);
        BigInteger resB = (bigIntegerB.mod(new BigInteger("2"))).add(new BigInteger("1"));
        String key = "ds" + resB;
        if (availableTargetNames.contains(key)) {
            return key;
        }
        throw new UnsupportedOperationException("route" + key + " is not supported,please check your config");
    }
}
public class RangeDSShardingAlgorithm implements RangeShardingAlgorithm<Long> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {

        //select * from course where cid between 200 and 300
        //300
        Long upperValue = rangeShardingValue.getValueRange().upperEndpoint();
        //200
        Long lowerValue = rangeShardingValue.getValueRange().lowerEndpoint();
        String logicTableName = rangeShardingValue.getLogicTableName();

        //ds$->{cid%2+1} ds1,ds2
        return Arrays.asList("ds1", logicTableName + "ds2");
    }
}

上面這種方式,只能實現in操作,不能實現between操作,因為上面只實現了分表的邏輯,沒有實現分庫的邏輯,分庫還是使用的inline的方式,所以,我們要實現以下分庫。

分庫邏輯

  1. yaml

    spring:
      shardingsphere:
        sharding:
          tables:
            course:
              table-strategy:
                standard:
                  sharding-column: cid
                  range-algorithm-class-name: com.atguigu.shardingjdbcdemo.algorithm.RangeTableShardingAlgorithm
                  precise-algorithm-class-name: com.atguigu.shardingjdbcdemo.algorithm.PreciseTableShardingAlgorithm
              database-strategy:
                standard:
                  sharding-column: cid
                    range-algorithm-class-name: com.atguigu.shardingjdbcdemo.algorithm.RangeDSShardingAlgorithm
                    precise-algorithm-class-name: com.atguigu.shardingjdbcdemo.algorithm.PreciseDSShardingAlgorithm
    
  2. 算法實現

    public class PreciseDSShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
        @Override
        public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
            String logicTableName = shardingValue.getLogicTableName();
            String cid = shardingValue.getColumnName();
            Long cidValue = shardingValue.getValue();
            //實現ds$->{cid%2+1}
            BigInteger bigIntegerB = BigInteger.valueOf(cidValue);
            BigInteger resB = (bigIntegerB.mod(new BigInteger("2"))).add(new BigInteger("1"));
            String key = logicTableName + resB;
            if (availableTargetNames.contains(key)) {
                return key;
            }
            throw new UnsupportedOperationException("route" + key + " is not supported,please check your config");
        }
    }
    
    public class RangeDSShardingAlgorithm implements RangeShardingAlgorithm<Long> {
        @Override
        public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
    
            //select * from course where cid between 200 and 300
            //300
            Long upperValue = rangeShardingValue.getValueRange().upperEndpoint();
            //200
            Long lowerValue = rangeShardingValue.getValueRange().lowerEndpoint();
            String logicTableName = rangeShardingValue.getLogicTableName();
    
            //ds$->{cid%2+1} ds1,ds2
            return Arrays.asList("ds1",  "ds2");
        }
    }
    

Sharding-Proxy

簡介

定位為透明化的數據庫代理端,提供封裝了數據庫二進制協議的服務端版本,用於完成對異構語言的支持。 目前先提供MySQL/PostgreSQL版本,它可以使用任何兼容MySQL/PostgreSQL協議的訪問客戶端(如:MySQL Command Client, MySQL Workbench, Navicat等)操作數據,對DBA更加友好。

  • 向應用程序完全透明,可直接當做MySQL/PostgreSQL使用。
  • 適用於任何兼容MySQL/PostgreSQL協議的的客戶端。
Sharding-Proxy Architecture

Sharding-Proxy是一個獨立的應用,使用時需要安裝服務,進行分庫分表或者讀寫分離配置,然后啟動就行。

安裝下載

  1. 下載

下載地址

image-20210323153157313

點擊binary后,會跳轉到apache官網,然后點擊下面的地址就可以下載了

真正的下載地址

  1. 安裝

    下載好之后,解壓,到bin目錄中啟動start.bat/start.sh文件即可

    image-20210323153619249

image-20210323153639710

Sharding-Proxy分表

  1. 進入到conf文件夾中,修改server.yaml文件

    #
    # Licensed to the Apache Software Foundation (ASF) under one or more
    # contributor license agreements.  See the NOTICE file distributed with
    # this work for additional information regarding copyright ownership.
    # The ASF licenses this file to You under the Apache License, Version 2.0
    # (the "License"); you may not use this file except in compliance with
    # the License.  You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #
    
    ######################################################################################################
    # 
    # If you want to configure orchestration, authorization and proxy properties, please refer to this file.
    # 
    ######################################################################################################
    #
    #orchestration:
    #  name: orchestration_ds
    #  overwrite: true
    #  registry:
    #    type: zookeeper
    #    serverLists: localhost:2181
    #    namespace: orchestration
    #
    authentication:
     users:
       # 賬戶
       root:
          # 密碼
         password: 123456
       sharding:
         password: sharding 
         # 庫名
         authorizedSchemas: sharding_db
    
    props:
     max.connections.size.per.query: 1
     acceptor.size: 16  # The default value is available processors count * 2.
     executor.size: 16  # Infinite by default.
     proxy.frontend.flush.threshold: 128  # The default value is 128.
       # LOCAL: Proxy will run with LOCAL transaction.
       # XA: Proxy will run with XA transaction.
       # BASE: Proxy will run with B.A.S.E transaction.
     proxy.transaction.type: LOCAL
     proxy.opentracing.enabled: false
     query.with.cipher.column: true
     sql.show: false
    
  2. 修改config-sharding.yaml,這個文件主要是配置分庫分表操作的。

    image-20210323155316962

    如果要連接mysql,需要把驅動復制到lib文件夾中

    image-20210323155550117

    配置分庫分表規則:

    
    schemaName: sharding_db
    dataSources:
     ds_0:
       url: jdbc:mysql://192.168.245.131:3306/edu_1?serverTimezone=UTC&useSSL=false
       username: root
       password: 123456
       connectionTimeoutMilliseconds: 30000
       idleTimeoutMilliseconds: 60000
       maxLifetimeMilliseconds: 1800000
       maxPoolSize: 50
    shardingRule:
     tables:
       t_order:
         actualDataNodes: ds_${0}.t_order_${0..1}
         tableStrategy:
           inline:
             shardingColumn: order_id
             algorithmExpression: t_order_${order_id % 2}
         keyGenerator:
           type: SNOWFLAKE
           column: order_id
     bindingTables:
       - t_order
     defaultDatabaseStrategy:
       inline:
         shardingColumn: user_id
         algorithmExpression: ds_${0}
     defaultTableStrategy:
       none:
    
  3. 啟動Sharding-Proxy服務

    雙擊start.bat啟動,出現下面的字樣表示成功啟動

    image-20210323160608051

    Sharding-Proxy默認端口號是3307,可以以命令行的方式啟動:在cmd命令行窗口中輸入start.bat 3308就可以以3308端口啟動

  4. 通過Sharding-Proxy啟動端口進行連接。

    打開cmd窗口,連接Sharding-Proxy,連接方式和連接mysql一樣

    mysql -u root -p 123456 -h127.0.0.1 -P3307
    # 這種方式連接的是Sharding-Proxy,不是連接的mysql
    

    image-20210323162913661

    • 建表SQL

      USE sharding_db;
      
      CREATE TABLE
      IF NOT EXISTS ds_0.t_order (
      	`order_id` BIGINT PRIMARY KEY,
      	`user_id` INT NOT NULL,
      	`status` VARCHAR ( 50 )
      );
      
      INSERT INTO t_order ( `order_id`, `user_id`, `status` ) VALUES ( 11, 1, 'jack' );
      

      image-20210323163216013

    • 做完以上操作后,可以在edu_1中看到,有兩個表

      image-20210323163327903

      可以看到在t_order_1中有一下數據

      image-20210323163358646

      再插入一條數據

      image-20210323163433383

      在t_order_0中可以看到

      image-20210323163528357

​ 這樣我們的分表操作就配置完成了。

Sharding-Proxy分庫

  1. 創建兩個數據庫

image-20210323172034733

  1. 在配置文件中完成相應配置

    schemaName: sharding_db
    
    dataSources:
     ds_0:
       url: jdbc:mysql://192.168.245.131:3306/edu_db_1?serverTimezone=UTC&useSSL=false
       username: root
       password: 123456
       connectionTimeoutMilliseconds: 30000
       idleTimeoutMilliseconds: 60000
       maxLifetimeMilliseconds: 1800000
       maxPoolSize: 50
     ds_1:
       url: jdbc:mysql://192.168.245.131:3306/edu_db_2?serverTimezone=UTC&useSSL=false
       username: root
       password: 123456
       connectionTimeoutMilliseconds: 30000
       idleTimeoutMilliseconds: 60000
       maxLifetimeMilliseconds: 1800000
       maxPoolSize: 50
    shardingRule:
     # 分表策略
     tables:
       t_order:
         actualDataNodes: ds_${0..1}.t_order_${1..2}
         tableStrategy:
           inline:
             shardingColumn: order_id
             algorithmExpression: t_order_${order_id % 2 + 1}
         keyGenerator:
           type: SNOWFLAKE
           column: order_id
     bindingTables:
       - t_order
     # 分庫策略
     defaultDatabaseStrategy:
       inline:
         shardingColumn: user_id
         algorithmExpression: ds_${user_id % 2}
     defaultTableStrategy:
       none:
    
  2. 啟動Sharding-Proxy服務

    image-20210323172848771

    image-20210323173100964

  3. 創建表

    create table if not exists ds_0.t_order(order_id bigint not null,user_id int not null,status varchar(50),primary key(order_id));
    
    insert into t_order(order_id,user_id,status) values(1,1,"init");
    

    image-20210323173410528

    image-20210323173937917

    可以看到,在edu_db_2中的t_order_2中有數據

Sharding-Proxy讀寫分離

  1. 創建三個數據庫

    image-20210323175929752

  2. 修改config-master_slave.yaml

    schemaName: master_slave_db
    
    dataSources:
     master_ds:
       url: jdbc:mysql://192.168.245.131:3306/demo_ds_master?serverTimezone=UTC&useSSL=false
       username: root
       password: 123456
       connectionTimeoutMilliseconds: 30000
       idleTimeoutMilliseconds: 60000
       maxLifetimeMilliseconds: 1800000
       maxPoolSize: 50
     slave_ds_0:
       url: jdbc:mysql://192.168.245.131:3306/demo_ds_slave_0?serverTimezone=UTC&useSSL=false
       username: root
       password: 123456
       connectionTimeoutMilliseconds: 30000
       idleTimeoutMilliseconds: 60000
       maxLifetimeMilliseconds: 1800000
       maxPoolSize: 50
     slave_ds_1:
       url: jdbc:mysql://192.168.245.131:3306/demo_ds_slave_1?serverTimezone=UTC&useSSL=false
       username: root
       password: 123456
       connectionTimeoutMilliseconds: 30000
       idleTimeoutMilliseconds: 60000
       maxLifetimeMilliseconds: 1800000
       maxPoolSize: 50
    
    masterSlaveRule:
     name: ms_ds
     masterDataSourceName: master_ds
     slaveDataSourceNames:
       - slave_ds_0
       - slave_ds_1
    
  3. 啟動sharding-Proxy服務

Sharding-JDBC原理

內核剖析

數據分片就是把一個邏輯SQL轉成多個實際SQL去執行。

分片架構圖

SQL 解析

分為詞法解析和語法解析。 先通過詞法解析器將 SQL 拆分為一個個不可再分的單詞。再使用語法解析器對 SQL 進行理解,並最終提煉出解析上下文。 解析上下文包括表、選擇項、排序項、分組項、聚合函數、分頁信息、查詢條件以及可能需要修改的占位符的標記。

執行器優化

合並和優化分片條件,如 OR 等。

SQL 路由

根據解析上下文匹配用戶配置的分片策略,並生成路由路徑。目前支持分片路由和廣播路由。

SQL 改寫

將 SQL 改寫為在真實數據庫中可以正確執行的語句。SQL 改寫分為正確性改寫和優化改寫。

SQL 執行

通過多線程執行器異步執行。

結果歸並

將多個執行結果集歸並以便於通過統一的 JDBC 接口輸出。結果歸並包括流式歸並、內存歸並和使用裝飾者模式的追加歸並這幾種方式。

解析引擎

解析過程分為詞法解析和語法解析。詞法解析用於將SQL拆解為不可再分的原子符號,稱為Token。

並根據不同數據庫方言鎖提供的字典,將其歸類為關鍵字、表達式、字面量和操作符。再使用語法

解析器將SQL轉換為抽象語法樹(簡稱AST,Abstract syntax Tree)。

例如對下面一條SQL語句:

select id,name from t_user where status='active' and age>18;

會被解析成下面這樣一顆樹:

image-20210324165449367

為了便於理解,抽閑語法樹中的關鍵字的Token用綠色表示,變量的Token用紅色表示,灰色表示需要進一步拆解。通過對抽象語法樹的遍歷,可以標記出所有可能需要改寫的位置。SQL的一次解析過程是不可逆的,所有token按SQL原本的順序依次進行解析,性能很高。並且在解析過程中,需要考慮各種數據庫SQL方言的異同,提供不同的解析模板。

其中,SQL解析是整個分庫分表產品的核心,其性能和兼容性是最重要的衡量指標、ShardingSphere在1.4.x之前采用的是性能較快的Druid作為SQL解析器。1.5.x版本后,采用自研的SQL解析器,針對分庫分表場景,采取對SQL辦理解的方式,提高SQL解析的性能和兼容性。然后從3.0.x版本后,開始使用ANTLR作為SQL解析引擎。這是個開源的SQL解析引擎,ShardingSphere在使用ANTLR時,還增加了一些AST的緩存功能。整堆ANLTR4的特性,官網建議盡量采用PreparedStatement的預編譯方式來提高SQL執行的性能。

SQL解析整體結構:

image-20210324170325797

路由引擎

根據解析上下文匹配數據庫和表分片策略,生成路由 路徑。

ShardingSphere的分片策略主要分為單片路由(分片鍵的操作符是=)、多片路由(分片鍵的操作符是IN)和范圍路由(分片鍵的操作符是Between)。不攜帶分片鍵的SQl則是廣播路由。

分片策略通常可以由數據庫內置也可有用戶配置。內置的分片策略大致可以分為尾數取模、哈希、范圍、標簽、時間等。由用戶配置的分片策略則更加靈活,可以根據使用需求定制分片策略。

image-20210324170812428

改寫引擎

用戶只需要面向邏輯庫和邏輯表來寫SQL,最終由ShardingSphere的改寫引擎將SQL改寫為在真實數據庫中可以正確執行的語句。SQL改寫分為正確性改寫和優化改寫。

image-20210324171625336

執行引擎

ShardingSphere並不是簡單的將改寫完的SQl提交到數據庫執行。執行引擎的目標是自動化的平衡資源控制和執行效率。

例如他的連接模式分為內存限制模式(MEMORY_STRICTLY)和連接限制模式(CONNECTION_STRICTLY)。內存限制模式只關注一個數據庫連接的處理數量,通常一張真實表一個數據庫連接。而連接限制模式則關注數據庫連接的數量,較大的查詢會進行串行操作。

執行引擎流程圖

歸並引擎

將從各個數據節點獲取的多數據結果集,組合成為一個結果集並正確的返回至請求客戶端,稱為結果歸並。

其中,流式歸並是指一條一條數據的方式進行歸並,而內存歸並是將所有結果集都查詢到內存中,進行統一歸並。

歸並引擎結構


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM