海量数据之Sharding-JDBC分库分表

1.Sharding-JDBC快速入门

1.引入maven依赖
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>${latest.release.version}</version>
</dependency>

注意: 请将${latest.release.version}更改为实际的版本号。

2.规则配置

Sharding-JDBCk可以通过Java、YAML、Spring命名空间和Spring Boot Starter四种方式配置,开发者可根据场景选择适合的配置方式。

  • Java配置

    配置项说明

    ​ 数据分片 ShardingDataSourceFactory

    ​ 数据分片的数据源创建工厂

    名称数据类型说明
    dataSourceMapMap<String, DataSource>数据源配置
    shardingRuleConfigShardingRuleConfiguration数据分片配置规则
    props (?)Properties属性配置
    ShardingRuleConfiguration

    分片规则配置对象

    名称数据类型说明
    tableRuleConfigsCollection分片规则列表
    bindingTableGroups (?)Collection绑定表规则列表
    broadcastTables (?)Collection广播表规则列表
    defaultDataSourceName (?)String未配置分片规则的表将通过默认数据源定位
    defaultDatabaseShardingStrategyConfig (?)ShardingStrategyConfiguration默认分库策略
    defaultTableShardingStrategyConfig (?)ShardingStrategyConfiguration默认分表策略
    defaultKeyGeneratorConfig (?)KeyGeneratorConfiguration默认自增列值生成器配置,缺省将使用org.apache.shardingsphere.core.keygen.generator.impl.SnowflakeKeyGenerator
    masterSlaveRuleConfigs (?)Collection读写分离规则,缺省表示不使用读写分离

    TableRuleConfiguration

    表封片规则配置对象

    名称数据类型说明
    logicTableString逻辑表名称
    actualDataNodes (?)String由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
    databaseShardingStrategyConfig (?)ShardingStrategyConfiguration分库策略,缺省表示使用默认分库策略
    tableShardingStrategyConfig (?)ShardingStrategyConfiguration分表策略,缺省表示使用默认分表策略
    keyGeneratorConfig (?)KeyGeneratorConfiguration自增列值生成器配置,缺省表示使用默认自增主键生成器
    encryptorConfiguration (?)EncryptorConfiguration加解密生成器配置

    StandardShardingStrategyConfiguration

    ShardingStrategyConfiguration的实现类,用于单分片键的标准分片场景

    名称数据类型说明
    shardingColumnString分片列名称
    preciseShardingAlgorithmPreciseShardingAlgorithm精确分片算法,用于=和IN
    rangeShardingAlgorithm (?)RangeShardingAlgorithm范围分片算法,用于BETWEEN

    ComplexShardingStrategyConfiguration

    ShardingStrategyConfiguration的实现类,用于多分片键的复合分片场景

    名称数据类型说明
    shardingColumnsString分片列名称,多个列以逗号分隔
    shardingAlgorithmComplexKeysShardingAlgorithm复合分片算法

    InlineShardingStrategyConfiguration

    ShardingStrategyConfiguration的实现类,用于配置行表达式分片策略

    名称数据类型说明
    shardingColumnString分片列名称
    algorithmExpressionString分片算法行表达式,需符合groovy语法,详情请参考

    HintShardingStrategyConfiguration

    ShardingStrategyConfiguration的实现类,用于配置Hint方式分片策略

    名称数据类型说明
    shardingAlgorithmHintShardingAlgorithmHint分片算法

    NoneShardingStrategyConfiguration

    ShardingStrategyConfiguration的实现类,用于配置不分片的策略

    KeyGeneratorConfiguration

    名称数据类型说明
    columnString自增列名称
    typeString自增列值生成器类型,可自定义或选择内置类型:SNOWFLAKE/UUID
    propsProperties自增列值生成器的相关属性配置

    Properties

    属性配置项,可以为以下自增列值生成器的属性

    SNOWFLAKE

    名称数据类型说明
    worker.id (?)long工作机器唯一id,默认为0
    max.tolerate.time.difference.milliseconds (?)long最大容忍时钟回退时间,单位:毫秒。默认为10毫秒
    max.vibration.offset (?)int最大抖动上限值,范围[0, 4096),默认为1。注:若使用此算法生成值作分片值,建议配置此属性。此算法在不同毫秒内所生成的key取模2^n (2^n一般为分库或分表数) 之后结果总为0或1。为防止上述分片问题,建议将此属性值配置为(2^n)-1

    EncryptRuleConfiguration

    名称数据类型说明
    encryptorsMap<String, EncryptorRuleConfiguration>加解密器配置列表,可自定义或选择内置类型:MD5/AES
    tablesMap<String, EncryptTableRuleConfiguration>加密表配置列表

    EncryptorRuleConfiguration

    名称数据类型说明
    typeString加解密器类型,可自定义或选择内置类型:MD5/AES
    propertiesProperties属性配置, 注意:使用AES加密器,需要配置AES加密器的KEY属性:aes.key.value

    EncryptTableRuleConfiguration

    名称数据类型说明
    tablesMap<String, EncryptColumnRuleConfiguration>加密列配置列表

    EncryptColumnRuleConfiguration

    名称数据类型说明
    plainColumnString存储明文的字段
    cipherColumnString存储密文的字段
    assistedQueryColumnString辅助查询字段,针对ShardingQueryAssistedEncryptor类型的加解密器进行辅助查询
    encryptorString加解密器名字

    Properties

    属性配置项,可以为以下属性

    名称数据类型说明
    sql.show (?)boolean是否开启SQL显示,默认值: false
    executor.size (?)int工作线程数量,默认值: CPU核数
    max.connections.size.per.query (?)int每个物理数据库为每次查询分配的最大连接数量。默认值: 1
    check.table.metadata.enabled (?)boolean是否在启动时检查分表元数据一致性,默认值: false
    query.with.cipher.column (?)boolean当存在明文列时,是否使用密文列查询,默认值: true
    allow.range.query.with.inline.sharding (?)boolean当使用inline分表策略时,是否允许范围查询,默认值: false
  • Yaml配置

    数据分片

    dataSources: #数据源配置,可配置多个data_source_name
      <data_source_name>: #<!!数据库连接池实现类> `!!`表示实例化该类
        driverClassName: #数据库驱动类名
        url: #数据库url连接
        username: #数据库用户名
        password: #数据库密码
        # ... 数据库连接池的其它属性
    
    shardingRule:
      tables: #数据分片规则配置,可配置多个logic_table_name
        <logic_table_name>: #逻辑表名称
          actualDataNodes: #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
            
          databaseStrategy: #分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一
            standard: #用于单分片键的标准分片场景
              shardingColumn: #分片列名称
              preciseAlgorithmClassName: #精确分片算法类名称,用于=和IN。。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
              rangeAlgorithmClassName: #范围分片算法类名称,用于BETWEEN,可选。。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
            complex: #用于多分片键的复合分片场景
              shardingColumns: #分片列名称,多个列以逗号分隔
              algorithmClassName: #复合分片算法类名称。该类需实现ComplexKeysShardingAlgorithm接口并提供无参数的构造器
            inline: #行表达式分片策略
              shardingColumn: #分片列名称
              algorithmInlineExpression: #分片算法行表达式,需符合groovy语法
            hint: #Hint分片策略
              algorithmClassName: #Hint分片算法类名称。该类需实现HintShardingAlgorithm接口并提供无参数的构造器
            none: #不分片
          tableStrategy: #分表策略,同分库策略
          keyGenerator: 
            column: #自增列名称,缺省表示不使用自增主键生成器
            type: #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID
            props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性
              <property-name>: 属性名称
          
      bindingTables: #绑定表规则列表
      - <logic_table_name1, logic_table_name2, ...> 
      - <logic_table_name3, logic_table_name4, ...>
      - <logic_table_name_x, logic_table_name_y, ...>
      broadcastTables: #广播表规则列表
      - table_name1
      - table_name2
      - table_name_x
      
      defaultDataSourceName: #未配置分片规则的表将通过默认数据源定位  
      defaultDatabaseStrategy: #默认数据库分片策略,同分库策略
      defaultTableStrategy: #默认表分片策略,同分库策略
      defaultKeyGenerator: #默认的主键生成算法 如果没有设置,默认为SNOWFLAKE算法
        type: #默认自增列值生成器类型,缺省将使用org.apache.shardingsphere.core.keygen.generator.impl.SnowflakeKeyGenerator。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID
        props:
          <property-name>: #自增列值生成器属性配置, 比如SNOWFLAKE算法的worker.id与max.tolerate.time.difference.milliseconds
    
      masterSlaveRules: #读写分离规则,详见读写分离部分
        <data_source_name>: #数据源名称,需要与真实数据源匹配,可配置多个data_source_name
          masterDataSourceName: #详见读写分离部分
          slaveDataSourceNames: #详见读写分离部分
          loadBalanceAlgorithmType: #详见读写分离部分
          props: #读写分离负载算法的属性配置
            <property-name>: #属性值
          
    props: #属性配置
      sql.show: #是否开启SQL显示,默认值: false
      executor.size: #工作线程数量,默认值: CPU核数
      max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1
      check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false
    
  • Spring Boot配置

    配置项

    数据分片

    spring.shardingsphere.datasource.names= #数据源名称,多数据源以逗号分隔
    
    spring.shardingsphere.datasource.<data-source-name>.type= #数据库连接池类名称
    spring.shardingsphere.datasource.<data-source-name>.driver-class-name= #数据库驱动类名
    spring.shardingsphere.datasource.<data-source-name>.url= #数据库url连接
    spring.shardingsphere.datasource.<data-source-name>.username= #数据库用户名
    spring.shardingsphere.datasource.<data-source-name>.password= #数据库密码
    spring.shardingsphere.datasource.<data-source-name>.xxx= #数据库连接池的其它属性
    
    spring.shardingsphere.sharding.tables.<logic-table-name>.actual-data-nodes= #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
    
    #分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一
    
    #用于单分片键的标准分片场景
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.standard.sharding-column= #分片列名称
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.standard.precise-algorithm-class-name= #精确分片算法类名称,用于=和IN。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.standard.range-algorithm-class-name= #范围分片算法类名称,用于BETWEEN,可选。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
    
    #用于多分片键的复合分片场景
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.complex.sharding-columns= #分片列名称,多个列以逗号分隔
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.complex.algorithm-class-name= #复合分片算法类名称。该类需实现ComplexKeysShardingAlgorithm接口并提供无参数的构造器
    
    #行表达式分片策略
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.inline.sharding-column= #分片列名称
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.inline.algorithm-expression= #分片算法行表达式,需符合groovy语法
    
    #Hint分片策略
    spring.shardingsphere.sharding.tables.<logic-table-name>.database-strategy.hint.algorithm-class-name= #Hint分片算法类名称。该类需实现HintShardingAlgorithm接口并提供无参数的构造器
    
    #分表策略,同分库策略
    spring.shardingsphere.sharding.tables.<logic-table-name>.table-strategy.xxx= #省略
    
    spring.shardingsphere.sharding.tables.<logic-table-name>.key-generator.column= #自增列名称,缺省表示不使用自增主键生成器
    spring.shardingsphere.sharding.tables.<logic-table-name>.key-generator.type= #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID
    spring.shardingsphere.sharding.tables.<logic-table-name>.key-generator.props.<property-name>= #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性
    
    spring.shardingsphere.sharding.binding-tables[0]= #绑定表规则列表
    spring.shardingsphere.sharding.binding-tables[1]= #绑定表规则列表
    spring.shardingsphere.sharding.binding-tables[x]= #绑定表规则列表
    
    spring.shardingsphere.sharding.broadcast-tables[0]= #广播表规则列表
    spring.shardingsphere.sharding.broadcast-tables[1]= #广播表规则列表
    spring.shardingsphere.sharding.broadcast-tables[x]= #广播表规则列表
    
    spring.shardingsphere.sharding.default-data-source-name= #未配置分片规则的表将通过默认数据源定位
    spring.shardingsphere.sharding.default-database-strategy.xxx= #默认数据库分片策略,同分库策略
    spring.shardingsphere.sharding.default-table-strategy.xxx= #默认表分片策略,同分表策略
    spring.shardingsphere.sharding.default-key-generator.type= #默认自增列值生成器类型,缺省将使用org.apache.shardingsphere.core.keygen.generator.impl.SnowflakeKeyGenerator。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID
    spring.shardingsphere.sharding.default-key-generator.props.<property-name>= #自增列值生成器属性配置, 比如SNOWFLAKE算法的worker.id与max.tolerate.time.difference.milliseconds
    
    spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.master-data-source-name= #详见读写分离部分
    spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.slave-data-source-names[0]= #详见读写分离部分
    spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.slave-data-source-names[1]= #详见读写分离部分
    spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.slave-data-source-names[x]= #详见读写分离部分
    spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.load-balance-algorithm-class-name= #详见读写分离部分
    spring.shardingsphere.sharding.master-slave-rules.<master-slave-data-source-name>.load-balance-algorithm-type= #详见读写分离部分
    
    spring.shardingsphere.props.sql.show= #是否开启SQL显示,默认值: false
    spring.shardingsphere.props.executor.size= #工作线程数量,默认值: CPU核数
    

