您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

分库分表简单?那我想问如何实现“分库分表插件”?

时间:2020-03-23 10:45:56  来源:  作者:

前言

随着系统数据量的日益增长,在说起数据库架构和数据库优化的时候,我们难免会常常听到分库分表这样的名词。

当然,分库分表有很多的方法论,比如垂直拆分、水平拆分;也有很多的中间件产品,比如MyCat、ShardingJDBC。

根据业务场景选择合适的拆分方法,再选择一个熟悉的开源框架,就能帮助我们完成项目中所涉及到的数据拆分工作。

本文并不打算就这些方法论和开源框架展开深入的探讨,笔者想讨论另外一个场景:

如果系统中需要拆分的表并不多,只是1个或者少量的几个,我们是否值得引入一些相对复杂的中间件产品;特别是,如果我们对它们的原理不甚了解,是否有信心驾驭它们 ?

基于此,如果你的系统中有少量的表需要拆分,也没有专门的资源去研究开源组件,那么我们可以自己来实现一个简单的分库分表插件;当然,如果你的系统比较复杂,业务量较大,还是采用开源组件或者团队自研组件来解决这事较为稳妥。

分库分表简单?那我想问如何实现“分库分表插件”?

 

一、原理

分库分表这事说简单也简单,说复杂那也挺复杂...

简单是因为它的核心流程比较明确。就是解析SQL语句,然后根据预先配置的规则,重写或路由到真实的数据库表中去;

复杂在于,SQL语句复杂且灵活,比如分页、去重、排序、分组、聚合、关联查询等操作,如何正确的解析它们。

所以就算是ShardingJDBC,在官网中也明确了支持项和不支持项。

二、注解式配置

相对于复杂的配置文件,我们采用较为轻便的注解式配置,它的定义如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public%20@interface%20Sharding%20{
%20%20%20%20String%20tableName();%20%20%20%20%20//逻辑表名
%20%20%20%20String%20field();%20%20%20%20%20%20%20%20%20//分片键
%20%20%20%20String%20mode();%20%20%20%20%20%20%20%20%20%20//算法模式
%20%20%20%20int%20length()%20default%200;%20//分表数量
}

那么,在哪里使用它呢%20?%20比如我们的用户表需要分表,那就在User这个实体对象上标注。

@Data
@Sharding(tableName%20=%20"user",field%20=%20"id",mode%20=%20"hash",length%20=%2016)
public%20class%20User%20{
%20%20%20%20private%20Long%20id;
%20%20%20%20private%20String%20name;
%20%20%20%20private%20String%20address;
%20%20%20%20private%20String%20tel;
%20%20%20%20private%20String%20email;
}

这就说明了,我一共有%2016%20张用户表,根据用户ID,使用Hash算法来计算它的位置。

当然,我们不止有Hash算法,还可以根据日期范围来定义。

@Data
@Sharding(tableName%20=%20"car",field%20=%20"creatTime",mode%20=%20"range")
public%20class%20Car%20{
%20%20%20%20private%20long%20id;
%20%20%20%20private%20String%20number;
%20%20%20%20private%20String%20brand;
%20%20%20%20private%20String%20creatTime;
%20%20%20%20private%20long%20userId;
}

三、分片算法

在这里,笔者实现了两种分片方式,就是HashAlgorithm和RangeAlgorithm%20。

1、范围分片

如果你的系统中有使用冷热数据分离,我们可以按照日期将不同月的数据分散到不同的表中。

比如车辆的创建时间是2019-12-10%2015:30:00,这条数据将会被分配到car_201912这张表中去。

我们通过截取时间的年月部分,然后再加上逻辑表名即可。

