Sharding-Jdbc学习笔记四之水平分库+水平分表+读写分离
ShardingJdbc本身的核心代码是支持上面这个操作的,但是有一些配置代码,拦截了这个处理,导致无法再配置了读写分离的库上再配置水平分库,需要修改一些额外源码才能达到这个效果,所以这个模块的核心其实是水平分库+水平分表。
关于这一块不知道是不是我表述有问题,反正最终在官网提issue提到最后我都不想再描述我的问题了。我本地已经通过修改源码解决了这个问题,但是牵扯到的类有点多,暂时后面演示会把读写分离配置都注释掉。如果有不需要改源码就能解决的办法,那最好了,就是我没找到这种配置方式。如果找不到又想要,真的有兴趣,可以再私聊。
关于这一块的问题描述有兴趣的可以看一下,反正最后这个其实是没有解决我的疑问的。
https://github.com/apache/shardingsphere/issues/7269
预期目标
-
只演示配置的多样性以求理解用法,不讨论分片的合理性
-
auth_user保留上面的水平分表原则,(其实也想保留它的读写分离,但后面为了演示核心逻辑,会把这部分配置注释掉)
-
再增加一个user_article和user_article_1,水平分表规则使用user_id取模法
-
读写分离配置屏蔽对user_article和user_article的同步,然后再根据user_id进行分库操作
1. mysql准备
1.1 sql
暂时还没修改mysql的配置文件,在master服务执行语句后会自动同步到从库,所以目前从库也会有这两张表
use `boot-quick`;
CREATE TABLE `boot-quick`.`user_article` (
`id` bigint(0) NOT NULL,
`user_id` bigint(0) NOT NULL,
`title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题',
`content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容',
`create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,
`modify_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`modify_time` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
`removed` int(0) NOT NULL DEFAULT 0,
`version` int(0) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `boot-quick`.`user_article_1` (
`id` bigint(0) NOT NULL,
`user_id` bigint(0) NOT NULL,
`title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标题',
`content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容',
`create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,
`modify_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`modify_time` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
`removed` int(0) NOT NULL DEFAULT 0,
`version` int(0) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
1.2 修改my.conf
由于user_article要进行分库操作,所以不能进行主从同步,现在要修改两个从库的my.conf,来忽略这两张表的主从同步,修改后重启两个从库的服务,然后自行验证在主表加入数据,从库是否真的停了这两张表的同步操作
replicate-ignore-table=boot-quick.user_article
replicate-ignore-table=boot-quick.user_article_1
2. 项目配置
同样,实体的代码操作,依然不是这里的范畴
2.1 user_article分库策略
之前分片策略都是采用的inline, 然后使用的是Groovy的表达式。但现在是有三个库, 然后通过user_id进行取模,
如果为0 则对应数据源为master;如果为1, 则对应数据源为slave1; 如果为2,则对应数据源为slave2;
然后这个Groovy表达式我不会写,所以就只能写java代码了
简单说明对应的配置是,具体要看后面的全部核心配置文件
database-strategy:
standard:
sharding-column: user_id
preciseAlgorithmClassName: com.ddf.boot.quick.sharding.UserArticleDatabasePreciseShardingAlgorithm
对应的分库代码类
package com.ddf.boot.quick.sharding;
import cn.hutool.core.collection.CollUtil;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.Collection;
/**
* user_article的数据库分库策略$
*
* @author dongfang.ding
* @date 2020/9/5 0005 13:16
*/
public class UserArticleDatabasePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* Sharding.
*
* @param availableTargetNames available data sources or tables's names
* @param shardingValue sharding value
* @return sharding result for data source or table's name
*/
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
if (CollUtil.isEmpty(availableTargetNames)) {
throw new RuntimeException("无可用的数据源");
}
// 目前配置了三个数据库,master,slave0,slave1
// 当前这个分库策略是给user_article用的,分库字段是user_id,userId对可用数据库进行取模
Long value = shardingValue.getValue();
long index = value % availableTargetNames.size();
int i = 0;
for (String availableTargetName : availableTargetNames) {
if (i == index) {
return availableTargetName;
}
i ++;
}
return null;
}
}
2.1 项目核心配置
######################################水平分库分表+读写分离配置############################################################
# 如果既要支持读写分离又要水平分库,
# 那么得最少两个master,否则的话master的数据就会被同步到从库里。这里没有搞多个master,
# 但又要演示水平分库,所以下面配置的前提是目前用来演示水平分库的表是没有配置主从同步的
# auth_user 配置水平分表+主从同步+读写分离
# user_article 配置水平分库分表+特意在主从同步配置文件中忽略了该表的主从同步
# mysql master连接信息
master.mysql.host: localhost
master.mysql.port: 3306
master.mysql.db: boot-quick
master.mysql.username: root
master.mysql.password: 123456
# mysql slave0连接信息
slave0.mysql.host: localhost
slave0.mysql.port: 3307
slave0.mysql.db: boot-quick
slave0.mysql.username: root
slave0.mysql.password: 123456
# mysql slave0连接信息
slave1.mysql.host: localhost
slave1.mysql.port: 3308
slave1.mysql.db: boot-quick
slave1.mysql.username: root
slave1.mysql.password: 123456
spring:
main:
allow-bean-definition-overriding: true
# 经测试filter需要配置再datasource.druid下,而每个连接的信息还是得配置在sharding-sphere下的datasource每个自己的
datasource:
druid:
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 3000
wall:
enabled: true # 开启WallFilter
db-type: mysql
## 开启内置监控界面 访问路径: /context-path/druid/index.html
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: true
login-username: admin
login-password: 123456
allow:
shardingsphere:
datasource:
names: master,slave0,slave1
# 配置数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${master.mysql.host}:${master.mysql.port}/${master.mysql.db}?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&tinyInt1isBit=false
username: ${master.mysql.username}
password: ${master.mysql.password}
initial-size: 5
asyncInit: true
max-active: 30
min-idle: 10
keep-alive: true
max-wait: 60000
use-unfair-lock: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 600000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
poolPreparedStatements: false
max-open-prepared-statements: 20
# 配置数据源
slave0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${slave0.mysql.host}:${slave0.mysql.port}/${slave0.mysql.db}?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&tinyInt1isBit=false
username: ${slave0.mysql.username}
password: ${slave0.mysql.password}
initial-size: 5
asyncInit: true
max-active: 30
min-idle: 10
keep-alive: true
max-wait: 60000
use-unfair-lock: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 600000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
poolPreparedStatements: false
max-open-prepared-statements: 20
# 配置数据源
slave1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${slave1.mysql.host}:${slave1.mysql.port}/${slave1.mysql.db}?useUnicode=true&characterEncoding=UTF8&useSSL=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&tinyInt1isBit=false
username: ${slave1.mysql.username}
password: ${slave1.mysql.password}
initial-size: 5
asyncInit: true
max-active: 30
min-idle: 10
keep-alive: true
max-wait: 60000
use-unfair-lock: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 600000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
poolPreparedStatements: false
max-open-prepared-statements: 20
# 分支分片策略
sharding:
# https://shardingsphere.apache.org/document/legacy/4.x/document/cn/faq/#6-%E5%A6%82%E6%9E%9C%E5%8F%AA%E6%9C%89%E9%83%A8%E5%88%86%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E6%98%AF%E5%90%A6%E9%9C%80%E8%A6%81%E5%B0%86%E4%B8%8D%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8%E7%9A%84%E8%A1%A8%E4%B9%9F%E9%85%8D%E7%BD%AE%E5%9C%A8%E5%88%86%E7%89%87%E8%A7%84%E5%88%99%E4%B8%AD
# 为了解决不分库分表的表不需要配置分片规则
default-data-source-name: master
# 分表
tables:
# 逻辑表名, 即原表名比如有auth_user,现在以这个为依据分为实际的auth_user_1, auth_user_2, auth_user即为逻辑表名
auth_user:
# 配置数据表分布情况,哪个表在哪个数据源里,有哪些表,只要分配要写的表即可,这个因为要做读写分离和水平分表,所以只配了master的auth_user表分配情况
actual-data-nodes: master.auth_user,master.auth_user_1
table-strategy:
# 指定当前表的分片策略
inline:
sharding-column: id
# 配置分表策略, id对2(分表数量)进行取模,为0则使用则为auth_user, 其它则增加后缀"_" + 取模后的值
algorithm-expression: auth_user$->{id % 2 == 0 ? "":"_" + id % 2}
# 逻辑表名, user_article,现在以这个为依据分为实际的auth_user, auth_user_2, user_article即为逻辑表名
user_article:
# https://github.com/apache/shardingsphere/issues/7269
# 目前发现是有这个问题的,后续会跟踪最终结果。为了解决这个问题,目前本地提供的解决方案
useMasterSlaveRulesDatasourceNameIfExist: false
# 配置数据表分布情况,哪个表在哪个数据源里,有哪些表,只要配置要写的表,这个因为不做读写分离,只做水平分库分表,所以要把该逻辑表对应的所有数据库和数据表都配置出来
# 这里已经明确告知了框架,有这么多的库和表是对应这个逻辑表的,如果只配置了分表策略,而不配置分库策略,则插入时按照分表策略,则会将这个表对应的所有库都会执行插入语句
# 所以千万不要忘记配置了分库策略
actual-data-nodes: master.user_article,master.user_article_1,slave$->{0..1}.user_article,slave$->{0..1}.user_article_1
# 对actual-data-nodes配置的表的数据库的分布情况做分库策略
database-strategy:
# 分库文档 https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/sharding/concept/sharding/
standard:
# 根据哪个字段进行分库
sharding-column: user_id
# 配置分库策略对应的类,由于库名取的无法(或者我不会写)直接使用inline支持的Groovy表达式,所以需要自己开发实现一个类
# 参考官网文档https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/sharding/concept/sharding/
preciseAlgorithmClassName: com.ddf.boot.quick.sharding.UserArticleDatabasePreciseShardingAlgorithm
# 对actual-data-nodes配置的表的分布情况做分表策略
table-strategy:
# 指定当前表的分片策略
# 分库文档 https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/sharding/concept/sharding/
inline:
sharding-column: user_id
# 配置分表策略, user_id对2(分表数量)进行取模,为0则使用则为user_article, 其它则增加后缀"_" + 取模后的值
algorithm-expression: user_article$->{user_id % 2 == 0 ? "":"_" + user_id % 2}
# 这个是配置默认分库策略的,但是我们现在没有一个全局的默认分库策略,所以不用配置,我们现在只有具体表来分库的策略,所有要配置在sharding.tables下
# default-database-strategy:
# 配置分片之后的读写分离,分片之后的读写分离配置和只有读写分离的匹配属性是一样的,但是配置的位置是不同的,分片后读写分离配置要在sharding下
master-slave-rules:
# 对上面配置的actual-data-nodes的库配置读写分离策略
master:
# 从库负载均衡算法
load-balance-algorithm-type: round_robin
# 主库数据源名称, 从上面配置的数据源中选择
master-data-source-name: master
# 从库数据源名称列表, 从上面配置的数据源中选择
slave-data-source-names: slave0,slave1
props:
# 显示sql具体信息
sql.show: true
enabled: true
3. 演示效果
-
读写分离和分库后的查询依然有冲突问题,保持关注,所以还是先注释掉这块的读写分离配置
-
新增user_article, user_id为99,按照规则, 99 % 3 = 0, 则路由到master数据库;99 % 2 = 1, 则路由到user_article_1
-
新增user_article, user_id为100, 按照规则, 100 % 3 = 1, 则路由到slave0数据库; 100 % 2 = 0, 则路由到user_article表
项目通用配置和项目错误汇总
-
默认连接未配置,刚开始使用的版本没有这个问题,后来升到了4.1.1有这个问题
Action: Consider the following: If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
则需要在主启动类上排除当前项目所依赖的连接池的自动配置类,如使用的
druid
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
-
datasource已定义
Description: The bean 'dataSource', defined in class path resource [org/apache/shardingsphere/shardingjdbc/spring/boot/SpringBootConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/alibaba/druid/spring/boot/autoconfigure/DruidDataSourceAutoConfigure.class] and overriding is disabled. Action: Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
则按照上面的提示,加上配置
spring: main: allow-bean-definition-overriding: true