2核心概念

2.1数据分片

核心概念

​ SQL

  • 逻辑表

    水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0t_order_9,他们的逻辑表名为t_order

  • 真实表

    在分片的数据库中真实存在的物理表。即上个示例中的t_order_0t_order_9

  • 数据节点

    数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0

  • 绑定表

    指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:

    SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
    
  • 广播表

    指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表

分片

  • 分片键

    用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片, 则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片

  • 分片算法

    通过分片算法将数据分片,支持通过=>=<=><BETWEENIN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

    目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

    • 精确分片算法

      对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用

    • 范围分片算法

      对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用

    • 复合分片算法

      对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用

    • Hint分片算法

      对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用

  • 分片策略

    • 标准分片策略

      对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理

    • 复合分片策略

      对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度

    • 行表达式分片策略

      对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0t_user_7

    • Hint分片策略

      对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略

    • 不分片策略

      对应NoneShardingStrategy。不分片的策略

  • SQL Hint

    对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用

2.2 内核剖析

解析引擎
抽象语法树

解析过程分为词法解析和语法解析。 词法解析器用于将SQL拆解为不可再分的原子符号,称为Token。并根据不同数据库方言所提供的字典,将其归类为关键字,表达式,字面量和操作符。 再使用语法解析器将SQL转换为抽象语法树

