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

前言随着系统数据量的日益增长,在说起数据库架构和数据库优化的时候,我们难免会常常听到分库分表这样的名词 。
当然,分库分表有很多的方法论,比如垂直拆分、水平拆分;也有很多的中间件产品,比如MyCat、ShardingJDBC 。
根据业务场景选择合适的拆分方法,再选择一个熟悉的开源框架,就能帮助我们完成项目中所涉及到的数据拆分工作 。
本文并不打算就这些方法论和开源框架展开深入的探讨,笔者想讨论另外一个场景:

如果系统中需要拆分的表并不多,只是1个或者少量的几个,我们是否值得引入一些相对复杂的中间件产品;特别是,如果我们对它们的原理不甚了解,是否有信心驾驭它们 ?
基于此,如果你的系统中有少量的表需要拆分,也没有专门的资源去研究开源组件,那么我们可以自己来实现一个简单的分库分表插件;当然,如果你的系统比较复杂,业务量较大,还是采用开源组件或者团队自研组件来解决这事较为稳妥 。
分库分表简单?那我想问如何实现“分库分表插件”?

文章插图
 
一、原理分库分表这事说简单也简单,说复杂那也挺复杂...
简单是因为它的核心流程比较明确 。就是解析SQL语句,然后根据预先配置的规则,重写或路由到真实的数据库表中去;
复杂在于,SQL语句复杂且灵活,比如分页、去重、排序、分组、聚合、关联查询等操作,如何正确的解析它们 。
所以就算是ShardingJDBC,在官网中也明确了支持项和不支持项 。
二、注解式配置相对于复杂的配置文件,我们采用较为轻便的注解式配置,它的定义如下:
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic%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源码中学到的哦 。


推荐阅读