目录
- 一、目标
- 二、环境准备
- 1、基本信息
- 2、数据库环境准备
- 3、建库 & 导入分表
- 三、配置&实践
- 1、pom文件
- 2、常量配置
- 3、yml 配置
- 4、分库分表策略
- 5、dao层编写
- 6、单元测试
- 四、总结
一、目标
本文将完成如下目标:
- 分表数量: 256 分库数量: 4
- 以用户id(user_id) 为数据库分片key
- 最后测试订单创建,更新,删除, 单订单号查询,根据user_id查询列表操作。
架构图:
表结构如下:
create table `order_xxx` ( `order_id` bigint(20) unsigned not null, `user_id` int(11) default '0' comment '订单id', `status` int(11) default '0' comment '订单状态', `booking_date` datetime default null, `create_time` datetime default null, `update_time` datetime default null, primary key (`order_id`), key `idx_user_id` (`user_id`), key `idx_bdate` (`booking_date`), key `idx_ctime` (`create_time`), key `idx_utime` (`update_time`) ) engine=innodb default charset=utf8;
注: 000<= xxx <= 255, 本文重点在于分库分表实践, 只保留具有代表性字段,其它场景可以在此基础上做改进。
全局唯一id设计
要求:1.全局唯一 2:粗略有序 3:可反解出库编号
- 1bit + 39bit时间差 + 8bit机器号 + 8bit用户编号(库号) + 8bit自增序列
| 订单号组成项 | 保留字段 | 毫秒级时间差 | 机器数 | 用户编号(表编号) | 自增序列 |
|---|---|---|---|---|---|
| 所占字节(单位bit) | 1 | 39 | 8 | 8 | 8 |
单机最大qps: 256000 使用寿命: 17年
二、环境准备
1、基本信息
| 项 | 版本 | 备注 |
|---|---|---|
| springboot | 2.1.10.release | |
| mango | 1.6.16 | wiki地址:https://github.com/jfaster/mango |
| hikaricp | 3.2.0 | |
| mysql | 5.7 | 测试使用docker一键搭建 |
2、数据库环境准备
进入mysql:
#主库 mysql -h 172.30.1.21 -uroot -pbytearch #从库 mysql -h 172.30.1.31 -uroot -pbytearch
进入容器
#主 docker exec -it db_1_master /bin/bash #从 docker exec -it db_1_slave /bin/bash
查看运行状态
#主 docker exec db_1_master sh -c 'mysql -u root -pbytearch -e "show master status \g"' #从 docker exec db_1_slave sh -c 'mysql -u root -pbytearch -e "show slave status \g"'
3、建库 & 导入分表
(1)在mysql master实例分别建库
172.30.1.21( o rder_db_ 1) , 172.30.1.22( order_db_2) ,
172.30.1.23( ord er_db_3) , 172.30.1.24( order_db_4 )
(2)依次导入建表sql 命令为
mysql -uroot -pbytearch -h172.30.1.21 order_db_1<fast-cloud-mysql-sharding/doc/sql/order_db_1.sql; mysql -uroot -pbytearch -h172.30.1.22 order_db_2<fast-cloud-mysql-sharding/doc/sql/order_db_2.sql; mysql -uroot -pbytearch -h172.30.1.23 order_db_3<fast-cloud-mysql-sharding/doc/sql/order_db_3.sql; mysql -uroot -pbytearch -h172.30.1.24 order_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、常量配置
package com.bytearch.fast.cloud.mysql.sharding.common;
/**
* 分库分表策略常用常量
*/
public class shardingstrategyconstant {
/**
* database 逻辑名称 ,真实库名为 order_db_xxx
*/
public static final string logic_order_database_name = "order_db";
/**
* 分表数 256,一旦确定不可更改
*/
public static final int sharding_table_num = 256;
/**
* 分库数, 不建议更改, 可以更改,但是需要dba迁移数据
*/
public static final int sharding_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分库分表策略
package com.bytearch.fast.cloud.mysql.sharding.strategy;
import com.bytearch.fast.cloud.mysql.sharding.common.shardingstrategyconstant;
import com.bytearch.id.generator.identity;
import com.bytearch.id.generator.seqidutil;
import org.jfaster.mango.sharding.shardingstrategy;
/**
* 订单号分库分表策略
*/
public class orderidshardingstrategy implements shardingstrategy<long, long> {
@override
public string getdatasourcefactoryname(long orderid) {
if (orderid == null || orderid < 0l) {
throw new illegalargumentexception("order_id is invalid!");
}
identity identity = seqidutil.decodeid(orderid);
if (identity.getextraid() >= shardingstrategyconstant.sharding_table_num) {
throw new illegalargumentexception("sharding table num is invalid, tablenum:" + identity.getextraid());
}
//1. 计算步长
int step = shardingstrategyconstant.sharding_table_num / shardingstrategyconstant.sharding_database_node_num;
//2. 计算出库编号
long dbno = math.floordiv(identity.getextraid(), step) + 1;
//3. 返回数据源名
return string.format("%s_%s", shardingstrategyconstant.logic_order_database_name, dbno);
}
@override
public string gettargettable(string logictablename, long orderid) {
if (orderid == null || orderid < 0l) {
throw new illegalargumentexception("order_id is invalid!");
}
identity identity = seqidutil.decodeid(orderid);
if (identity.getextraid() >= shardingstrategyconstant.sharding_table_num) {
throw new illegalargumentexception("sharding table num is invalid, tablenum:" + identity.getextraid());
}
// 基于约定,真实表名为 logictablename_xxx, xxx不足三位补0
return string.format("%s_%03d", logictablename, identity.getextraid());
}
}
2). 根据user_id 为shardkey分库分表策略
package com.bytearch.fast.cloud.mysql.sharding.strategy;
import com.bytearch.fast.cloud.mysql.sharding.common.shardingstrategyconstant;
import org.jfaster.mango.sharding.shardingstrategy;
/**
* 指定分片key 分库分表策略
*/
public class useridshardingstrategy implements shardingstrategy<integer, integer> {
@override
public string getdatasourcefactoryname(integer userid) {
//1. 计算步长 即单库放得表数量
int step = shardingstrategyconstant.sharding_table_num / shardingstrategyconstant.sharding_database_node_num;
//2. 计算出库编号
long dbno = math.floordiv(userid % shardingstrategyconstant.sharding_table_num, step) + 1;
//3. 返回数据源名
return string.format("%s_%s", shardingstrategyconstant.logic_order_database_name, dbno);
}
@override
public string gettargettable(string logictablename, integer userid) {
// 基于约定,真实表名为 logictablename_xxx, xxx不足三位补0
return string.format("%s_%03d", logictablename, userid % shardingstrategyconstant.sharding_table_num);
}
}
5、dao层编写
1). orderpartitionbyiddao
package com.bytearch.fast.cloud.mysql.sharding.dao;
import com.bytearch.fast.cloud.mysql.sharding.common.shardingstrategyconstant;
import com.bytearch.fast.cloud.mysql.sharding.pojo.entity.orderentity;
import com.bytearch.fast.cloud.mysql.sharding.strategy.orderidshardingstrategy;
import org.jfaster.mango.annotation.*;
@db(name = shardingstrategyconstant.logic_order_database_name, table = "order")
@sharding(shardingstrategy = orderidshardingstrategy.class)
public interface orderpartitionbyiddao {
@sql("insert into #table (order_id, user_id, status, booking_date, create_time, update_time) values" +
"(:orderid,:userid,:status,:bookingdate,:createtime,:updatetime)"
)
int insertorder(@tableshardingby("orderid") @databaseshardingby("orderid") orderentity orderentity);
@sql("update #table set update_time = now()" +
"#if(:bookingdate != null),booking_date = :bookingdate #end " +
"#if (:status != null), status = :status #end" +
"where order_id = :orderid"
)
int updateorderbyorderid(@tableshardingby("orderid") @databaseshardingby("orderid") orderentity orderentity);
@sql("select * from #table where order_id = :1")
orderentity getorderbyid(@tableshardingby @databaseshardingby long orderid);
@sql("select * from #table where order_id = :1")
@usemaster
orderentity getorderbyidfrommaster(@tableshardingby @databaseshardingby long orderid);
6、单元测试
@springboottest(classes = {application.class})
@runwith(springjunit4classrunner.class)
public class shardingtest {
@autowired
orderpartitionbyiddao orderpartitionbyiddao;
@autowired
orderpartitionbyuseriddao orderpartitionbyuseriddao;
@test
public void testcreateorderrandom() {
for (int i = 0; i < 20; i++) {
int userid = threadlocalrandom.current().nextint(1000,1000000);
orderentity orderentity = new orderentity();
orderentity.setorderid(seqidutil.nextid(userid % shardingstrategyconstant.sharding_table_num));
orderentity.setstatus(1);
orderentity.setuserid(userid);
orderentity.setcreatetime(new date());
orderentity.setupdatetime(new date());
orderentity.setbookingdate(new date());
int ret = orderpartitionbyiddao.insertorder(orderentity);
assert.assertequals(1, ret);
}
}
@test
public void testorderall() {
//insert
int userid = threadlocalrandom.current().nextint(1000,1000000);
orderentity orderentity = new orderentity();
orderentity.setorderid(seqidutil.nextid(userid % shardingstrategyconstant.sharding_table_num));
orderentity.setstatus(1);
orderentity.setuserid(userid);
orderentity.setcreatetime(new date());
orderentity.setupdatetime(new date());
orderentity.setbookingdate(new date());
int i = orderpartitionbyiddao.insertorder(orderentity);
assert.assertequals(1, i);
//get from master
orderentity orderinfo = orderpartitionbyiddao.getorderbyidfrommaster(orderentity.getorderid());
assert.assertnotnull(orderinfo);
assert.assertequals(orderinfo.getorderid(), orderentity.getorderid());
//get from slave
orderentity slaveorderinfo = orderpartitionbyiddao.getorderbyid(orderentity.getorderid());
assert.assertnotnull(slaveorderinfo);
//update
orderentity updateentity = new orderentity();
updateentity.setorderid(orderinfo.getorderid());
updateentity.setstatus(2);
updateentity.setupdatetime(new date());
int affectrows = orderpartitionbyiddao.updateorderbyorderid(updateentity);
assert.asserttrue( affectrows > 0);
}
@test
public void testgetlistbyuserid() {
int userid = threadlocalrandom.current().nextint(1000,1000000);
for (int i = 0; i < 5; i++) {
orderentity orderentity = new orderentity();
orderentity.setorderid(seqidutil.nextid(userid % shardingstrategyconstant.sharding_table_num));
orderentity.setstatus(1);
orderentity.setuserid(userid);
orderentity.setcreatetime(new date());
orderentity.setupdatetime(new date());
orderentity.setbookingdate(new date());
orderpartitionbyiddao.insertorder(orderentity);
}
try {
//防止主从延迟引起的校验错误
thread.sleep(1000);
} catch (interruptedexception e) {
e.printstacktrace();
}
list<orderentity> orderlistbyuserid = orderpartitionbyuseriddao.getorderlistbyuserid(userid);
assert.assertnotnull(orderlistbyuserid);
assert.asserttrue(orderlistbyuserid.size() == 5);
}
}
大功告成:
四、总结
本篇主要介绍java版使用mango框架实现mysql分库分表实战,分库分表中间件也可以使用类似于shardingjdbc,或者自研。
以上分库分表数量仅供演示参考,实际工作中分表数量、分库数量、是根据公司实际业务数据增长速度, 高峰期qps,物理机器配置等等因素计算。
到此这篇关于浅谈订单重构之 mysql 分库分表实战篇的文章就介绍到这了,更多相关mysql 分库分表内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!