《软件架构与中间件》课程让我接触到了分库分表的各种第三方库,比如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
总结
- 不要盲目听信一些博客,要带着思考看问题。
- 重视官方文档。虽然它可能不是最好的,但一定是最准确的,如果遇到一些错误无法解决的时候,优先考虑从官方文档中寻求解决方案
评论区