例如,以下SQL:

SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18

解析之后的为抽象语法树见下图:

在这里插入图片描述

最后,通过对抽象语法树的遍历去提炼分片所需的上下文,并标记有可能需要改写的位置。 供分片使用的解析上下文包含查询选择项(Select Items)、表信息(Table)、分片条件(Sharding Condition)、自增主键信息(Auto increment Primary Key)、排序信息(Order By)、分组信息(Group By)以及分页信息(Limit、Rownum、Top)。 SQL的一次解析过程是不可逆的,一个个Token的按SQL原本的顺序依次进行解析,性能很高。 考虑到各种数据库SQL方言的异同,在解析模块提供了各类数据库的SQL方言字典

SQL解析引擎

SQL解析作为分库分表类产品的核心,其性能和兼容性是最重要的衡量指标。 ShardingSphere的SQL解析器经历了3代产品的更新迭代。

第一代SQL解析器为了追求性能与快速实现,在1.4.x之前的版本使用Druid作为SQL解析器。经实际测试,它的性能远超其它解析器。

第二代SQL解析器从1.5.x版本开始,ShardingSphere采用完全自研的SQL解析引擎。 由于目的不同,ShardingSphere并不需要将SQL转为一颗完全的抽象语法树,也无需通过访问器模式进行二次遍历。它采用对SQL半理解的方式,仅提炼数据分片需要关注的上下文,因此SQL解析的性能和兼容性得到了进一步的提高。

第三代SQL解析器则从3.0.x版本开始,ShardingSphere尝试使用ANTLR作为SQL解析的引擎,并计划根据DDL -> TCL -> DAL –> DCL -> DML –>DQL这个顺序,依次替换原有的解析引擎,目前仍处于替换迭代中。 使用ANTLR的原因是希望ShardingSphere的解析引擎能够更好的对SQL进行兼容。对于复杂的表达式、递归、子查询等语句,虽然ShardingSphere的分片核心并不关注,但是会影响对于SQL理解的友好度。 经过实例测试,ANTLR解析SQL的性能比自研的SQL解析引擎慢3-10倍左右。为了弥补这一差距,ShardingSphere将使用PreparedStatement的SQL解析的语法树放入缓存。 因此建议采用PreparedStatement这种SQL预编译的方式提升性能。

第三代SQL解析引擎的整体结构划分如下图所示:

