Sharding Sphere学习

643 阅读15分钟

sharding-sphere

以下来自官网的原话: Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

Apache ShardingSphere 旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。

Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。b

ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。

img

定位为轻量级 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 标准的数据库。

sharding架构

img

sharding功能

  • 功能列表
    • 数据分片
    • 分库 & 分表
    • 读写分离
    • 分片策略定制化
    • 无中心化分布式主键
  • 分布式事务
    • 标准化事务接口
    • XA 强一致事务
    • 柔性事务
    • 数据库治理
  • 分布式治理
    • 弹性伸缩
    • 可视化链路追踪
    • 数据加密

ShardingSphere数据分片内核剖析

img

sql解析

分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记

查询优化

合并和优化分片条件,如 OR 等

SQL 路由

根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。

SQL改写

将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。

SQL执行

通过多线程执行器异步执行。

结果归并

将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式

2、复习mysql主从复制搭建

mysql主从服务器的版本必须是相同的

master

配置挂载my.cnf目录

mkdir /usr/local/mysqlData/master/cnf -p
mkdir /usr/local/mysqlData/master/data -p

配置my.cnf配置文件

这两条是真正核心的配置,不可或缺

log-bin = mysql-bin server-id = 1

[mysqld]
pid-file    = /var/run/mysqld/mysqld.pid
socket    = /var/run/mysqld/mysqld.sock
datadir    = /var/lib/mysql
symbolic-links=0
character-set-server = utf8   
#skip-networking  
innodb_print_all_deadlocks = 1
max_connections = 2000  
max_connect_errors = 6000  
open_files_limit = 65535  
table_open_cache = 128   
max_allowed_packet = 4M  
binlog_cache_size = 1M  
max_heap_table_size = 8M  
tmp_table_size = 16M  
read_buffer_size = 2M  
read_rnd_buffer_size = 8M  
sort_buffer_size = 8M  
join_buffer_size = 28M  
key_buffer_size = 4M  
thread_cache_size = 8  
query_cache_type = 1  
query_cache_size = 8M  
query_cache_limit = 2M  
ft_min_word_len = 4  
log-bin = mysql-bin
server-id = 1
binlog_format = mixed  
performance_schema = 0  
explicit_defaults_for_timestamp  
#lower_case_table_names = 1  
interactive_timeout = 28800  
wait_timeout = 28800  
#Recommended in standard MySQL setup  
sql_mode=NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER,STRICT_TRANS_TABLES   
[mysqldump]  
quick  
max_allowed_packet = 16M  
[myisamchk]
key_buffer_size = 8M
sort_buffer_size = 8M
read_buffer = 4M
write_buffer = 4M

docker创建mysql5.7容器

#创建容器绑定宿主机端口3306
docker run -itd -p 3306:3306 --name master -v /usr/local/mysqlData/master/cnf:/etc/mysql/conf.d -v /usr/local/mysqlData/master/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=masterpwd 450379344707

#创建slave用户    
GRANT REPLICATION SLAVE ON *.* to 'reader'@'%' identified by 'readerpwd';

#刷新权限设置
FLUSH PRIVILEGES;

slave

创建挂载目录

[root@localhost ~]# mkdir /usr/local/mysqlData/slave/cnf -p
[root@localhost ~]# mkdir /usr/local/mysqlData/slave/cnf -p
[root@localhost ~]# vim /usr/local/mysqlData/slave/cnf/mysql.cnf

配置slave.cnf

[mysqld]
pid-file    = /var/run/mysqld/mysqld.pid
socket    = /var/run/mysqld/mysqld.sock
datadir    = /var/lib/mysql

symbolic-links=0

character-set-server = utf8   
#skip-networking  
innodb_print_all_deadlocks = 1
max_connections = 2000  
max_connect_errors = 6000  
open_files_limit = 65535  
table_open_cache = 128   
max_allowed_packet = 4M  
binlog_cache_size = 1M  
max_heap_table_size = 8M  
tmp_table_size = 16M  
  
