2022-10-27 400
发布上篇文章浅谈订单重构之路之后,有很多小伙伴想知道,分库分表具体是如何实现的。那么这篇文章具体介绍下,分库分表实战。
本文将完成如下目标:
* 分表数量: 256 分库数量: 4
* 以用户ID(user_id) 为数据库分片Key
* 最后测试订单创建,更新,删除, 单订单号查询,根据user_id查询列表操作。
架构图:
表结构如下:
CREATETABLE`order_XXX`( `order_id`bigint(20)unsignedNOTNULL, `user_id`int(11)DEFAULT'0'COMMENT'订单id', `status`int(11)DEFAULT'0'COMMENT'订单状态', `booking_date`datetimeDEFAULTNULL, `create_time`datetimeDEFAULTNULL, `update_time`datetimeDEFAULTNULL, PRIMARYKEY(`order_id`), KEY`idx_user_id`(`user_id`), KEY`idx_bdate`(`booking_date`), KEY`idx_ctime`(`create_time`), KEY`idx_utime`(`update_time`) )ENGINE=InnoDBDEFAULTCHARSET=utf8;
注: 000<= XXX <= 255, 本文重点在于分库分表实践, 只保留具有代表性字段,其它场景可以在此基础上做改进。
全局唯一ID设计
要求:1.全局唯一 2:粗略有序 3:可反解出库编号
订单号组成项 | 保留字段 | 毫秒级时间差 | 机器数 | 用户编号(表编号) | 自增序列 |
---|---|---|---|---|---|
所占字节(单位bit) | 1 | 39 | 8 | 8 | 8 |
单机最大QPS: 256000 使用寿命: 17年
订单号生成规则说明详见浅谈分布式唯一Id生成器之最佳实践
1. 基本信息
2. 数据库环境准备
温馨提示:使用docker-compose快速搭建了4主4从数据库集群,实现本地快速一键部署,生产环境一般由DBA同学搭建。
具体实现请移步查看:https://gitee.com/bytearch_admin/docker-app/tree/main/mysql-cluster
3. 建库 & 导入分表
* 在mysql master实例分别建库
172.30.1.21(order_db_1),172.30.1.22(order_db_2),
172.30.1.23(order_db_3), 172.30.1.24(order_db_4)
* 依次导入建表SQL命令为
mysql-uroot-pbytearch-h172.30.1.21order_db_1<fast-cloud-mysql-sharding/doc/sql/order_db_1.sql; mysql-uroot-pbytearch-h172.30.1.22order_db_2<fast-cloud-mysql-sharding/doc/sql/order_db_2.sql; mysql-uroot-pbytearch-h172.30.1.23order_db_3<fast-cloud-mysql-sharding/doc/sql/order_db_3.sql; mysql-uroot-pbytearch-h172.30.1.24order_db_4<fast-cloud-mysql-sharding/doc/sql/order_db_4.sql;
1. pom文件
<!--mango分库分表中间件--> <dependency> <groupId>org.jfaster</groupId> <artifactId>mango-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <!--分布式ID生成器--> <dependency> <groupId>com.bytearch</groupId> <artifactId>fast-cloud-id-generator</artifactId> <version>${version}</version> </dependency> <!--https://mvnrepository.com/artifact/mysql/mysql-connector-java--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency>
2. 常量配置
packagecom.bytearch.fast.cloud.mysql.sharding.common; /** *分库分表策略常用常量 */ publicclassShardingStrategyConstant{ /** *database逻辑名称,真实库名为order_db_XXX */ publicstaticfinalStringLOGIC_ORDER_DATABASE_NAME="order_db"; /** *分表数256,一旦确定不可更改 */ publicstaticfinalintSHARDING_TABLE_NUM=256; /** *分库数,不建议更改,可以更改,但是需要DBA迁移数据 */ publicstaticfinalintSHARDING_DATABASE_NODE_NUM=4; }
3. yml 配置
4主4从数据库配置, 这里仅测试默认使用root用户密码,生产环境不建议使用root用户。
mango: scan-package:com.bytearch.fast.cloud.mysql.sharding.dao datasources: -name:order_db_1 master: driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.21:3306/order_db_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:3000 slaves: -driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.31:3306/order_db_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:3000 -name:order_db_2 master: driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.22:3306/order_db_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:3000 slaves: -driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.32:3306/order_db_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:3000 -name:order_db_3 master: driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.23:3306/order_db_3?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:3000 slaves: -driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.33:3306/order_db_3?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:3000 -name:order_db_4 master: driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.24:3306/order_db_4?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:3000 slaves: -driver-class-name:com.mysql.cj.jdbc.Driver jdbc-url:jdbc:mysql://172.30.1.34:3306/order_db_4?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false user-name:root password:bytearch maximum-pool-size:10 connection-timeout:300
4. 分库分表策略
1). 根据order_id为shardKey分库分表策略
packagecom.bytearch.fast.cloud.mysql.sharding.strategy; importcom.bytearch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant; importcom.bytearch.id.generator.IdEntity; importcom.bytearch.id.generator.SeqIdUtil; importorg.jfaster.mango.sharding.ShardingStrategy; /** *订单号分库分表策略 */ publicclassOrderIdShardingStrategyimplementsShardingStrategy<Long,Long>{ @Override publicStringgetDataSourceFactoryName(LongorderId){ if(orderId==null||orderId<0L){ thrownewIllegalArgumentException("order_idisinvalid!"); } IdEntityidEntity=SeqIdUtil.decodeId(orderId); if(idEntity.getExtraId()>=ShardingStrategyConstant.SHARDING_TABLE_NUM){ thrownewIllegalArgumentException("shardingtableNumisinvalid,tableNum:"+idEntity.getExtraId()); } //1.计算步长 intstep=ShardingStrategyConstant.SHARDING_TABLE_NUM/ShardingStrategyConstant.SHARDING_DATABASE_NODE_NUM; //2.计算出库编号 longdbNo=Math.floorDiv(idEntity.getExtraId(),step)+1; //3.返回数据源名 returnString.format("%s_%s",ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME,dbNo); } @Override publicStringgetTargetTable(StringlogicTableName,LongorderId){ if(orderId==null||orderId<0L){ thrownewIllegalArgumentException("order_idisinvalid!"); } IdEntityidEntity=SeqIdUtil.decodeId(orderId); if(idEntity.getExtraId()>=ShardingStrategyConstant.SHARDING_TABLE_NUM){ thrownewIllegalArgumentException("shardingtableNumisinvalid,tableNum:"+idEntity.getExtraId()); } //基于约定,真实表名为logicTableName_XXX,XXX不足三位补0 returnString.format("%s_%03d",logicTableName,idEntity.getExtraId()); } }
2). 根据user_id 为shardKey分库分表策略
packagecom.bytearch.fast.cloud.mysql.sharding.strategy; importcom.bytearch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant; importorg.jfaster.mango.sharding.ShardingStrategy; /** *指定分片KEY分库分表策略 */ publicclassUserIdShardingStrategyimplementsShardingStrategy<Integer,Integer>{ @Override publicStringgetDataSourceFactoryName(IntegeruserId){ //1.计算步长即单库放得表数量 intstep=ShardingStrategyConstant.SHARDING_TABLE_NUM/ShardingStrategyConstant.SHARDING_DATABASE_NODE_NUM; //2.计算出库编号 longdbNo=Math.floorDiv(userId%ShardingStrategyConstant.SHARDING_TABLE_NUM,step)+1; //3.返回数据源名 returnString.format("%s_%s",ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME,dbNo); } @Override publicStringgetTargetTable(StringlogicTableName,IntegeruserId){ //基于约定,真实表名为logicTableName_XXX,XXX不足三位补0 returnString.format("%s_%03d",logicTableName,userId%ShardingStrategyConstant.SHARDING_TABLE_NUM); } }
5. dao层编写
1). OrderPartitionByIdDao
packagecom.bytearch.fast.cloud.mysql.sharding.dao; importcom.bytearch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant; importcom.bytearch.fast.cloud.mysql.sharding.pojo.entity.OrderEntity; importcom.bytearch.fast.cloud.mysql.sharding.strategy.OrderIdShardingStrategy; importorg.jfaster.mango.annotation.*; @DB(name=ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME,table="order") @Sharding(shardingStrategy=OrderIdShardingStrategy.class) publicinterfaceOrderPartitionByIdDao{ @SQL("INSERTINTO#table(order_id,user_id,status,booking_date,create_time,update_time)VALUES"+ "(:orderId,:userId,:status,:bookingDate,:createTime,:updateTime)" ) intinsertOrder(@TableShardingBy("orderId")@DatabaseShardingBy("orderId")OrderEntityorderEntity); @SQL("UPDATE#tablesetupdate_time=now()"+ "#if(:bookingDate!=null),booking_date=:bookingDate#end"+ "#if(:status!=null),status=:status#end"+ "WHEREorder_id=:orderId" ) intupdateOrderByOrderId(@TableShardingBy("orderId")@DatabaseShardingBy("orderId")OrderEntityorderEntity); @SQL("SELECT*FROM#tableWHEREorder_id=:1") OrderEntitygetOrderById(@TableShardingBy@DatabaseShardingByLongorderId); @SQL("SELECT*FROM#tableWHEREorder_id=:1") @UseMaster OrderEntitygetOrderByIdFromMaster(@TableShardingBy@DatabaseShardingByLongorderId);
6. 单元测试
@SpringBootTest(classes={Application.class}) @RunWith(SpringJUnit4ClassRunner.class) publicclassShardingTest{ @Autowired OrderPartitionByIdDaoorderPartitionByIdDao; @Autowired OrderPartitionByUserIdDaoorderPartitionByUserIdDao; @Test publicvoidtestCreateOrderRandom(){ for(inti=0;i<20;i++){ intuserId=ThreadLocalRandom.current().nextInt(1000,1000000); OrderEntityorderEntity=newOrderEntity(); orderEntity.setOrderId(SeqIdUtil.nextId(userId%ShardingStrategyConstant.SHARDING_TABLE_NUM)); orderEntity.setStatus(1); orderEntity.setUserId(userId); orderEntity.setCreateTime(newDate()); orderEntity.setUpdateTime(newDate()); orderEntity.setBookingDate(newDate()); intret=orderPartitionByIdDao.insertOrder(orderEntity); Assert.assertEquals(1,ret); } } @Test publicvoidtestOrderAll(){ //insert intuserId=ThreadLocalRandom.current().nextInt(1000,1000000); OrderEntityorderEntity=newOrderEntity(); orderEntity.setOrderId(SeqIdUtil.nextId(userId%ShardingStrategyConstant.SHARDING_TABLE_NUM)); orderEntity.setStatus(1); orderEntity.setUserId(userId); orderEntity.setCreateTime(newDate()); orderEntity.setUpdateTime(newDate()); orderEntity.setBookingDate(newDate()); inti=orderPartitionByIdDao.insertOrder(orderEntity); Assert.assertEquals(1,i); //getfrommaster OrderEntityorderInfo=orderPartitionByIdDao.getOrderByIdFromMaster(orderEntity.getOrderId()); Assert.assertNotNull(orderInfo); Assert.assertEquals(orderInfo.getOrderId(),orderEntity.getOrderId()); //getfromslave OrderEntityslaveOrderInfo=orderPartitionByIdDao.getOrderById(orderEntity.getOrderId()); Assert.assertNotNull(slaveOrderInfo); //update OrderEntityupdateEntity=newOrderEntity(); updateEntity.setOrderId(orderInfo.getOrderId()); updateEntity.setStatus(2); updateEntity.setUpdateTime(newDate()); intaffectRows=orderPartitionByIdDao.updateOrderByOrderId(updateEntity); Assert.assertTrue(affectRows>0); } @Test publicvoidtestGetListByUserId(){ intuserId=ThreadLocalRandom.current().nextInt(1000,1000000); for(inti=0;i<5;i++){ OrderEntityorderEntity=newOrderEntity(); orderEntity.setOrderId(SeqIdUtil.nextId(userId%ShardingStrategyConstant.SHARDING_TABLE_NUM)); orderEntity.setStatus(1); orderEntity.setUserId(userId); orderEntity.setCreateTime(newDate()); orderEntity.setUpdateTime(newDate()); orderEntity.setBookingDate(newDate()); orderPartitionByIdDao.insertOrder(orderEntity); } try{ //防止主从延迟引起的校验错误 Thread.sleep(1000); }catch(InterruptedExceptione){ e.printStackTrace(); } List<OrderEntity>orderListByUserId=orderPartitionByUserIdDao.getOrderListByUserId(userId); Assert.assertNotNull(orderListByUserId); Assert.assertTrue(orderListByUserId.size()==5); } }
大功告成:
以上源码已开源至: https://gitee.com/bytearch_admin/fast-cloud/tree/master/fast-cloud-mysql-sharding 欢迎点赞收藏。
本篇主要介绍Java版使用Mango框架实现Mysql分库分表实战,分库分表中间件也可以使用类似于ShardingJDBC,或者自研。
以上分库分表数量仅供演示参考,实际工作中分表数量、分库数量、是根据公司实际业务数据增长速度, 高峰期QPS,物理机器配置等等因素计算。
原文链接:https://77isp.com/post/10479.html
=========================================
https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。
数据库技术 2022-03-28
网站技术 2022-11-26
网站技术 2023-01-07
网站技术 2022-11-17
Windows相关 2022-02-23
网站技术 2023-01-14
Windows相关 2022-02-16
Windows相关 2022-02-16
Linux相关 2022-02-27
数据库技术 2022-02-20
抠敌 2023年10月23日
嚼餐 2023年10月23日
男忌 2023年10月22日
瓮仆 2023年10月22日
簿偌 2023年10月22日
扫码二维码
获取最新动态