目录
前言
分库分表
现成分库分表框架
Sharding-JDBC
简介
架构图
个人看法
概念
数据源
分片键
注意要点
分片策略
pom.xml
项目
运行输出
个人思考
数据拆分的方式
个人总结
1.取模
2.范围方案
文中总结
分布式id
应用
资料
Springboot整合Sharding-Jdbc
pom.xml
配置
Mapper.xml
自定义分片策略
github
分库配置
当数据表的数据量达到百万,千万级别的时候,性能是比较差的。这个时候我们需要对它进行分库分表。
分库:数据量很多,分成多个数据库储存
分表:储存到多个表中
参考这一篇
功能 | Cobar | Mycat | Heisenberg | TDDL | Sharding-JDBC |
---|---|---|---|---|---|
是否开源 | 开源 | 开源 | 开源 | 部分开源 | 开源 |
架构模型 | Proxy架构 | Proxy架构 | Proxy架构 | 应用集成架构 | 应用集成架构 |
数据库支持 | MySQL | 任意 | 任意 | 任意 | MySQL(计划Oracle) |
外围依赖 | 无 | 无 | 无 | Diamond | 无 |
使用复杂度 | 一般 | 一般 | 一般 | 复杂 | 一般 |
技术文档支持 | 较少 | 付费 | 较少 | 无 | 一般 |
开源组织 | 阿里 | 社区(Cobar衍生) | 社区(Cobar衍生) | 阿里 | 当当 |
个人接触到的就Mycat还有Sharding-JDBC
MyCat应该是应用级别上的分库分表,它还可以实现mysql集群的管理。
下面摘自其他文章
这一篇主要总结Sharding-JDBC
Sharding-JDBC是当当开源的数据库分库分表中间件。Sharding-JDBC直接封装JDBC协议,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零。Sharding-JDBC定位为轻量级java框架,使用客户端直连数据库,以jar包形式提供服务,无proxy代理层,无需额外部署,无其他依赖,DBA也无需改变原有的运维方式
从简介还有架构图中,我们可以看出sharding-jdbc是通过分片策略改写sql语句,最后进行分库分表的。不像Mycat通过应用级别去操作。
应用配置数据库的数据源,特别是分库
jdbc_driver0=com.mysql.jdbc.Driver
jdbc_url0=jdbc:mysql://localhost:3306/sharding_0?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&autoReconnect=true
jdbc_username0=root
jdbc_password0=123456
jdbc_driver1=com.mysql.jdbc.Driver
jdbc_url1=jdbc:mysql://localhost:3306/sharding_1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&autoReconnect=true
jdbc_username1=root
jdbc_password1=123456
通过分片键来匹配分库规则,从而进行改写数据库
注意:分片键的数据库类型为int(11)
yml配置
分片键要跟查询字段一致,包括大小,如果大小不匹配,会插到到所有表中
public class UserSingleKeyDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm{
/**
* sql 中关键字 匹配符为 =的时候,表的路由函数
*/
public String doEqualSharding(Collection availableTargetNames, ShardingValue shardingValue) {
for (String each : availableTargetNames) {
if (each.endsWith(shardingValue.getValue() % 2 + "")) {
return each;
}
}
throw new IllegalArgumentException();
}
/**
* sql 中关键字 匹配符为 in 的时候,表的路由函数
*/
public Collection doInSharding(Collection availableTargetNames, ShardingValue shardingValue) {
Collection result = new LinkedHashSet(availableTargetNames.size());
for (Integer value : shardingValue.getValues()) {
for (String tableName : availableTargetNames) {
if (tableName.endsWith(value % 2 + "")) {
result.add(tableName);
}
}
}
return result;
}
/**
* sql 中关键字 匹配符为 between的时候,表的路由函数
*/
public Collection doBetweenSharding(Collection availableTargetNames,
ShardingValue shardingValue) {
Collection result = new LinkedHashSet(availableTargetNames.size());
Range range = (Range) shardingValue.getValueRange();
for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
for (String each : availableTargetNames) {
if (each.endsWith(i % 2 + "")) {
result.add(each);
}
}
}
return result;
}
}
Collection
availableTargetNames:所有的资源列表,比如数据表:user_0,user_1...
shardingValue:输入的分片键的值,比如设定user_id,输入为2
通过定义分片算法来实现分片
或者Springboot配置
org.apache.shardingsphere
sharding-jdbc-core
${sharding-sphere.version}
别人的一个项目
我刚刚也修改了里面一个bug,九色分片键大小写问题导致插入所有表
可以看到他通过xml配置以及重写SingleKeyDatabaseShardingAlgorithm,我们也可以通过springboot进行配置
DEBUG MemberMapper.insert - ==> Preparing: insert into t_member (ID,NAME,STRATEGY, CARD) values (?,?,?,?)
DEBUG MemberMapper.insert - ==> Parameters: 6263c1ad-86b6-4dfa-9890-5df326662d32(String), 1(String), 3(Long), 2(String)
DEBUG parser.SQLParserFactory - Logic SQL: insert into t_member (ID,NAME,STRATEGY, CARD) values (?,?,?,?)
DEBUG parser.SQLParseEngine - Parsed SQL result: SQLParsedResult(routeContext=RouteContext(tables=[Table(name=t_member, alias=Optional.absent())], sqlBuilder=null), conditionContexts=[ConditionContext(conditions={Condition.Column(columnName=STRATEGY, tableName=t_member)=Condition(column=Condition.Column(columnName=STRATEGY, tableName=t_member), operator==, values=[3])})], mergeContext=MergeContext(orderByColumns=[], groupByColumns=[], aggregationColumns=[], limit=null))
DEBUG parser.SQLParseEngine - Parsed SQL: INSERT INTO [Token(t_member)] (ID, NAME, STRATEGY, CARD) VALUES (?, ?, ?, ?)
**************************************tableNames:[t_member_0, t_member_1, t_member_2]************************************************************
**************************************shardingValue:ShardingValue(columnName=STRATEGY, value=3, values=[], valueRange=null)************************************************************
DEBUG router.SQLExecutionUnit - route sql to db: [sharding_1] sql: [INSERT INTO t_member_0 (ID, NAME, STRATEGY, CARD) VALUES (?, ?, ?, ?)]
DEBUG MemberMapper.insert - <== Updates: 1
可以看到其中的sql解析以及改写
既然分片储存了,那么碰到跨库查询,或者跨表查询的怎么办?因为数据表数据量都很大,范围查询很慢的。(分库分表范围查询都会去查询所有表符合的数据)
那么我们需要比较好的拆分数据,范围查询时可以指定特定库进行查询。分表的时候要根据业务来拆数据,比如我是一个统计功能为主的,可以根据时间去拆,比如时间字段,数据为20190618,表示某一天,作为一个分片键。那么一天的数据都在一张表里面,我们到时可以根据时间字段直接找到数据。
缺点:存在数据热点,比如我一天数据量特别大,其他的数据量很少
如果说没有这种需求,我们可以通过id取模进行储存。
特定:数据均匀
那么他们之间怎么处理呢?看下一节数据拆分的方式
这一篇讲得很详细
包括很多id取模,时间hash之后取模
订单数据可以均匀的放到那4张表中,这样此订单进行操作时,就不会有热点问题。
比如id超过1w就跳到下一张表,或者符合什么范围就跳
我们小伙伴们想一下,此方案是不是有利于将来的扩容,不需要做数据迁移。即时再增加4张表,之前的4张表的范围不需要改变,id=12的还是在0表,id=1300万的还是在1表,新增的4张表他们的范围肯定是 大于 4000万之后的范围划分的。
有热点问题,我们想一下,因为id的值会一直递增变大,那这段时间的订单是不是会一直在某一张表中,如id=1000万 ~ id=2000万之间,这段时间产生的订单是不是都会集中到此张表中,这个就导致1表过热,压力过大,而其他的表没有什么压力。
文章最后也给了总结:先按照范围在按取模。
这样有什么好处呢?
好处:查询可以指定到特定的数据表范围,又可以避免数据热点
这个也是我们需要解决的,不可能说使用主键,网上很多方案,什么redis集群发号,雪花算法(推特 Snowflake算法实现)
可以参照下这一篇
它大概由3部分组成:
Bits 名字 说明
1 符号位 等于 0
41 时间戳 从 2016/11/01 零点开始的毫秒数,支持 2 ^41 /365/24/60/60/1000=69.7年
10 工作进程编号 支持 1024 个进程
12 序列号 每毫秒从 0 开始自增,支持 4096 个编号
我们在插入的时候要设置id为分布式唯一id
目前网上学习资料都蛮少的
https://github.com/1181888200/sharding-jdbc-study别人的一个入门项目
https://shardingsphere.apache.org/document/current/en/overview/官网
https://cloud.tencent.com/developer/article/1441250分库分表注意事项
参考尹吉欢尹大大的一篇文章
采用mybatis,好像使用jpa框架不行
io.shardingjdbc
sharding-jdbc-spring-boot-starter
2.0.0.M3
sharding.jdbc.datasource.names=sharding_0
# 数据源
sharding.jdbc.datasource.sharding_0.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.sharding_0.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.sharding_0.url=jdbc:mysql://localhost:3306/sharding_0?characterEncoding=utf-8
sharding.jdbc.datasource.sharding_0.username=root
sharding.jdbc.datasource.sharding_0.password=123456
# 分表配置
sharding.jdbc.config.sharding.tables.t_user.actual-data-nodes=sharding_0.t_user_${0..2}
#自定义分片
sharding.jdbc.config.sharding.tables.t_user.table-strategy.standard.sharding-column=user_id
sharding.jdbc.config.sharding.tables.t_user.table-strategy.standard.precise-algorithm-class-name=com.example.demo.MyPreciseShardingAlgorithm
#默认hash分片
#sharding.jdbc.config.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
#sharding.jdbc.config.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user_${user_id.longValue() % 3}
mybatis.mapperLocations=classpath:mapper/*Mapper.xml
上面已经定义了两种方式:一种是取模,一种是自定义
sharding_0库名
t_user_0,t_user_1,t_user_2表名
insert into t_user (user_id,name,age) values (#{userId},#{name},#{age})
注意:数据表名写数据表头就行,比如t_user
public class MyPreciseShardingAlgorithm implements PreciseShardingAlgorithm {
/*PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。
RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。*/
@Override
public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) {
for (String tableName : availableTargetNames) {
if (tableName.endsWith(shardingValue.getValue() % 3 + "")) {
return tableName;
}
}
throw new IllegalArgumentException();
}
}
下面摘自文章内容:
我们这边引入的Spring Boot Starter包是2.x的版本,在这个版本中,分片算法的接口有调整,我们需要用到标准分片策略StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。
StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。
PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。
https://github.com/dajitui/sharding-jdbc1
参考文章
# 根据merchant列进行分库
sharding.jdbc.config.sharding.default-database-strategy.standard.sharding-column=merchant
# 自定义分库算法
sharding.jdbc.config.sharding.default-database-strategy.standard.precise-algorithm-class-name=com.afei.boot.util.DbShardingAlgorithm
sharding.jdbc.config.props.sql.show=true