read_buffer_size = 2M  
read_rnd_buffer_size = 8M  
sort_buffer_size = 8M  
join_buffer_size = 28M  
key_buffer_size = 4M  
  
thread_cache_size = 8  
  
query_cache_type = 1  
query_cache_size = 8M  
query_cache_limit = 2M  
  
ft_min_word_len = 4  
  
log-bin = mysql-bin
server-id = 2
binlog_format = mixed  
 
performance_schema = 0  
explicit_defaults_for_timestamp  
  
#lower_case_table_names = 1  
  
interactive_timeout = 28800  
wait_timeout = 28800  

#Recommended in standard MySQL setup  
  
sql_mode=NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER,STRICT_TRANS_TABLES   
  
[mysqldump]  
quick  
max_allowed_packet = 16M  
  
[myisamchk]
key_buffer_size = 8M
sort_buffer_size = 8M
read_buffer = 4M
write_buffer = 4M

创建slavemysql服务器

docker run -itd -p 3307:3306 --name slaver -v /usr/local/mysqlData/slave/cnf:/etc/mysql/conf.d -v /usr/local/mysqlData/slave/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=slavepwd 450379344707

开启主从复制

在从服务器上配置连接主服务器的信息

/*主服务上执行*/
    mysql> show master status;
    +------------------+----------+--------------+------------------+-------------------+
    | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
    +------------------+----------+--------------+------------------+-------------------+
    | mysql-bin.000003 |      765 |              |                  |                   |
    +------------------+----------+--------------+------------------+-------------------+
    1 row in set (0.01 sec)

slave

#在master容器中通过cat /etc/hosts查看ip地址       
change master to master_host='172.17.0.2',master_user='reader',master_password='readerpwd',master_log_file='mysql-bin.000003',master_log_pos=765;

启动slave

mysql> start slave;
Query OK, 0 rows affected (0.03 sec)
mysql> show slave status\G
           Slave_IO_State: Waiting for master to send event
              Master_Host: 172.17.0.2
              Master_User: reader
              Master_Port: 3306
            Connect_Retry: 60
          Master_Log_File: mysql-bin.000003
      Read_Master_Log_Pos: 765
           Relay_Log_File: b3a8ba2fdc0c-relay-bin.000002
            Relay_Log_Pos: 494
    Relay_Master_Log_File: mysql-bin.000003
         Slave_IO_Running: Yes
     Slave_SQL_Running: Yes

测试主从复制

#master
49.232.216.235:3306 root masterpwd
#slave
49.232.216.235:3307 root slavepwd

3、搭建一个SpringBoot项目

添加sharding-JDBC依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sharding-learn</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <sharding-sphere.version>4.0.0-RC1</sharding-sphere.version>
    </properties>
    <dependencies>
        <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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

yml配置

server:
  port: 8085

spring:
  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://49.232.216.235:3306/ksd_order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: masterpwd
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds2-slave
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://49.232.216.235:3307/ksd_order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: slavepwd
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds2-slave
      ds3:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://49.232.216.235:3307/ksd_order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: slavepwd
        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节点的负载均衡均衡策略,默认机制是:round_robin 如果你修改成随机RANDOM
      load-balance-algorithm-type: round_robin
  main:
    allow-bean-definition-overriding: true

mybatis:
  mapper-locations: classpath*:generator/**/*Dao.xml

读写分离+主从复制

Rule Type: master-slave

2021-04-14 18:28:52.624  INFO 16092 --- [nio-8085-exec-1] ShardingSphere-SQL                       : Rule Type: master-slave
2021-04-14 18:28:52.624  INFO 16092 --- [nio-8085-exec-1] ShardingSphere-SQL                       : SQL: select * from ksd_user ::: DataSources: ds2
2021-04-14 18:28:54.264  INFO 16092 --- [nio-8085-exec-3] ShardingSphere-SQL                       : Rule Type: master-slave
2021-04-14 18:28:54.264  INFO 16092 --- [nio-8085-exec-3] ShardingSphere-SQL                       : SQL: insert into ksd_user (nickname, `password`, sex,
      birthday)
    values (?, ?, ?,
      ?) ::: DataSources: ds1
