这天霞妹又来找烧哥了。

“烧哥,帮我看下这个分库分表,升级5.0后不能用了呢?”
“Let me c c?哇,Sharding-jdbc刚推出的最新版?”

背景

关系数据库当今依然占有巨大市场份额,前期通常会存储至单一节点。为提高性能和可用性,一种方案是迁移到NoSQL ,但会有较大技术成本。另一种则考虑数据分片,按照某个维度将数据分散地存储到多个库或表,来有效的避免大数据量产生的查询瓶颈。

数据分片也有两种。垂直分片讲究专库专用,通常按业务模块划分。水平分片则是按字段,通过某种规则拆分到不同库或表。通过搭建多主多从的数据库架构,读写分离,配合水平拆分,实际场景中较为常见。

ShardingSphere则同时提供了这两种解决方案,2020.4.16成为 Apache 软件基金会的顶级项目。Sharding-jdbc作为子产品,以Jar包形式提供服务,可理解为增强版的 JDBC 驱动,能够几乎不改动代码的情况下实现架构迁移,2021.11.10推出了5.0.0版。

问题重现

首先看之前的配置文件:
pom.xml

    org.apache.shardingsphere    sharding-jdbc-spring-boot-starter    4.1.1

application.yml

spring:  shardingsphere:    datasource:      names: master1,slave1      master1:        driver-class-name: com.mysql.cj.jdbc.Driver        password:         type: com.alibaba.druid.pool.DruidDataSource        url:         username:       slave1:        driver-class-name: com.mysql.cj.jdbc.Driver        password:         type: com.alibaba.druid.pool.DruidDataSource        url:         username:     props:      sql:        show: true    sharding:      tables:        b_gcg_content:          actual-data-nodes: ds.b_gcg_content_$->{0..1}          table-strategy:            inline:              sharding-column: content_id              algorithm-expression: b_gcg_content_$->{content_id % 2}              key-generator:                column: content_id                type: SNOWFLAKE      master-slave-rules:        ds:          load-balance-algorithm-type: round_robin          master-data-source-name: master1          slave-data-source-names: slave1

主库master1,从库slave1,对content表根据content_id取模拆分为两个表。

查询,插入,运行正常。

再看之后的:
pom.yml

    org.apache.shardingsphere    shardingsphere-jdbc-core-spring-boot-starter    5.0.0

application.yml

spring:  shardingsphere:    datasource:      names: master1,slave1      master1:        type: com.zaxxer.hikari.HikariDataSource        driverClassName: com.mysql.cj.jdbc.Driver        jdbcUrl:         username:         password:       slave1:        type: com.zaxxer.hikari.HikariDataSource        driverClassName: com.mysql.cj.jdbc.Driver        jdbcUrl:         username:         password:     props:      sql-show: true    rules:      sharding:        tables: # 数据分片规则配置          b_gcg_content: # 逻辑表名称            actualDataNodes: ds.b_gcg_content_$->{0..1} # 由数据源名 + 表名组成(参考Inline语法规则)            tableStrategy: # 分表策略,同分库策略              standard: # 用于单分片键的标准分片场景                shardingColumn: content_id # 分片列名称                shardingAlgorithmName: my # 分片算法名称            keyGenerateStrategy: # 分布式序列策略              column: content_id # 自增列名称,缺省表示不使用自增主键生成器              keyGeneratorName: my # 分布式序列算法名称        shardingAlgorithms:          my: # 分片算法名称            type: INLINE  # 分片算法类型            props: # 分片算法属性配置              algorithm-expression: b_gcg_content_$->{content_id % 2}        keyGenerators:          my: # 分布式序列算法名称            type: SNOWFLAKE # 分布式序列算法类型      readwriteSplitting:        dataSources:          ds:           loadBalancerName: my           writeDataSourceName: master1           readDataSourceNames: slave1        loadBalancers:          my: # 负载均衡算法名称             type: ROUND_ROBIN # 负载均衡算法类型

查询正常,插入时报错:

Insert statement does not support sharding table routing to multiple data nodes.

解谜

1.是否符合官方标准?(不熟悉的话常犯)

首先看到配置文件的语法,升级后有很大改变,根据官方文档挨个排查,确认格式全部正确。

2.是否新版本有缺陷?(最近经常有新闻,xx又有重大漏洞)

搜索这个报错,全网都没有。到官方仓库issue,有大把。引起的原因有很多,但都已修复。
那就下最新源码,5.0.1-SNAPSHOT,重新编译放入私服,岂不简单?
编译比较麻烦,不过最终还是成功了,更改maven版本,奇怪了,依然报错。