在这里插入图片描述

  • 路由引擎

    • 分片路由

      用于根据分片键进行路由的场景,又细分为直接路由、标准路由和笛卡尔积路由这3种类型

      • 直接路由

        满足直接路由的条件相对苛刻,它需要通过Hint(使用HintAPI直接指定路由至库表)方式分片,并且是只分库不分表的前提下,则可以避免SQL解析和之后的结果归并。 因此它的兼容性最好,可以执行包括子查询、自定义函数等复杂情况的任意SQL。直接路由还可以用于分片键不在SQL中的场景。例如,设置用于数据库分片的键为3

        hintManager.setDatabaseShardingValue(3);
        

        假如路由算法为value % 2,当一个逻辑库t_order对应2个真实库t_order_0t_order_1时,路由后SQL将在t_order_1上执行。下方是使用API的代码样例:

        String sql = "SELECT * FROM t_order";
        try (
                HintManager hintManager = HintManager.getInstance();
                Connection conn = dataSource.getConnection();
                PreparedStatement pstmt = conn.prepareStatement(sql)) {
            hintManager.setDatabaseShardingValue(3);
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    //...
                }
            }
        }
        
      • 标准路由

        标准路由是ShardingSphere最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅包含绑定表之间关联查询的SQL。 当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是BETWEEN或IN时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。 举例说明,如果按照order_id的奇数和偶数进行数据分片,一个单表查询的SQL如下:

        SELECT * FROM t_order WHERE order_id IN (1, 2);
        

        那么路由的结果应为:

        SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
        SELECT * FROM t_order_1 WHERE order_id IN (1, 2);
        
      • 笛卡尔路由

        笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询需要拆解为笛卡尔积组合执行。 如果上个示例中的SQL并未配置绑定表关系,那么路由的结果应为:

        SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
        SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
        SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
        SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id  WHERE order_id IN (1, 2);
        

        笛卡尔路由查询性能较低,需谨慎使用

    • 广播路由

      对于不携带分片键的SQL,则采取广播路由的方式。根据SQL类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这5种类型

      • 全库表路由

        全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作,主要包括不带分片键的DQL和DML,以及DDL等。例如

        SELECT * FROM t_order WHERE good_prority IN (1, 10);
        

        则会遍历所有数据库中的所有表,逐一匹配逻辑表和真实表名,能够匹配得上则执行。路由后成为

        SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
        SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);
        SELECT * FROM t_order_2 WHERE good_prority IN (1, 10);
        SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);
        
      • 全库路由

        全库路由用于处理对数据库的操作,包括用于库设置的SET类型的数据库管理命令,以及TCL这样的事务控制语句。 在这种情况下,会根据逻辑库的名字遍历所有符合名字匹配的真实库,并在真实库中执行该命令,例如:

        SET autocommit=0;
        

        t_order中执行,t_order有2个真实库。则实际会在t_order_0t_order_1上都执行这个命令

      • 全实例路由

        全实例路由用于DCL操作,授权语句针对的是数据库的实例。无论一个实例中包含多少个Schema,每个数据库的实例只执行一次。例如:

        CREATE USER customer@127.0.0.1 identified BY '123';
        

        这个命令将在所有的真实数据库实例中执行,以确保customer用户可以访问每一个实例

      • 单播路由

        单播路由用于获取某一真实表信息的场景,它仅需要从任意库中的任意真实表中获取数据即可。例如:

        DESCRIBE t_order;
        

        t_order的两个真实表t_order_0,t_order_1的描述结构相同,所以这个命令在任意真实表上选择执行一次

      • 阻断路由

        阻断路由用于屏蔽SQL对数据库的操作,例如:

        USE order_db;
        

        这个命令不会在真实数据库中执行,因为ShardingSphere采用的是逻辑Schema的方式,无需将切换数据库Schema的命令发送至数据库中。

        路由引擎的整体结构划分如下图:

        在这里插入图片描述

  • 改写引擎

    • 正确性改写

      在包含分表的场景中,需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。仅分库则不需要表名称的改写。除此之外,还包括补列和分页信息修正等内容

      • 标识符改写

        需要改写的标识符包括表名称、索引名称以及Schema名称。

        表名称改写是指将找到逻辑表在原始SQL中的位置,并将其改写为真实表的过程。表名称改写是一个典型的需要对SQL进行解析的场景。 从一个最简单的例子开始,若逻辑SQL为:

        SELECT order_id FROM t_order WHERE order_id=1;
        

        假设该SQL配置分片键order_id,并且order_id=1的情况,将路由至分片表1。那么改写之后的SQL应该为:

        SELECT order_id FROM t_order_1 WHERE order_id=1;
        

        在这种最简单的SQL场景中,是否将SQL解析为抽象语法树似乎无关紧要,只要通过字符串查找和替换就可以达到SQL改写的效果。 但是下面的场景,就无法仅仅通过字符串的查找替换来正确的改写SQL了:

        SELECT order_id FROM t_order WHERE order_id=1 AND remarks=' t_order xxx';
        正确改写的SQL应该是
        SELECT order_id FROM t_order_1 WHERE order_id=1 AND remarks=' t_order xxx';
        
        
      • 补列

        需要在查询语句中补列通常由两种情况导致。 第一种情况是ShardingSphere需要在结果归并时获取相应数据,但该数据并未能通过查询的SQL返回。 这种情况主要是针对GROUP BY和ORDER BY。结果归并时,需要根据GROUP BYORDER BY的字段项进行分组和排序,但如果原始SQL的选择项中若并未包含分组项或排序项,则需要对原始SQL进行改写。 先看一下原始SQL中带有结果归并所需信息的场景:

        SELECT order_id, user_id FROM t_order ORDER BY user_id;
        

        由于使用user_id进行排序,在结果归并中需要能够获取到user_id的数据,而上面的SQL是能够获取到user_id数据的,因此无需补列。

        如果选择项中不包含结果归并时所需的列,则需要进行补列,如以下SQL:

        SELECT order_id FROM t_order ORDER BY user_id;
        

        由于原始SQL中并不包含需要在结果归并中需要获取的user_id,因此需要对SQL进行补列改写。补列之后的SQL是:

        SELECT order_id, user_id AS ORDER_BY_DERIVED_0 FROM t_order ORDER BY user_id;
        
      • 分页修正

        从多个数据库获取分页数据与单数据库的场景是不同的。 假设每10条数据为一页,取第2页数据。在分片环境下获取LIMIT 10, 10,归并之后再根据排序条件取出前10条数据是不正确的。 举例说明,若SQL为:

        SELECT score FROM t_score ORDER BY score DESC LIMIT 1, 2;
        

        下图展示了不进行SQL的改写的分页执行结果:

        在这里插入图片描述

        通过图中所示,想要取得两个表中共同的按照分数排序的第2条和第3条数据,应该是9590。 由于执行的SQL只能从每个表中获取第2条和第3条数据,即从t_score_0表中获取的是9080;从t_score_0表中获取的是8575。 因此进行结果归并时,只能从获取的90808575之中进行归并,那么结果归并无论怎么实现,都不可能获得正确的结果。

        正确的做法是将分页条件改写为LIMIT 0, 3,取出所有前两页数据,再结合排序条件计算出正确的数据。 下图展示了进行SQL改写之后的分页执行结果。

        在这里插入图片描述

        越获取偏移量位置靠后数据,使用LIMIT分页方式的效率就越低。 有很多方法可以避免使用LIMIT进行分页。比如构建行记录数量与行偏移量的二级索引,或使用上次分页数据结尾ID作为下次查询条件的分页方式等。

        分页信息修正时,如果使用占位符的方式书写SQL,则只需要改写参数列表即可,无需改写SQL本身

      • 批量拆分

        在使用批量插入的SQL时,如果插入的数据是跨分片的,那么需要对SQL进行改写来防止将多余的数据写入到数据库中。 插入操作与查询操作的不同之处在于,查询语句中即使用了不存在于当前分片的分片键,也不会对数据产生影响;而插入操作则必须将多余的分片键删除。

        INSERT INTO t_order (order_id, xxx) VALUES (1, 'xxx'), (2, 'xxx'), (3, 'xxx');
        

        假设数据库仍然是按照order_id的奇偶值分为两片的,仅将这条SQL中的表名进行修改,然后发送至数据库完成SQL的执行 ,则两个分片都会写入相同的记录。 虽然只有符合分片查询条件的数据才能够被查询语句取出,但存在冗余数据的实现方案并不合理。因此需要将SQL改写为:

        INSERT INTO t_order_0 (order_id, xxx) VALUES (2, 'xxx');
        INSERT INTO t_order_1 (order_id, xxx) VALUES (1, 'xxx'), (3, 'xxx');
        