2021-04-14 18:28:55.465  INFO 16092 --- [nio-8085-exec-5] ShardingSphere-SQL                       : Rule Type: master-slave
2021-04-14 18:28:55.465  INFO 16092 --- [nio-8085-exec-5] ShardingSphere-SQL                       : SQL: select * from ksd_user ::: DataSources: ds3

4、分库分表

一般的机器(4核16G),单库的MySQL并发(QPS+TPS)超过了2k,系统基本就完蛋了。最好是并发量控制在1k左右。这里就引出一个问题,为什么要分库分表?

答:分库分表目的:解决高并发,和数据量大的问题

1、高并发情况下,会造成IO读写频繁,自然就会造成读写缓慢,甚至是宕机。一般单库不要超过2k并发,NB的机器除外。 2、数据量大的问题。主要由于底层索引实现导致,MySQL的索引实现为B+TREE,数据量其他,会导致索引树十分庞大,造成查询缓慢。第二,innodb的最大存储限制64TB。

答:要解决上述问题。最常见做法,就是分库分表。 分库分表的目的,是将一个表拆成N个表,就是让每个表的数据量控制在一定范围内,保证SQL的性能。 一个表数据建议不要超过500W。

img

分库分表

img

水平拆分:统一个表的数据拆到不同的库不同的表中。可以根据时间、地区、或某个业务键维度,也可以通过hash进行拆分,最后通过路由访问到具体的数据。拆分后的每个表结构保持一致。

垂直拆分:就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,可以根据业务维度进行拆分,如订单表可以拆分为订单、订单支持、订单地址、订单商品、订单扩展等表;也可以,根据数据冷热程度拆分,20%的热点字段拆到一个表,80%的冷字段拆到另外一个表

不停机分库分表数据迁移

一般数据库的拆分也是有一个过程的,一开始是单表,后面慢慢拆成多表。那么我们就看下如何平滑的从MySQL单表过度到MySQL的分库分表架构。 1、利用mysql+canal做增量数据同步,利用分库分表中间件,将数据路由到对应的新表中。 2、利用分库分表中间件,全量数据导入到对应的新表中。 3、通过单表数据和分库分表数据两两比较,更新不匹配的数据到新表中。 4、数据稳定后,将单表的配置切换到分库分表配置上。

img

总结

垂直拆分:业务模块拆分、商品库,用户库,订单库 水平拆分:对表进行水平拆分(也就是我们说的:分表) 表进行垂直拆分:表的访问频度比较高,并且表的字段过多,字段使用的频率不一。(可以拆分两个表建立1:1关系)

一定要在数据量足够大的情况下再使用分库分表,否则反而会影响访问速度和性能

5、分库分表编写

逻辑表配置

spring:
  shardingsphere:
    sharding:
      tables:
        # ksd_user 逻辑表名
        ksd_user:

分库分表配置

tables:
        # ksd_user 逻辑表名
        ksd_user:
          # 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 相同表
          actual-data-nodes: ds$->{0..2}.ksd_user$->{0..1}
           # 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 不同表
          actual-data-nodes: ds0.ksd_user$->{0..1},ds1.ksd_user$->{2..4}
          # 指定单数据源的配置方式
          actual-data-nodes: ds0.ksd_user$->{0..4}
          # 全部手动指定
          actual-data-nodes: ds0.ksd_user0,ds1.ksd_user0,ds0.ksd_user1,ds1.ksd_user1,

数据结点说明

img

分库分表5种分片策略

数据源分片分为两种:

  • 数据源分片
  • 表分片

这两个是不同维度的分片规则,但是它们额能用的分片策略和规则是一样的。它们由两部分构成:

  • 分片键
  • 分片算法

第一种:none