3.化繁为简,缩小范围,精准定位(首要思路)

报错中提到了data nodes,配置里同时有读写分离和分表,那就去掉一个,只要分表

    rules:      sharding:        tables: # 数据分片规则配置          b_gcg_content: # 逻辑表名称            actualDataNodes: master1.b_gcg_content_$->{0..1} # 由数据源名 + 表名组成(参考Inline语法规则)            tableStrategy: # 分表策略,同分库策略              standard: # 用于单分片键的标准分片场景                shardingColumn: content_id # 分片列名称                shardingAlgorithmName: my # 分片算法名称            keyGenerateStrategy: # 分布式序列策略              column: content_id # 自增列名称,缺省表示不使用自增主键生成器              keyGeneratorName: my # 分布式序列算法名称        shardingAlgorithms:          my: # 分片算法名称            type: INLINE  # 分片算法类型            props: # 分片算法属性配置              algorithm-expression: b_gcg_content_$->{content_id % 2}        keyGenerators:          my: # 分布式序列算法名称            type: SNOWFLAKE # 分布式序列算法类型

还是报错。
那不分表总可以吧?再来

    rules:      sharding:        tables: # 数据分片规则配置          b_gcg_content: # 逻辑表名称            actualDataNodes: master1.b_gcg_content # 由数据源名 + 表名组成(参考Inline语法规则)            tableStrategy: # 分表策略,同分库策略              standard: # 用于单分片键的标准分片场景                shardingColumn: content_id # 分片列名称                shardingAlgorithmName: my # 分片算法名称            keyGenerateStrategy: # 分布式序列策略              column: content_id # 自增列名称,缺省表示不使用自增主键生成器              keyGeneratorName: my # 分布式序列算法名称        shardingAlgorithms:          my: # 分片算法名称            type: INLINE  # 分片算法类型            props: # 分片算法属性配置              algorithm-expression: b_gcg_content        keyGenerators:          my: # 分布式序列算法名称            type: SNOWFLAKE # 分布式序列算法类型

这下倒是可以,看来的确是分表有问题。

4.跟正常的对比,由外到内,等价替换(廉价快速手段)

那官方demo不会也报错吗?apache出品的不至于吧?怀疑人生。
4.x的shardingsphere-example项目已经停更,新demo合并到了主库。
但官方demo是正常的?那就蹊跷了。
难道我的表不行?换成demo的库试试,还是报错,奇葩。
再来看看上面配置,唯一的区别就在于名称改成了my,难不成名称是不能改的??
改成跟demo一样名称,竟然行了。。。

5.避坑
spring:  shardingsphere:    datasource:      names: master1,slave1      master1:        type: com.zaxxer.hikari.HikariDataSource        driverClassName: com.mysql.cj.jdbc.Driver        jdbcUrl:         username:         password:       slave1:        type: com.zaxxer.hikari.HikariDataSource        driverClassName: com.mysql.cj.jdbc.Driver        jdbcUrl:         username:         password:     props:      sql-show: true    rules:      sharding:        tables: # 数据分片规则配置          b_gcg_content: # 逻辑表名称            actualDataNodes: ds.b_gcg_content_$->{0..1} # 由数据源名 + 表名组成(参考Inline语法规则)            tableStrategy: # 分表策略,同分库策略              standard: # 用于单分片键的标准分片场景                shardingColumn: content_id # 分片列名称                shardingAlgorithmName: my-table # 分片算法名称            keyGenerateStrategy: # 分布式序列策略              column: content_id # 自增列名称,缺省表示不使用自增主键生成器              keyGeneratorName: my-key # 分布式序列算法名称        shardingAlgorithms:          my-table: # 分片算法名称            type: INLINE  # 分片算法类型            props: # 分片算法属性配置              algorithm-expression: b_gcg_content_$->{content_id % 2}        keyGenerators:          my-key: # 分布式序列算法名称            type: SNOWFLAKE # 分布式序列算法类型      readwriteSplitting:        dataSources:          ds:           loadBalancerName: my-load           writeDataSourceName: master1           readDataSourceNames: slave1        loadBalancers:          my-load: # 负载均衡算法名称             type: ROUND_ROBIN # 负载均衡算法类型

“霞妹可以啦”
“烧哥好棒,我这有张券,中午一起嘛”

填坑

架构师的工作就是解决各种疑难杂症,思路的锻炼来自长期实战经历。

Apache的品质总体还是可信赖的,这算个小问题。源码应该是把某些名称放在了一个map下,或者是缓存时出了岔子,有空去提个issue.

又增加一条规范:复杂配置文件中,自定义名称不应该重复。