2.3 使用规范
  • SQL

    由于SQL语法灵活复杂,分布式数据库和单机数据库的查询场景又不完全相同,难免有和单机数据库不兼容的SQL出现。

    本文详细罗列出已明确可支持的SQL种类以及已明确不支持的SQL种类,尽量让使用者避免踩坑。

    示例

    支持的SQL

    SQL必要条件
    SELECT * FROM tbl_name
    SELECT * FROM tbl_name WHERE (col1 = ? or col2 = ?) and col3 = ?
    SELECT * FROM tbl_name WHERE col1 = ? ORDER BY col2 DESC LIMIT ?
    SELECT COUNT(*), SUM(col1), MIN(col1), MAX(col1), AVG(col1) FROM tbl_name WHERE col1 = ?
    SELECT COUNT(col1) FROM tbl_name WHERE col2 = ? GROUP BY col1 ORDER BY col3 DESC LIMIT ?, ?
    INSERT INTO tbl_name (col1, col2,…) VALUES (?, ?, ….)
    INSERT INTO tbl_name VALUES (?, ?,….)
    INSERT INTO tbl_name (col1, col2, …) VALUES (?, ?, ….), (?, ?, ….)
    UPDATE tbl_name SET col1 = ? WHERE col2 = ?
    DELETE FROM tbl_name WHERE col1 = ?
    CREATE TABLE tbl_name (col1 int, …)
    ALTER TABLE tbl_name ADD col1 varchar(10)
    DROP TABLE tbl_name
    TRUNCATE TABLE tbl_name
    CREATE INDEX idx_name ON tbl_name
    DROP INDEX idx_name ON tbl_name
    DROP INDEX idx_name
    SELECT DISTINCT * FROM tbl_name WHERE col1 = ?
    SELECT COUNT(DISTINCT col1) FROM tbl_name

    不支持的SQL

    SQL不支持原因
    INSERT INTO tbl_name (col1, col2, …) VALUES(1+2, ?, …)VALUES语句不支持运算表达式
    INSERT INTO tbl_name (col1, col2, …) SELECT col1, col2, … FROM tbl_name WHERE col3 = ?INSERT … SELECT
    SELECT COUNT(col1) as count_alias FROM tbl_name GROUP BY col1 HAVING count_alias > ?HAVING
    SELECT * FROM tbl_name1 UNION SELECT * FROM tbl_name2UNION
    SELECT * FROM tbl_name1 UNION ALL SELECT * FROM tbl_name2UNION ALL
    SELECT * FROM ds.tbl_name1包含schema
    SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name详见DISTINCT支持情况详细说明
    SELECT * FROM tbl_name WHERE to_date(create_time, ‘yyyy-mm-dd’) = ?会导致全路由

    DISTINCT支持情况详细说明

    SQL
    SELECT DISTINCT * FROM tbl_name WHERE col1 = ?
    SELECT DISTINCT col1 FROM tbl_name
    SELECT DISTINCT col1, col2, col3 FROM tbl_name
    SELECT DISTINCT col1 FROM tbl_name ORDER BY col1
    SELECT DISTINCT col1 FROM tbl_name ORDER BY col2
    SELECT DISTINCT(col1) FROM tbl_name
    SELECT AVG(DISTINCT col1) FROM tbl_name
    SELECT SUM(DISTINCT col1) FROM tbl_name
    SELECT COUNT(DISTINCT col1) FROM tbl_name
    SELECT COUNT(DISTINCT col1) FROM tbl_name GROUP BY col1
    SELECT COUNT(DISTINCT col1 + col2) FROM tbl_name
    SELECT COUNT(DISTINCT col1), SUM(DISTINCT col1) FROM tbl_name
    SELECT COUNT(DISTINCT col1), col1 FROM tbl_name GROUP BY col1
    SELECT col1, COUNT(DISTINCT col1) FROM tbl_name GROUP BY col1

    不支持的SQL

    SQL不支持原因
    SELECT SUM(DISTINCT col1), SUM(col1) FROM tbl_name同时使用普通聚合函数和DISTINCT聚合函数
  • 分页

    完全支持MySQL的分页查询

    • 分页性能

      性能瓶颈

      ​ 查询偏移量过大的分页会导致数据库获取数据性能低下,以MySQL为例:

      SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10
      

      这句SQL会使得MySQL在无法利用索引的情况下跳过1000000条记录后,再获取10条记录,其性能可想而知。 而在分库分表的情况下(假设分为2个库),为了保证数据的正确性,SQL会改写为:

      SELECT * FROM t_order ORDER BY id LIMIT 0, 1000010
      

      即将偏移量前的记录全部取出,并仅获取排序后的最后10条记录。这会在数据库本身就执行很慢的情况下,进一步加剧性能瓶颈。 因为原SQL仅需要传输10条记录至客户端,而改写之后的SQL则会传输1,000,010 * 2的记录至客户端

      ShardingSphere的优化

      ShardingSphere进行了2个方面的优化。

      首先,采用流式处理 + 归并排序的方式来避免内存的过量占用。由于SQL改写不可避免的占用了额外的带宽,但并不会导致内存暴涨。 与直觉不同,大多数人认为ShardingSphere会将1,000,010 * 2记录全部加载至内存,进而占用大量内存而导致内存溢出。 但由于每个结果集的记录是有序的,因此ShardingSphere每次比较仅获取各个分片的当前结果集记录,驻留在内存中的记录仅为当前路由到的分片的结果集的当前游标指向而已。 对于本身即有序的待排序对象,归并排序的时间复杂度仅为O(n),性能损耗很小。

      其次,ShardingSphere对仅落至单分片的查询进行进一步优化。 落至单分片查询的请求并不需要改写SQL也可以保证记录的正确性,因此在此种情况下,ShardingSphere并未进行SQL改写,从而达到节省带宽的目的。

    • 分页方案优化

      由于LIMIT并不能通过索引查询数据,因此如果可以保证ID的连续性,通过ID进行分页是比较好的解决方案:

      SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id
      

      或通过记录上次查询结果的最后一条记录的ID进行下一页的查询:

      SELECT * FROM t_order WHERE id > 100000 LIMIT 10
      
    • 分页子查询

      MySQL和PostgreSQL都支持LIMIT分页,无需子查询:

      SELECT * FROM t_order o ORDER BY id LIMIT ? OFFSET ?
      