public%20class%20RangeAlgorithm%20implements%20Algorithm%20{
%20%20%20%20@Override
%20%20%20%20public%20String%20doSharding(String%20tableName,%20Object%20value,int%20length)%20{
%20%20%20%20%20%20%20%20if%20(value!=null){
%20%20%20%20%20%20%20%20%20%20%20%20try{
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20DateUtil.parseDateTime(value.toString());
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20String%20replace%20=%20value.toString().substring(0,%207).replace("-",%20"");
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20String%20newName%20=%20tableName+"_"+replace;
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20newName;
%20%20%20%20%20%20%20%20%20%20%20%20}catch%20(DateException%20ex){
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20logger.error("时间格式不符合要求!传入参数:{},正确格式:{}",value.toString(),"yyyy-MM-dd%20HH:mm:ss");
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName;
%20%20%20%20%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20return%20tableName;
%20%20%20%20}
}

2、Hash分片

在Hash分片算法中,我们可以先判断表的数量,是不是2的幂次方。如果不是,就通过算数方式获取下标,如果是呢,就通过位运算的方式获取下标。当然了,这是在HashMap源码中学到的哦。

public%20class%20HashAlgorithm%20implements%20Algorithm%20{
%20%20%20%20@Override
%20%20%20%20public%20String%20doSharding(String%20tableName,%20Object%20value,int%20length)%20{
%20%20%20%20%20%20%20%20if%20(this.isEmpty(value)){
%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName;
%20%20%20%20%20%20%20%20}else{
%20%20%20%20%20%20%20%20%20%20%20%20int%20h;
%20%20%20%20%20%20%20%20%20%20%20%20int%20hash%20=%20(h%20=%20value.hashCode())%20^%20(h%20>>>%2016);
%20%20%20%20%20%20%20%20%20%20%20%20int%20index;
%20%20%20%20%20%20%20%20%20%20%20%20if%20(is2Power(length)){
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%20=%20(length%20-%201)%20&%20hash;
%20%20%20%20%20%20%20%20%20%20%20%20}else%20{
%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%20=%20Math.floorMod(hash,%20length);
%20%20%20%20%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20%20%20%20%20return%20tableName+"_"+index;
%20%20%20%20%20%20%20%20}
%20%20%20%20}
}

四、拦截器

配置和分片算法都有了,接下来就是重头戏了。在这里,我们使用Mybatis拦截器将它们派上用场。

常年CRUD的我们,都知道一条业务SQL肯定逃不出它们的范围。其中,在业务上我们的删除功能一般都是逻辑删除,所以,基本上不会有DELETE操作。

相较而言,新增和修改SQL都比较简单且格式固定,查询SQL往往比较灵活且复杂。所以,在这里笔者定义了两个拦截器。

不过,在介绍拦截器之前,我们有理由要了解另外两个东西:SQL语法解析器和分片算法处理器。

1、JSqlParser

JSqlParser负责解析SQL语句,并转化为JAVA类的层次结构。我们可以先看个简单的例子来认识它。

public%20static%20void%20main(String[]%20args)%20throws%20JSQLParserException%20{

	String%20insertSql%20=%20"insert%20into%20user%20(id,name,age)%20value(1001,'范闲',20)";
	Statement%20parse%20=%20CCJSqlParserUtil.parse(insertSql);
	Insert%20insert%20=%20(Insert)%20parse;

	String%20tableName%20=%20insert.getTable().getName();
	List<Column>%20columns%20=%20insert.getColumns();
	ItemsList%20itemsList%20=%20insert.getItemsList();
	System.out.println("表名:"+tableName+"%20列名:"+columns+"%20属性:"+itemsList);
}
输出:%20表名:user%20列名:[id,%20name,%20age]%20属性:(1001,%20'范闲',%2020)

我们可以看到,JSqlParser可以解析出SQL的语法信息。相应的,我们也可以更改对象内容,从而达到修改SQL语句的目的。

2、算法处理器

我们的分片算法有多个,具体应该调用哪一个是在程序运行期来决定的。所以,我们使用一个Map先将算法注册起来,然后根据分片模式来调用它。这也是策略模式的体现。

@Component
public%20class%20AlgorithmHandler%20{
%20%20%20%20private%20Map<String,%20Algorithm>%20algorithm%20=%20new%20HashMap<>();
%20%20%20%20@PostConstruct
%20%20%20%20public%20void%20init(){
%20%20%20%20%20%20%20%20algorithm.put("range",new%20RangeAlgorithm());
%20%20%20%20%20%20%20%20algorithm.put("hash",new%20HashAlgorithm());
%20%20%20%20}
%20%20%20%20public%20String%20handler(String%20mode,String%20name,Object%20value,int%20length){
%20%20%20%20%20%20%20%20return%20algorithm.get(mode).doSharding(name,%20value,length);
%20%20%20%20}
}

3、拦截器

我们知道,MyBatis允许你在已映射语句执行过程中的某一点进行拦截调用。

如果你对它的原理还不熟悉,那么可以先看看笔者的文章:Mybatis拦截器的原理。

整体来看,它的流程如下:

  • 通过Mybatis拦截待执行的SQL;
  • 通过JSqlParser解析SQL,获取逻辑表名等;
  • 调用分片算法获取真实表名;
  • 修改SQL,并修改BoundSql;
  • Mybatis执行修改后的SQL,达成目的。

比如,对于insert语句和update语句,它的核心代码如下:

 

五、查询及分页

事实上,新增和修改都比较简单,较为复杂的是查询语句。

但是,我们的插件并不在于要满足所有的查询语句,而是可以根据真实的业务场景来扩展修改。

不过分页功能基本上是逃不开的。拿PageHelper为例,它的原理也是通过Mybatis拦截器来实现的。如果它和我们的分表插件在一起,可能会产生冲突。

所以在分表插件中,笔者也集成了分页功能,基本上和PageHelper一样,但并未直接使用它。另外,对于查询来说,在查询条件中是否带有分片键,也是很关键的地方。

1、查询

在范围算法中,在业务上我们要求只查询特定某一个月或者近几个月的数据即可;在Hash算法中,我们则要求每次都带有主键。

但第二个条件往往不能成立,业务方也满足不了每次都必须带有主键。

针对这种情况,我们只能遍历所有的表,查询符合条件的数据,然后再汇总返回;

 

分库分表简单?那我想问如何实现“分库分表插件”?

 

这种方式的缺点显而易见,性能较差。还有一种方式就是可以将常用的查询条件与分片键建立映射关系,在查询时先根据查询条件找到分片键的字段值,然后再根据分片键查询。

2、分页

如上所言,插件中集成了分页功能,实现流程与PageHelper一样,但考虑到冲突,并未直接使用。

 

分库分表简单?那我想问如何实现“分库分表插件”?

 


作者:清幽之地
原文链接:https://juejin.im/post/5dfc6cc0518825126f3735d7



Tags:分库分表   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
分库分表介绍:分库分表的目的是为了系统高并发、高可用。分库和年发表是两回事,两个概念,都是为了防止数据库服务因为同一时间内访问量过大导致宕机而设计的一种应对策略。一、...【详细内容】
2021-06-28  Tags: 分库分表  点击:(103)  评论:(0)  加入收藏
Sharding-JDBC背景:出于工作的需要,非常需要详细了解数据库分表分库的内容,这样便于选择版本,了解原理及性能,做出更好的选择。Sharding-JDBC是ShardingSphere的第一个产品,也是Sh...【详细内容】
2021-05-14  Tags: 分库分表  点击:(214)  评论:(0)  加入收藏
在文章开头先抛几个问题: 什么时候才需要分库分表呢?我们的评判标准是什么? 一张表存储了多少数据的时候,才需要考虑分库分表? 数据增长速度很快,每天产生多少数据,才需要考虑做分...【详细内容】
2021-04-07  Tags: 分库分表  点击:(200)  评论:(0)  加入收藏
分库分表的文章网上非常多,但是大多内容比较零散,以讲解知识点为主,没有完整地说明一个大表的切分、新架构设计、上线的完整过程。因此,我结合去年做的一个大型分库分表项目,来复...【详细内容】
2020-11-19  Tags: 分库分表  点击:(176)  评论:(0)  加入收藏
1、场景描述例如订单库进行了分库分表,其实例如下图所示: 现在的需求是希望创建一个任务就将数据同步到MQ集群,而不是为每一个数据库实例单独创建一个任务,将其数据导入到MQ集...【详细内容】
2020-11-16  Tags: 分库分表  点击:(310)  评论:(0)  加入收藏
《sharding-jdbc 分库分表的 4种分片策略》 中我们介绍了 sharding-jdbc 4种分片策略的使用场景,可以满足基础的分片功能开发,这篇我们来看看分库分表后,应该如何为分片表生成...【详细内容】
2020-11-10  Tags: 分库分表  点击:(148)  评论:(0)  加入收藏
在之前写过一篇关于mysql分库分表的文章,那篇文章只是给大家提供了一个思路,但是回复下面有很多说是细节问题没有提到。所以咔咔就在出了这篇文章。...【详细内容】
2020-08-18  Tags: 分库分表  点击:(70)  评论:(0)  加入收藏
随着互联网的迅速发展,会导致产生海量的数据,在数据量还比较小的时候,传统的处理方式是将数据存储在关系或者非关系型数据库中,但是随着数据量逐渐增加,单个数据库的表已经很难容...【详细内容】
2020-06-12  Tags: 分库分表  点击:(72)  评论:(0)  加入收藏
0x01:TDDL(Taobao Distributed Data Layer)框架淘宝根据自己的业务特点开发了TDDL(Taobao Distributed Data Layer)框架,主要解决了分库分表对应用的透明化以及异构数据库之间的数...【详细内容】
2020-05-03  Tags: 分库分表  点击:(69)  评论:(0)  加入收藏
数据库瓶颈不管是IO瓶颈还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载的活跃连接数的阈值。在业务service来看, 就是可用数据库连接少甚至无...【详细内容】
2020-03-26  Tags: 分库分表  点击:(74)  评论:(0)  加入收藏
▌简易百科推荐
作者:雷文霆 爱可生华东交付服务部 DBA 成员,主要负责Mysql故障处理及相关技术支持。爱好看书,电影。座右铭,每一个不曾起舞的日子,都是对生命的辜负。 本文来源:原创投稿 *爱可生...【详细内容】
2021-12-24  爱可生    Tags:MySQL   点击:(7)  评论:(0)  加入收藏
生成间隙(gap)锁、临键(next-key)锁的前提条件 是在 RR 隔离级别下。有关Mysql记录锁、间隙(gap)锁、临键锁(next-key)锁的一些理论知识之前有写过,详细内容可以看这篇文章...【详细内容】
2021-12-14  python数据分析    Tags:MySQL记录锁   点击:(18)  评论:(0)  加入收藏
binlog 基本认识 MySQL的二进制日志可以说是MySQL最重要的日志了,它记录了所有的DDL和DML(除了数据查询语句)语句,以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二...【详细内容】
2021-12-14  linux上的码农    Tags:mysql   点击:(13)  评论:(0)  加入收藏
为查询优化你的查询 大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查...【详细内容】
2021-12-09  元宇宙iwemeta    Tags:mysql   点击:(15)  评论:(0)  加入收藏
测试的目的和原因,公司有很多程序员,每个程序员对数据库和表结构都有自己的理解。而且每个程序员的理解往往是以效率考虑。既然都是为了效率考虑,那么我就来测试一下究竟哪种使...【详细内容】
2021-12-08  吴彬的分享    Tags:Mysql数据库   点击:(14)  评论:(0)  加入收藏
当你们考虑项目并发的时候,我在部署环境,当你们在纠结使用ArrayList还是LinkedArrayList的时候,我还是在部署环境。所以啊,技术不止境,我在部环境。今天这篇文章缕一下在同一台服...【详细内容】
2021-12-08  秃头码哥    Tags:MySQL数据库   点击:(17)  评论:(0)  加入收藏
对于数据分析来说,MySQL使用最多的是查询,比如对数据进行排序、分组、去重、汇总及字符串匹配等,如果查询的数据涉及多个表,还需要要对表进行连接,本文就来说说MySQL中常用的查询...【详细内容】
2021-12-06  笨鸟学数据分析    Tags:MySQL   点击:(21)  评论:(0)  加入收藏
在学习SQL语句之前,首先需要区分几个概念,我们常说的数据库是指数据库软件,例如MySQL、Oracle、SQL Server等,而本文提到的数据库是指数据库软件中的一个个用于存储数据的容器。...【详细内容】
2021-11-24  笨鸟学数据分析    Tags:SQL语句   点击:(23)  评论:(0)  加入收藏
概述以前参加过一个库存系统,由于其业务复杂性,搞了很多个应用来支撑。这样的话一份库存数据就有可能同时有多个应用来修改库存数据。比如说,有定时任务域xx.cron,和SystemA域...【详细内容】
2021-11-05  Java云海    Tags:分布式锁   点击:(31)  评论:(0)  加入收藏
MySQL的进阶查询 一、 按关键字排序 使用ORDERBY语句来实现排序排序可针对一个或多个字段ASC:升序,默认排序方式 【升序是从小到大】DESC:降序 【降序是从大到小】ORDER BY的...【详细内容】
2021-11-05  Java热点    Tags:SQL语句   点击:(28)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条