这天霞妹又来找烧哥了。
“烧哥,帮我看下这个分库分表,升级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.
又增加一条规范:复杂配置文件中,自定义名称不应该重复。