3分库分表策略场景应用

常见策略Range
  • 场景方案:

​ 自增ID,根据ID范围进行分表(左闭右开)

  • 规则案例

    • 1~1000000是table_1
    • 1000000~2000000是table_2
    • 2000000~3000000是table_3
    • …更多
  • 优点

    • id是自增长,可以无限增长
    • 扩容不用迁移数据,容易理解和维护
  • 缺点

    • 大部分读和写都会访问新的数据,有IO瓶颈,整体资源利用率低
    • 数据倾斜严重,热点数据过于集中,部分节点有拼颈
Range策略延伸进阶
  • Range范围分库分表常用场景
    • 数字
      • 自增ID范围
    • 时间
      • 年、月、日范围
      • 比如按照月份生成库或表pay_log_2022_01、pay_log_2022_02
    • 空间
      • 地理位置: 省份、区域(华东、华北、华南)
      • 比如按照省份生成库或表
    • 基于Range范围分库分表业务场景
      • 微博发送记录、微信消息记录、日志记录,id曾长/时间分区都行
      • 水平分表为主,水平分库则容易造成资源的浪费
      • 网站签到等活动流水数据时间分区最好
        • 水平分区为主,水平分库则容易造成资源的浪费
      • 大区划分
        • SaaS业务水平拆分(华东、华南、华北等)
    • Hash取模策略
      • 案例规则
        • 用户ID是整数型的,要分2库,每个库数量4表,一共8张表
        • 用户ID取模后,值是0到7的要平均分配到每张表
A库ID=userId % 库数量 2
表ID userId /库数量 2 % 表数据4

4.SpringBoot+MybatisPlus整 合Sharding-Jdbc实战

SpringBoot2.5+MybatisPlus+Sharding-Jdbc集成
  • 框架版本说明
<properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.boot.version>2.5.5</spring.boot.version>
        <mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version>
        <lombok.version>1.18.16</lombok.version>
        <sharding-jdbc.version>4.1.1</sharding-jdbc.version>
        <junit.version>4.12</junit.version>
        <druid.version>1.1.16</druid.version>
        <!--跳过单元测试-->
        <skipTests>true</skipTests>
