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
org.apache.shardingsphere sharding-jdbc-spring-boot-starter 4.0.0-RC1 org.apache.shardingsphere sharding-jdbc-spring-namespace 4.0.0-RC1 -
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