前言
1.基础的环境介绍请移步ShardingSphere应用专题–4.1.1版本–sharding jdbc环境搭建(四)
你可以同时打开两个页面,避免因查找原始配置上下翻动。
2.ShardingSphere官方文档更新不及时,很容易踩坑。贴出的4.x版本文档实际是4.0.1版本的,如果你准备使用该版本可以参考官方文档。本文使用的是此时最新的正式版本4.1.1版本,配置与官方文档配置不同。事实上,源码中有非常详细的版本配置文档,本文也是参考4.1.1 tag的源码配置。
3.ShardingSphere各版本差异很大,甚至核心依赖的包名都不一样,使用时,一定要确认使用哪个版本,目前调研的结果:3.x、4.0.1、4.1.1、5.0.0-alpha都存在很大的配置差异
4.为了更好理解,在看本文前应先了解Sharding-JDBC,Sharding-Proxy,Sharding-UI相关产品,具体可参阅:
ShardingSphere应用专题–4.1.1版本–Sharding-JDBC环境搭建(四)
中的文章链接
Sharding-Scaling简介
Apache ShardingSphere 提供了数据分片的能力,可以将数据分散到不同的数据库节点上,提升整体处理能力。 但对于使用单数据库运行的系统来说,如何安全简单地将数据迁移至水平分片的数据库上,一直以来都是一个迫切的需求; 同时,对于已经使用了 Apache ShardingSphere 的用户来说,随着业务规模的快速变化,也可能需要对现有的分片集群进行弹性扩容或缩容。
- 老项目首次使用Sharding-JDBC按照一定的规则分库分表,老数据怎么办?
- 老项目已经使用了Sharding-JDBC将数据分成了2库2表,现在数据量还是很大,想分成10库10表,老数据怎么办?
这个就是现实中的数据库的弹性伸缩的场景
Sharding-Scaling就是ShardingSphere给出的一个提供给用户的通用数据接入迁移及弹性伸缩的解决方案
弹性伸缩方案架构
挑战
Apache ShardingSphere 在分片策略和算法上提供给用户极大的自由度,但却给弹性伸缩造成了极大的挑战。 如何找到一种方式,即能支持各类不同用户的分片策略和算法,又能高效地将数据节点进行伸缩,是弹性伸缩面临的第一个挑战;
同时,弹性伸缩过程中,不应该对正在运行的业务造成影响,尽可能减少伸缩时数据不可用的时间窗口,甚至做到用户完全无感知,是弹性伸缩的另一个挑战;
最后,弹性伸缩不应该对现有的数据造成影响,如何保证数据的可用性和正确性,是弹性伸缩的第三个挑战。
目标
支持各类用户自定义的分片策略,减少用户在数据伸缩及迁移时的重复工作及业务影响,提供一站式的通用弹性伸缩解决方案,是 Apache ShardingSphere 弹性伸缩的主要设计目标。
状态
当前处于 alpha 开发阶段。
核心概念
考虑到 Apache ShardingSphere 的弹性伸缩模块的几个挑战,目前的弹性伸缩解决方案为:临时地使用两个数据库集群,伸缩完成后切换的方式实现。
这种实现方式有以下优点:
- 伸缩过程中,原始数据没有任何影响
- 伸缩失败无风险
- 不受分片策略限制
同时也存在一定的缺点:
-
在一定时间内存在冗余服务器
-
所有数据都需要移动
弹性伸缩模块会通过解析旧分片规则,提取配置中的数据源、数据节点等信息,之后创建伸缩作业工作流,将一次弹性伸缩拆解为4个主要阶段 -
准备阶段
-
存量数据迁移阶段
-
增量数据同步阶段
-
规则切换阶段
弹性伸缩测试
1.业务场景描述
还是使用 ShardingSphere应用专题–4.1.1版本–服务治理(十四)一文中的数据。
- 目前的数据分成了mydb0,mydb1两个库,每个库中的bill表按照id%2分成了bill_0,bill_1两张表
- 目前的业务场景设定为,2张表已经不满足我们的业务数据,需要扩容至4张表
这里偷懒了一下,仍然使用mydb0,mydb1两个库,表名更改为bill_scaling
2.表结构准备
DROP TABLE IF EXISTS `mydb0`.`bill_scaling_0`;
CREATE TABLE `mydb0`.`bill_scaling_0`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
DROP TABLE IF EXISTS `mydb0`.`bill_scaling_1`;
CREATE TABLE `mydb0`.`bill_scaling_1`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
DROP TABLE IF EXISTS `mydb0`.`bill_scaling_2`;
CREATE TABLE `mydb0`.`bill_scaling_2`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
DROP TABLE IF EXISTS `mydb0`.`bill_scaling_3`;
CREATE TABLE `mydb0`.`bill_scaling_3`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
DROP TABLE IF EXISTS `mydb1`.`bill_scaling_0`;
CREATE TABLE `mydb1`.`bill_scaling_0`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
DROP TABLE IF EXISTS `mydb1`.`bill_scaling_1`;
CREATE TABLE `mydb1`.`bill_scaling_1`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
DROP TABLE IF EXISTS `mydb1`.`bill_scaling_2`;
CREATE TABLE `mydb1`.`bill_scaling_2`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
DROP TABLE IF EXISTS `mydb1`.`bill_scaling_3`;
CREATE TABLE `mydb1`.`bill_scaling_3`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`bill_name` varchar(255) NOT NULL DEFAULT '' COMMENT '账单名称',
`bill_amount` int unsigned NOT NULL DEFAULT '0' COMMENT '账单金额',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`is_delete` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0:未删除 1:已删除',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
mydb0,mydb1中分别创建了4张bill_scaling表,即bill_scaling_$->{id%4}
3.配置Sharding-Proxy
由于存在非分库分表的表无法正确查询的bug(具体参考ShardingSphere应用专题–4.1.1版本–Sharding-Proxy的使用(十六)-表名附带库名,非分库分表逻辑表路由错误的bug,这里使用Proxy的源码进行配置,方便使用Navicat查看数据
(1)配置文件
(a)server.yaml
orchestration:
registry:
orchestrationType: registry_center,config_center,distributed_lock_manager
instanceType: zookeeper
serverLists: 127.0.0.1:2181
namespace: sharding-namespace
props:
overwrite: true
retryIntervalMilliseconds: 500
timeToLiveSeconds: 60
maxRetries: 3
operationTimeoutMilliseconds: 500
authentication:
users:
root:
password: root
sharding:
password: sharding
authorizedSchemas: sharding_db
props:
max.connections.size.per.query: 1
acceptor.size: 16 # The default value is available processors count * 2.
executor.size: 16 # Infinite by default.
proxy.frontend.flush.threshold: 128 # The default value is 128.
# LOCAL: Proxy will run with LOCAL transaction.
# XA: Proxy will run with XA transaction.
# BASE: Proxy will run with B.A.S.E transaction.
proxy.transaction.type: LOCAL
proxy.opentracing.enabled: false
proxy.hint.enabled: false
query.with.cipher.column: false
sql.show: true
allow.range.query.with.inline.sharding: false
(b)config-sharding.yaml
schemaName: scaling_db
dataSources:
ds_0:
url: jdbc:mysql://127.0.0.1:4406/mydb0?serverTimezone=UTC&useSSL=false
username: root
password: 111
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
ds_1:
url: jdbc:mysql://127.0.0.1:4406/mydb1?serverTimezone=UTC&useSSL=false
username: root
password: 111
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
shardingRule:
tables:
bill:
actualDataNodes: ds_$->{0..1}.bill_scaling_$->{0..3}
tableStrategy:
inline:
shardingColumn: id
algorithmExpression: bill_scaling_$->{Long.parseLong(String.valueOf(id)) % 4}
keyGenerator:
type: SNOWFLAKE
column: id
databaseStrategy:
inline:
shardingColumn: bill_amount
algorithmExpression: ds_$->{bill_amount % 2}
defaultDataSourceName: ds_0
- 这样,就按照要求使用mydb0,mydb1两个库,表名更改为bill_scaling,每个库按id%4分成了4个表
- 注意这里的id要配成Long.parseLong(String.valueOf(id))的形式,具体为什么参考ShardingSphere应用专题–4.1.1版本–Sharding-Proxy的使用(十六)–类型不兼容bug
(c)Sharding-UI确认配置
配置中心已经获取目标数据源的配置
(d)确认proxy的配置正确
通过navicat 链接proxy,测试
- 插入
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (1, 'test-bill', 106, '2020-12-27 13:14:00', 0);
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (2, 'test-bill', 107, '2020-12-27 13:14:00', 0);
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (3, 'test-bill', 108, '2020-12-27 13:14:00', 0);
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (4, 'test-bill', 109, '2020-12-27 13:14:00', 0);
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (5, 'test-bill', 110, '2020-12-27 13:14:00', 0);
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (6, 'test-bill', 111, '2020-12-27 13:14:00', 0);
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (7, 'test-bill', 112, '2020-12-27 13:14:00', 0);
INSERT INTO `bill`(`id`, `bill_name`, `bill_amount`, `create_time`, `is_delete`) VALUES (8, 'test-bill', 113, '2020-12-27 13:14:00', 0);
分库分表逻辑也正确。ok,新的proxy配置没问题。删除测试数据
4.启动Shading-Scaling
具体的启动参考官方文档,这里就不说了
5.使用Sharding-UI创建弹性伸缩任务
- 配置项的名字叫Source,即源数据的Schema.这样,源数据的配置中心数据就指定了
- 这个用户名及密码是配置的proxy的用户名密码
- 配置的时proxy的jdbc链接地址
- 同时启动的任务数,如果数据量比较大,增加这个参数会提高并行度
源数据的配置,通过Source配置项拿到了,目标数据配置,Proxy中定义了,这样,
- Proxy就可以通过配置获取到源数据源的数据,然后将数据通过目标数据源配置,正确的写入到目标数据源
- 对于增量数据,proxy使用了mysql的同步协议拉取源数据的binlog.获取到增量数据后,和上面一样写入到目标数据源
点击确认:
出现问题了
1.scaling 使用Integer接收id的bug
目前数据库中是我使用系统自带的雪花算法生成的id,结果:
毕竟是alpha项目,算了,也懒得改了,就直接更换我的老数据,使用int的数据
再次配置并确认:
ok,历史数据同步显示正确,再看看数据库
数据没有问题,历史数据确认ok了
2.同步的serviceId写死的bug
这个时候,观察scaling的日志发现下面这个问题:
Sharding Scaling增量同步阶段,会通过binlog的方式同步增量数据,而mysql有个机制,在slave订阅masterbinlog的时候,需要告诉master自己的service_uuid/service_id,且同一个master下,不同订阅者的service_uuid/service_id不能相同,否则后者相同的slave将无法正常建立主从同步。
-service_uuid是mysql初始化时生成的一串uuid,且生成后写入auto.conf,除非复制了已经启动的mysql,此时会导致uuid一样(我应该不是这种)
-service_id这个是协议中规定的,这个就要看scaling的参数传得是个啥了
查看源码,scaling在建立主从同步时,使用的serviceId是写死的12345(!!!)
好吧,修改源码,使用不同的id就好,使用源码启动。问题得到解决,增量同步也ok。圆满完成,后面的数据就不贴出来了
毕竟是alpha版本,就忍忍吧。当然,我遇到这个问题是因为,我定义的mydb0,mydb1在同一个数据库,如果分处在不同的数据库,这里写死的也不会出现这个问题。
6.规则切换阶段
通过设置数据库只读或ShardingSphere的熔断机制,让旧数据节点中的数据短暂静态,确保增量同步已完全完成。
这个窗口期时间短则数秒,长则数分钟,取决于数据量和用户是否需要对数据进行强校验。 确认完成后,Apache ShardingSphere 可通过配置中心修改配置,将业务导向新规则的集群,弹性伸缩完成