</properties>
  • pom文件配置
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring.boot.version}</version>
            <scope>test</scope>
        </dependency>


        <!--mybatis plus和springboot整合-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus.boot.starter.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <!--<scope>provided</scope>-->
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>${sharding-jdbc.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring.boot.version}</version>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>

        </plugins>
    </build>
订单案例分库分表SQL脚本创建说明
  • 分库分表

    • 2库2表
  • 数据库

    • shop_order_0

      • product_order_0
      • product_order_1
    • shop_order_1

      • product_order_0
      • product_order_1
    • SQL脚本

      CREATE TABLE `product_order_0` (
        `id` bigint NOT NULL AUTO_INCREMENT,
        `out_trade_no` varchar(64) DEFAULT NULL COMMENT '订单唯一标识',
        `state` varchar(11) DEFAULT NULL COMMENT 'NEW 未支付订单,PAY已经支付订单,CANCEL超时取消订单',
        `create_time` datetime DEFAULT NULL COMMENT '订单生成时间',
        `pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订单实际支付价格',
        `nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
        `user_id` bigint DEFAULT NULL COMMENT '用户id',
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
      
      
      CREATE TABLE `ad_config` (
        `id` bigint unsigned NOT NULL COMMENT '主键id',
        `config_key` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置key',
        `config_value` varchar(1024) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '配置value',
        `type` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '类型',
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
      
      #绑定表
      CREATE TABLE `product_order_item_0` (
        `id` bigint unsigned NOT NULL AUTO_INCREMENT,
        `product_order_id` bigint DEFAULT NULL COMMENT '订单号',
        `product_id` bigint DEFAULT NULL COMMENT '产品id',
        `product_name` varchar(128) DEFAULT NULL COMMENT '商品名称',
        `buy_num` int DEFAULT NULL COMMENT '购买数量',
        `user_id` bigint DEFAULT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
      
    • 实体模型类

      //数据库实体类
      public interface ProductOrderMapper extends BaseMapper<ProductOrderDO> {
      
      }
      
      public interface ProductOrderItemMapper extends BaseMapper<ProductOrderItemDO> {
      }
      
      public interface AdConfigMapper extends BaseMapper<AdConfigDO> {
      }
      
      @Data
      @TableName("product_order")
      @EqualsAndHashCode(callSuper = false)
      public class ProductOrderDO {
      
          private Long id;
      
          private String outTradeNo;
      
          private String state;
      
          private Date createTime;
      
          private Double payAmount;
      
          private String nickname;
      
          private Long userId;
      
      }
      
      
      
      @Data
      @TableName("product_order_item")
      @EqualsAndHashCode(callSuper = false)
      public class ProductOrderItemDO {
      
          private Long id;
      
          private Long productOrderId;
      
          private Long productId;
      
          private String productName;
      
          private Integer buyNum;
      
          private Long userId;
      
      }
      
      @Data
      @EqualsAndHashCode(callSuper = false)
      @TableName("ad_config")
      public class AdConfigDO {
      
          private Integer id;
          private String configKey;
          private String configValue;
          private String type;
      
      }
      
      
Sharding-Jdbc常规数据源配置和⽔平分表
  • 配置文件配置

    # 打印执行的数据库以及语句
    spring.shardingsphere.props.sql.show=true
    
    # 数据源 db0
    spring.shardingsphere.datasource.names=ds0,ds1
    
    # 第一个数据库
    spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.2.133:3306/shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    spring.shardingsphere.datasource.ds0.username=root
    spring.shardingsphere.datasource.ds0.password=123456
    
    
    # 第二个数据库
    spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://192.168.2.133:3306/shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    spring.shardingsphere.datasource.ds1.username=root
    spring.shardingsphere.datasource.ds1.password=123456
    
    
    #配置workId
    spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1
    
    
    #配置广播表
    spring.shardingsphere.sharding.broadcast-tables=ad_config
    spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
    spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
    
    
    #配置【默认分库策略】
    #spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
    #spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2 }
    #配置分库规则
    #spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.sharding-column=user_id
    #spring.shardingsphere.sharding.tables.product_order.database-strategy.inline.algorithm-expression=ds$->{user_id % 2 }
    
    
    #id生成策略
    spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
    spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
    
    # 指定product_order表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...},
    # 但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...}
    #spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
    #spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
    # 指定product_order表的分片策略,分片策略包括【分片键和分片算法】
    #spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=id
    #spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{id % 2}
    
    
    # 指定product_order_item表的分片策略,分片策略包括【分片键和分片算法】
    #spring.shardingsphere.sharding.tables.product_order_item.actual-data-nodes=ds$->{0..1}.product_order_item_$->{0..1}
    #spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.sharding-column=product_order_id
    #spring.shardingsphere.sharding.tables.product_order_item.table-strategy.inline.algorithm-expression=product_order_item_$->{product_order_id % 2}
    
    #配置绑定表
    #spring.shardingsphere.sharding.binding‐tables[0]=product_order,product_order_item
    
    #精准分片-水平分表
    # 指定product_order表的数据分布情况,配置数据节点,在 Spring 环境中建议使用 $->{...}
    spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds$->{0..1}.product_order_$->{0..1}
    #spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
    
    
SpringBoot+Sharding-Jdbc单元测试

5.分库分表之Snowflake雪花算法

分库分表下常⻅主键id⽣产策略实现
  • 单库下⼀般使⽤Mysql⾃增ID, 但是分库分表后,会造成不同分⽚
    上的数据表主键会重复

  • 需求

    • 性能强劲
    • 全局唯一
    • 防⽌恶意⽤户规矩id的规则来获取数据
  • 业界常⽤ID解决⽅案

    • 数据库⾃增ID
      • 利⽤⾃增id, 设置不同的⾃增步⻓
      • 缺点
        • 依靠数据库系统的功能实现,但是未来扩容麻烦
        • 主从切换时的不⼀致可能会导致重复发号
        • 性能瓶颈存在单台sql上
    • UUID
      • 性能⾮常⾼,没有⽹络消耗
      • 缺点
        • ⽆序的字符串,不具备趋势⾃增特性
        • UUID太⻓,不易于存储,浪费存储空间,很多场景不
          适⽤
    • Redis发号器
      • 利⽤Redis的INCR和INCRBY来实现,原⼦操作,线程安
        全,性能⽐Mysql强劲
      • 缺点
        • 需要占⽤⽹络资源,增加系统复杂度
    • Snowflake雪花算法
      • twitter 开源的分布式 ID ⽣成算法,代码实现简单、不占
        ⽤宽带、数据迁移不受影响
      • ⽣成的 id 中包含有时间戳,所以⽣成的 id 按照时间递增
      • 部署了多台服务器,需要保证系统时间⼀样,机器编号不
        ⼀样
      • 缺点
        • 依赖系统时钟(多台服务器时间⼀定要⼀样)
  • 分布式ID⽣成器Snowflake需要注意的问题

    • 分布式部署就需要分配不同的workId, 如果workId相同,
      可能会导致⽣成的id相同
    • 分布式情况下,需要保证各个系统时间⼀致,如果服务器
      的时钟回拨,就会导致⽣成的 id 重复
  • 配置实操

    #配置workId
    spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1
    
    • 方式实现一

      • 订单id使⽤MybatisPlus的配置

        @TableId(value = "id", type = IdType.ASSIGN_ID)
        默认实现类为DefaultIdentifierGenerator雪花算法
        
    • 方式实现二

      • 使⽤Sharding-Jdbc配置⽂件,注释DO类⾥⾯的id分配策略

        #id生成策略
        spring.shardingsphere.sharding.tables.product_order.key-generator.column=id
        spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
        
  • workerId动态指定

    • 动态制定workerId

      /**
       * sharding-jdbc 使用雪花算法生成主键时,workId配置
       *
       */
      @Configuration
      @Slf4j
      public class WorkIdConfig {
       
          /**
           * 设置workerIdValue的值
           */
          static {
       
              int workId = 0;
              try {
                  workId = getWorkId();
              } catch (Exception e) {
                  log.error("生成workId发生异常.", e);
              }
       
              System.setProperty("workerIdValue", String.valueOf(workId));
          }
       
       
          /**
           * 根据机器名称生成workId
           * @return
           */
          private static int getWorkId() {
              String hostAddress = System.getenv("HOSTNAME");
              log.info("============= hostAddress: {} =============", hostAddress);
              int[] ints = StringUtils.toCodePoints(hostAddress);
              int sum = 0;
       
              for (int b : ints) {
                  sum += b;
              }
       
              int workId = (sum % 32);
              log.info("============== workId:{} =============", workId);
              return workId;
          }
      }
      
      配置文件配置:
      spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=${workerIdValue}
      

6.⼴播表和绑定表配置实战

Sharding-Jdbc⼴播表介绍和配置实战
  • 什么是⼴播表

    • 指所有的分⽚数据源中都存在的表,表结构和表中的数据在每
      个数据库中均完全⼀致
    • 适⽤于数据量不⼤且需要与海量数据的表进⾏关联查询的场景
    • 例如:字典表、配置表
  • 注意点

    • 分库分表中间件,对应的数据库字段,不能是sql的关键字,
      否则容易出问题,且报错不明显
  • 配置实战

    • ad_config表

    • 配置文件

      #配置广播表
      spring.shardingsphere.sharding.broadcast-tables=ad_config
      spring.shardingsphere.sharding.tables.ad_config.key-generator.column=id
      spring.shardingsphere.sharding.tables.ad_config.key-generator.type=SNOWFLAKE
      
Sharding-Jdbc绑定表配置实战
  • 什么是绑定表

    • 指分⽚规则⼀致的主表和⼦表
    • ⽐如product_order表和product_order_item表,均按照
      order_id分⽚,则此两张表互为绑定表关系
    • 绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询
      效率将⼤⼤提升
  • 分库分表配置

    • 分库规则 根据 user_id 进⾏分库
    • 分表规则 根据 product_order_id 订单号进⾏分表
  • 实战代码配置

    #配置绑定表
    #错误配置,因为带有空格所以会导致绑定关系失败
    spring.shardingsphere.sharding.binding‐tables[0] = product_order,product_order_item
    
    #正确配置绑定表实现,逻辑表两边不能带有空格
    spring.shardingsphere.sharding.binding-tables[0] =product_order,product_order_item
    

7.Sharding-Jdbc分库分表分布式事务

  • 问题:分库分表带来的分布式事务问题

    • 操作内容同时分布在不同库中,不可避免会带来跨库事务问题,即分布式事务
  • 常见分布式事务处理方案

    • 事务消息

      • 最大努力通知型
    • 分布式框架

      • Seata:支持AT、TCC、SAGA和XA多种模式
        • 使用最对的AT模式
      • RocketMQ: 自带事务消息分布式事务
    • 高并发场景分布式事务解决

      • 解决思路:服务自理:

        • 库存服务扣减库存前保存一个Task任务,记录扣减的商品,数量还有关联的订单ID(扣减和保存任务在同个事务里面)

        • 然后使用定时Task任务表扫描(库存允许一定时间内不同步,最终一致性)

          • 订单被超时取消:一定时间内订单超时未支付被关闭,则恢复这个商品的库存

          • 订单不存在:下单、锁定库存后,程序问题导致订单服务异常回滚,查询不到订单,则恢复这个商品库存

            在这里插入图片描述

8.Sharding-Jdbc分库分表常⻅问题

  • 多维度查询方案: 空间换时间

    在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值