NormanZyq
发布于 2020-05-30 / 1023 阅读
0
0

记一次配置Sharding-JDBC 时遇到的错误和解决方案(Springboot 配置式分库)

《软件架构与中间件》课程让我接触到了分库分表的各种第三方库,比如Mycat、Sharding-JDBC,不得不说,实现这些的人实在是太厉害了。
我们的实验两人一组,分头做,最后配置到上个学期写的“进销存”系统中,我接受了Sharding-JDBC的配置任务。

开始之前

先简单介绍一些我眼中的Sharding-JDBC。它是一个处在JDBC层的一个中间件,与在应用层的Mycat不同,Sharding-JDBC的配置十分简单,并且完全不影响更高层的编码,也就是说,就像是把它嵌入进了程序,除了一些配置,不需要做任何对代码的修改,即使是使用了Mybatis框架,也不需要修改。它将本应该直接执行的sql代码根据配置文件做了一定的变化然后再影响到数据库,取数据的时候也是,将结果作了一些处理,再返回到程序。

遇到的错误、爬坑的过程

问题一:适配问题

首先简单阐述一下我的需求:在上学期的《软件过程与工具》中,我们做的进销存系统中,许多表的主键是唯一的列,而它是一个自增的列,我希望根据这个ID的奇偶性,将数据插入到不同的库的同名字的表中。

注:实际上,似乎无法利用自增ID完成我的需求,我会在后面讨论。但为了还原我进行配置的辛酸经历并引出我遇到的种种问题,我们先假设自增ID是能够达到我的需求的。


在根据实验指导进行配置之后,我发现程序并没有按照我的设想运行,具体出现的问题是:可以正常执行查询操作,但是如果执行插入/更新操作的时候就会抛出这样的错误:

"Missing the data source 'null'"

其实,出现这个问题的原因是数据源配置不正确,也就是分库分表的那几个数据源名字等等存在错误。

后来知道Springboot项目可以直接使用sharding-jdbc-spring-boot-starter依赖来代替sharding-jdbc-core,并且能够兼容Springboot的配置文件,让配置更加便捷,于是我按照网上的博客,进行了下面这样的配置:

sharding:
 jdbc:
   config:
     sharding:
       tables:
         goods:
           actual-data-nodes: wholesalesystem${1..2}.goods
           tableStrategy: # sharding 规 则
             inline:
               shardingColumn: id # 列 名
               algorithmExpression: wholesalesystem${(id % 2) + 1}  # 例如:id=1 时表示 db3
           keyGeneratorColumnName: goods_id
         warehouse:
           actual-data-nodes: wholesalesystem${1..2}.warehouse
           tableStrategy: # sharding 规 则
             inline:
               shardingColumn: wh_id # 列 名
               algorithmExpression: wholesalesystem${(id % 2) + 1}  # 例如:id=1 时表示 db3
           keyGeneratorColumnName: wh_id

(注:这个配置有很大的问题,千万别抄,下面详细解释每一个配置的意义)

问题二:配置错误

使用上面的配置的话,无论是查询还是插入都不可行了,这中间包含了很多很多的错误,我一一讲述。

首先,要明白这部分配置涉及到的概念有支持分库和分表两种,如果两个配置混淆,不但达不到效果,还会产生错误。那么这个区别体现在哪里呢?

sharding.jdbc.config.sharding.tables.xxx(表名).database-strategy
sharding.jdbc.config.sharding.tables.xxx(表名).table-strategy

现在应该能够发现区别了吧,上者是分库的相关配置,它的子结点的所有配置是引导分库策略的,而下面的是分表策略,也就是将不同行分配到不同的表当中。

但此时,我的需求是分库,并非分表,所以不需要使用到下面那一行的配置,可是我却在我的配置文件中使用的全部都是table-strategy,显然是错误的。

第二,在

sharding.jdbc.config.sharding.tables.goods_warehouse_relation.database-strategy.inline.algorithm-expression=xxx

这部分的配置中,我使用了id,而不是我的表中id的列名,这也是其中一个错误,比如我的goods表(商品表)的主键是goods_id,那么,如果我希望根据这个列分库,我应该写的是wholesalesystem${(goods_id % 2) + 1},类似的,我的warehouse表(仓库表)的主键是wh_id,如果需要 利用它分库,那么我应该写的是wholesalesystem${(wh_id % 2) + 1}

修复完这几处错误后,配置应该就没有问题了。

问题三:错误理解报错的原因

上面的修复过程看似十分顺利,实则不然,我查询了许许多多的博客,没有一个博客会那么详尽的告诉你,分库和分表的配置是有区别的,他们上来就给你贴代码,良心一点的,给你放上一段注释,但是仍然没有重点提及这两项的区别。作为一个刚刚接触Sharding-JDBC的新手,很容易就被“骗”了。

我在配置的时候,一共遇到了两个让我记忆犹新的错误,其中一个刚才已经提到过了,是

Missing the data source name 'null'(或者其他)

另一个大概是"Cannot find xxx sharding rule xxx"的错误,具体我没有记录下来,错误的原因我也没有探清,但是按照我上面说的配置完成之后,这个错误就消失了。

问题四:存在某些局限(如自增ID)

前面提到了自增ID会存在一些无法分库分表的问题,Sharding-JDBC似乎无法提前知道即将插入数据库的数据的自增ID会是多少,因此不能根据这个来进行分库分表,在我分库的逻辑下,操作的结果是两个数据库的相同表都会新增同样一条数据,所以要根据能够确定的列字段来进行计算并分库分表。

当时情况紧急,正在验收实验的时候我才意识到了这个错误可能的原因是自增ID,为了能够按时提交实验,我就用我的customer表(客户表)的客户身份(0和1,分别代表批发客户和零售客户)来将不同数据插入到不同数据库的相同表中,幸运的是,我的猜想正确,确定的字段能够使分库正常。

附Springboot 配置式分库分表的所有属性含义

这部分来自官网的开发手册Yaml配置 :: ShardingSphere,我的解决方案全部在这里找到的。

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

总结

  1. 不要盲目听信一些博客,要带着思考看问题。
  2. 重视官方文档。虽然它可能不是最好的,但一定是最准确的,如果遇到一些错误无法解决的时候,优先考虑从官方文档中寻求解决方案

评论