对应NoneShardingStragey,不分片策略,SQL会被发给所有节点去执行,这个规则没有子项目可以配置。

第二种:inline 行表达时分片策略(核心,必须要掌握)

对应InlineShardingStragey。使用Groovy的表达时,提供对SQL语句种的=和in的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开放,如:ksd_user${分片键(数据表字段)userid % 5} 表示ksd_user表根据某字段(userid)模 5.从而分为5张表,表名称为:ksd_user0到ksd_user4 。如果库也是如此。

server:
  port: 8085

spring:
  shardingsphere:
    props:
      sql:
        show: true
    datasource:
      names: ds0,ds1,ds2
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://49.232.216.235:3306/ksd_order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: masterpwd
        maxPoolSize: 100
        minPoolSize: 5
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ksd_order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: root123
        maxPoolSize: 100
        minPoolSize: 5
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://49.232.216.235:3307/ksd_order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: slavepwd
        maxPoolSize: 100
        minPoolSize: 5
    sharding:
      tables:
        # ksd_user 逻辑表名
        ksd_user:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.ksd_user$->{0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            inline:
              sharding-column: sex    # 分片字段(分片键)
              algorithm-expression: ds$->{sex % 2} # 分片算法表达式
          # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: ksd_user$->{age % 2} # 分片算法表达式
  main:
    allow-bean-definition-overriding: true

mybatis:
  mapper-locations: classpath*:generator/**/*Dao.xml

对主库进行分库分表

第三种:根据实时间日期 - 按照标准规则分库分表

  • 对应StrandardShardingStrategy.提供对SQL语句中的=,in和恶between and 的分片操作支持。
  • StrandardShardingStrategy只支持但分片键。提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。
  • PreciseShardingAlgorithm是必选的呃,用于处理=和IN的分片
  • 和RangeShardingAlgorithm是可选的,是用于处理Betwwen and分片,如果不配置和RangeShardingAlgorithm,SQL的Between AND 将按照全库路由处理。
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;
            }
        }
        //teturn 数据源
        return target;
    }
}

按照月份进行分表

//分片策略类
package com.example.demo.config;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.Collection;
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;
    }
}
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/ksd-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: mkxiaoer1986.
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds1-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ksd_order_db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: root123
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds0
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds0
      # 配置分表的规则
      tables:
        ksd_user_order:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.ksd_user_order_$->{2021..2022}${(1..3).collect{t ->t.toString().padLeft(2,'0')} }
          key-generator:
            column: orderid
            type: SNOWFLAKE
          database-strategy:
            inline:
              sharding-column: orderid    # 分片字段(分片键)
              algorithm-expression: ds$->{orderid % 2} # 分片算法表达式
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          table-strategy:
            #            inline:
            #              shardingColumn: yearmonth
            #              algorithmExpression: ksd_user_order_$->{yearmonth}
            standard:
              shardingColumn: yearmonth
              preciseAlgorithmClassName: com.example.demo.config.YearMonthShardingAlgorithm
mybatis:
  mapper-locations: classpath*:generator/**/*Dao.xml

分片键不能为空,否则会向所有的表插入数据

分页查询

从pageNo开始查询,查询10@Select("select * from ksd_user_order limit #{pageNo},10")
List<UserOrder> finduserOrders(@Param("pageNo")Integer pageNo);

@Select("select count(1) from ksd_user_order")

es存储查询

6、事务

XA事务

  • 支持数据分片后的跨库XA事务
  • 两阶段提交保证操作的原子性和数据的强一致性
  • 服务宕机重启后,提交/回滚中的事务可自动恢复
  • SPI机制整合主流的XA事务管理器,默认Atomikos,可以选择使用Narayana和Bitronix
  • 同时支持XA和非XA的连接池
  • 提供spring-boot和namespace的接入端
 <!--依赖sharding-->
 <dependency>
     <groupId>io.shardingsphere</groupId>
     <artifactId>sharding-transaction-spring-boot-starter</artifactId>
     <version>3.1.0</version>
 </dependency>
