demo地址:https://gitee.com/luci-fast/mybatis-plus-sharding
最近进行技术重构,考虑到服务拆分与分表分库,首先考虑的是mycat,毕竟mycat是代理,对于代码方面来说,能做到零侵入。
但是了解了一下发现mycat社区活跃度,与一些数据分区难点的解决方案,个人觉得都不是很理想,就又看了sharding sphere.
sharding sphere首先是轻量级的,而且对于java来说,只是多集成jar包,对jdbc,jpa,mybatis都支持,社区活跃度还高,便沉下心来看了几天,
发现代码侵入性真的很低很低,因此把这次集成过程记录下来
**组件版本:**
springboot 2.1.1
mysql5.7
mybatis-plus 国人开发的mybatis增强工具,集成使用简单,一直关注着,很赞的组件。链接:https://mp.baomidou.com/
shardingsphere jdbc 4.0.4-RC1 4.x版本是加入apache组织后的发布版本,有很多改动。
shardingsphere proxy 最新版本 用于开发运维工具,分表分库以后,对于数据的查询我们很难像单库单表一样迅速定位,简单来说就是,
使用navicat连接 shardingsphere proxy服务,这样就可以很方便直观的操作数据库,因为官方也介绍proxy性能损耗大,适合做运维工具。
shardingsphere官方链接: https://shardingsphere.apache.org/
**集成过程**
- 1.分库分表依据
因为架构是基于微服务的,所以每个服务有自己的数据库,根据每个服务的未来理想化的数据进行预估。每张表量在千万以下。
比如:订单,预计一千亿数据,那么就分为32库,每个库72张表,最终一共两千多张表,一千亿数据平均下来,每张表只有几百万。
接下来 我们以订单相关的几张表为例,进行分表分库:订单表 订单明细表 订单字典表 订单子表(一些业务可能会有)
- 2.分片相关概念
逻辑表- 业务逻辑表。 例如:订单表o_order 订单明细表o_order_item 因为量大 都需要分库
真实表- 真实的数据分片表。 例如:o_order23
数据节点- 数据分片的最小单元。由数据源名称和数据表组成。例如: odb1.o_order23
绑定表- 具有数据关联的表,建立绑定关系,则在一个库下,可以进行join。 例如:订单数据与订单明细,如果在一个库下,则可以join查询。
广播表- 一些静态信息表。 广播分发到每一个数据源,方便查询join。例如:订单字典表。
不分片表- 不需要分片的表。 例如:订单子表,一些业务订单,可能拆分成子订单,但这种情况少之又少,数据很小,无需分表分库。
分片键- 进行分表分库的字段。 例如:根据商户id进行分库,根据订单id进行分表。
分片算法- 例如:商户id取模36得到数据源 订单id取模72得到表
分片策略- sphere提供了几种策略,这里采用最简单的行表达式
分布式id- 使用内置的雪花算法,但是workid没有想好在docker环境下如何处理
分布式事务- 使用内置xa支持
sql支持与不支持- 请查看官方文档
路由原理- 请查看官方文档 尤其是带不带分片键的区别
以上概念均可在官方文档查看。接下来进行实际操作,实现对订单与订单明细表的36库72表
- 3.创建数据库
在mysql创建37个数据库 其中odb为默认数据源 存放不需要分片的表
CREATE DATABASE `odb` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb0` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb1` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb2` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb3` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb4` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb5` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb6` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb7` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb8` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb9` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb10` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb11` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb12` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb13` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb14` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb15` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb16` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb17` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb18` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb19` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb20` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb21` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb22` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb23` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb24` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb25` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb26` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb27` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb28` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb29` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb30` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb31` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb32` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb33` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb34` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `odb35` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
设置最大连接数
set GLOBAL max_connections=1000;
- 4.导入pom
其它包省略
- 5.建立sharding规则
spring:
shardingsphere:
props:
# 打印sql
sql.show: true
check:
table:
metadata:
# 是否在启动时检查分表元数据一致性
enabled: false
datasource:
# 数据源汇总
names: odb,odb0,odb1,odb2,odb3,odb4,odb5,odb6,odb7,odb8,odb9,odb10,odb11,odb12,odb13,odb14,odb15,odb16,odb17,odb18,odb19,odb20,odb21,odb22,odb23,odb24,odb25,odb26,odb27,odb28,odb29,odb30,odb31,odb32,odb33,odb34,odb35
# 真实数据源连接
odb:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/odb?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT&allowPublicKeyRetrieval=true
username: root
password: root
odb0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/odb0
username: root
password: root
# 其中省略 1-34
odb35:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/odb35
username: root
password: root
# 分片规则
sharding:
# 默认数据源
default-data-source-name: odb
# 默认主键生成策略
default-key-generator:
type: SNOWFLAKE
column: id
worker:
id: order # 雪花算法workid 还没想好多实例docker情况下如何配置
max:
tolerate:
time:
difference:
milliseconds: 1
# 默认分库策略 根据门店id 取模 这样虽然做不到数据平均分摊,但是每个门店的订单都统一在一个库下,方便join。
default-database-strategy:
inline:
shardingColumn: shop_id
algorithmExpression: odb$->{shop_id % 36}
# 广播表
broadcastTables:
- o_dict
# 绑定表关系 分片策略相同的表,即可建立绑定关系
bindingTables:
- o_order,o_order_item
# 具体表策略
tables:
# 订单表 根据订单id取模
o_order:
actual-data-nodes: odb$->{0..35}.o_order$->{0..71}
table-strategy:
inline:
sharding-column: id
algorithm-expression: o_order$->{id % 72}
# 订单表详情 根据订单id取模
o_order_item:
actual-data-nodes: odb$->{0..35}.o_order_item$->{0..71}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: o_order_item$->{order_id % 72}
- 6 建表
项目中 orm框架去映射逻辑表,在执行sql时,会自动映射到真实物理表
启动服务,就可以利用mybatis执行建表语句
CREATE TABLE IF NOT EXISTS
o_order (
id BIGINT AUTO_INCREMENT,
shop_id BIGINT NOT NULL,
code VARCHAR(50),
create_time timestamp(0),
PRIMARY KEY (id)
);
DROP TABLE IF EXISTS `o_order_child`;
CREATE TABLE `o_order_child` (
`id` bigint(20) NOT NULL,
`order_id` bigint(20) NULL DEFAULT NULL,
`shop_id` bigint(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `o_dict`;
CREATE TABLE `o_dict` (
`id` bigint(20) NOT NULL,
`shop_id` bigint(20) NULL DEFAULT NULL,
`key` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE
IF NOT EXISTS o_order_item (
id BIGINT AUTO_INCREMENT,
order_id BIGINT,
shop_id BIGINT NOT NULL,
NAME VARCHAR (50),
PRIMARY KEY (id)
);
然后会发现,odb0到odb35 每个库都有72张订单表,72张订单明细表。每个库都有一张字典表。odb中有子单表。
- 6 sharding sphere proxy服务
通过官方的docker拉取服务,总是启动失败,未找到原因。因此直接下载了最新的客户端。将分片规则copy进去。就可启动。
默认端口3307 设置用户名密码 即可用navicat连接。
连接成功后,即可进行运维操作。比如建表等等。但是发现并未显示不分库的表,不过sql可以正常执行。
- 7 flyway继承
失败,因为flyway默认会读取系统表的信息,而我们的sharding时jdbc代理的逻辑表,没有真实表
- 8 读写分离
未加入,可自行根据官方文档参考加入
- 8 分库事务
@Transactional配合TransactionTypeHolder.set(TransactionType.XA);即可实现。
其实官方文档中有@ShardingTransactionType(TransactionType.LOCAL)注解,但本人未下载到相应jar包。
@Transactional(rollbackFor = Exception.class)
public void batchAdd() {
TransactionTypeHolder.set(TransactionType.XA);
for (int i=41;i<=60; i++) {
Order order = new Order();
order.setId(new Long(i));
order.setShopId(new Long(i));
order.setCode(i + "test");
orderMapper.insert(order);
if (i == 50) {
throw new RuntimeException();
}
for(int j=1;j<=10; j++) {
OrderItem orderItem = new OrderItem();
orderItem.setId(new Long((i*10)+j));
orderItem.setOrderId(order.getId());
orderItem.setName("明细 "+ j);
orderItem.setShopId(new Long(i));
itemMapper.insert(orderItem);
}
}
}
demo地址:https://gitee.com/luci-fast/mybatis-plus-sharding