类似于@Transcation的用法
@ShardingTransactionType(TransactionType.XA)

SETA分布式事务

  • 支持数据分片后的跨库XA事务
  • 两阶段提交保证操作的原子性和数据的强一致性
  • 服务宕机重启后,提交/回滚中的事务可自动恢复
  • SPI机制整合主流的XA事务管理器,默认Atomikos,可以选择使用Narayana和Bitronix
  • 同时支持XA和非XA的连接池
  • 提供spring-boot和namespace的接入端

7、总结

# 数据节点:数据源$->{0..N}.逻辑表名$->{0..N} futureTask
actual-data-nodes: ds$->{0..1}.ksd_user$->{0..1}

原理就是futureTask进行分开阻塞操作

8、常见面试问题

基础规范

  • 表必须要有主键
  • 禁止使用外键,表之间的关联性通过程序代码来完成
  • 表在设计之前,评估数量级,若表记录小于1kw,尽量使用单表,不建议分表
  • 将大字段,不常用字段进行水平拆分
  • 控制逻辑表的总数,单个表的数量控制在1024以内

列规范

  • 合理使用设计列的属性
  • 手机号尽量用varchar(20),input可能是字符串类型,避免隐式转换
  • 使用int存储ip地址,可以对ip进行比较
  • 设计金额使用Decimal,并且制定精度
  • 不要设计为null的字段,而是空字符,null需要占用更多的空间,并且使得索引和统计变得更加复杂

索引规范

  • 命名习惯:唯一索引用uniq_来标识
  • 命名习惯:非唯一索引使用idx_来标识
  • 不要在频繁更新的字段上加索引,一旦更新索引就会重建,一旦重建就会锁表
  • 非必要不要进行join查询,如果join,类型必须相同,并且建立索引,主表和join表都要建立索引
  • 单表索引控制在五个以内
  • 对于长度较大的字段进行索引查询时,考虑限定查询长度
  • 组合索引不要超过字段不要超过五个,理解组合索引的最左前缀原则,不要建立重复的索引,带头打个不在,那索引直接失效
  • orderBy/groupBy的字段上建立索引

SQL规范

  • 禁止使用select * ,只查询必要的字段
  • insert语句一定要给出需要插入的列
  • 尽量不要使用mysql内置函数或者表达式
  • 禁止使用负查询,以及%开头的查询,造成不能命中索引,导致全表扫描
  • 采用游标的方式进行查询
(select * from (select * from where id > 10)) limit 10000 , 50
  • 非必要不要进行join查询,如果join,类型必须相同,并且建立索引,主表和join表都要建立索引
  • 尽量捕捉sql异常

表的垂直拆分

垂直拆分:业务模块拆分、商品库,用户库,订单库 水平拆分:对表进行水平拆分(也就是我们说的:分表) 表进行垂直拆分:表的字段过多,字段使用的频率不一。(可以拆分两个表建立1:1关系)

  • 将一个属性过多的表,一行数据较大的表,将不同的属性分割到不同的数据库表中。以降低单库表的大小。 特点:
  • 每个表的结构不一致
  • 每个表的数量都是全量
  • 表和表之间一定会有一列会进行关联,一般都是主键

原则:

  • 将长度较短,访问频率较高的字段放在一个表中,主表
  • 将长度较长、访问频率比较低的字段放一个表中
  • 将经常访问字段放一个表中。
  • 所有表的并集是全量数据。

9、如何在不停服务的情况下平滑的添加字段

  1. 直接alter table add aolumn,不建议
  2. 提前预留字段,不优雅,而且列不具有语义性
  3. 新增一张表,迁移原表数据,再重命名为原表
  4. 放入一个扩展信息,exinfo,建立一个表产生映射关系,无法命中索引
  5. 提前设计,使用key/value进行存储,新增字段时,直接增加一个key就好,比如使用redis的map,set来进行处理,达到一定量级后慢慢进行数据的迁移