一种简单易懂的 MyBatis 分库分表方案

数据库分库分表除了使用中间件来代理请求分发以外,另一种常见的方法就是在客户端层面来分库分表 —— 经过适当地包装客户端代码使得分库分表的数据库访问操做代码编写起来也很方便。本文的分库分表方案基于 MyBatis 框架,可是又不一样于市面上经常使用的方案,它们通常都是经过编写复杂的 MyBatis 插件来重写 SQL 语句,这样的插件代码会巨复杂无比,可能最终只有插件的原做者本身能够彻底吃透相关代码,给项目的维护性带来必定问题。本文的方案很是简单易懂,并且也不失使用上的便捷性。它的设计哲学来源于 Python —— Explicit is better than Implicit,也就是显式优于隐式,它不会将分库分表的过程隐藏起来。mysql

不少分库分表的设计在实现上会尽可能将分库分表的逻辑隐藏起来,其实这是毫无必要的。使用者必须知道背后确实进行了分库分表,不然他怎么会没法进行全局的索引查找?他怎么会没法随意进行多表的 join 操做。若是你真的将它当成单表来用,到上线时必然会出大问题。git

项目名称叫:shardino,项目地址:https://github.com/pyloque/shardinogithub

接下来咱们来看看在本文的方案之下,数据库操做代码的形式是怎样的算法

帖子表一共分出来 64 个表,不一样的记录会各自分发到其中一个表,能够是按 hash 分发,也能够按照日期分发,分发逻辑由用户代码本身来决定。在不一样的环境中能够将分表数量设置为不一样的值,好比在单元测试下分表设为 4 个,而线上可能须要设置为 64 个。spring

帖子表又会被分配到多个库,这里就直接取模分配。假设有 4 个帖子库,帖子表总共分出来 64 个表,分别是 post_0、post_一、post_2 一直到 post_63。那么 post_0、post_四、post_8 等分配到 0 号库,post_一、post_五、post_9 等分配到 1 号库,post_二、post_六、post_10 等分配到 2 号库,post_三、post_五、post_11 等分配到 4 号库。sql

从配置文件中构建 MySQLGroupStore 数据库组对象,这个对象是咱们执行 MySQL 操做的入口,经过它能够找到具体的物理的 MySQL 主从数据源。docker

配置文件 application.properties 以下数据库

这里的数据库组是由多个对等的 Master-Slaves 对构成,每一个 Master-Slaves 是由一个主库和多个不一样权重的从库构成,Master-Slaves 对的数量就是分库的数量。springboot

mysqlgroup 还有一个特殊的配置选项 slaveEnabled 来控制是否须要从库,从而关闭读写分离,默认是关闭的,这样就不会去构建从库实例相关对象。session

post_k 这张表后缀 k 咱们称之为 partition number,也就是后续代码中处处在用的 partition 变量,代表当前的记录被分配到对应物理数据表的序号。咱们须要根据记录的内容计算出 partition number,再根据 partition number 决定出这条记录所在的物理表属于那个物理数据库,而后对这个物理数据库进行相应的读写操做。

在本例中,帖子表按照 userId 字段 hash 出 64 张表,平均分配到 2 对物理库中,每一个物理库包含一个主库和2个从库。

有了 MySQLGroupStore 实例,咱们就能够尽情操纵全部数据库了。

 

从上面的代码中能够看出全部的读写、建立、删除表操做的第一步都是计算出 partition number,而后根据它来选出目标主从库再进一步对目标的数据表进行操做。这里我默认开启了autocommit,因此不须要显式来 session.commit() 了。

在对数据表的操做过程当中,又须要将具体的 partition number 传递过去,如此 MyBatis 才能知道具体操做的是哪一个分表。

在每一条数据库操做中都必须带上 partition 参数,你可能会以为这有点繁琐。可是这也很直观,它明确地告诉咱们目前正在操做的是哪个具体的分表。

在 MyBatis 的注解 Mapper 类中,若是方法含有多个参数,须要使用 @Param 注解进行名称标注,这样才能够在 SQL 语句中直接使用相应的注解名称。不然你得使用默认的变量占位符名称 param0、param1 来表示,这就很不直观。

咱们将分表的 hash 算法写在实体类 Post 中,这里使用 CRC32 算法进行 hash。

 

代码中的 partitionFor 方法的参数 num 就是一共要分多少表。若是是按日期来分表,这个参数可能就不须要,直接返回日期的整数就行好比 20190304。

还有最后一个问题是多个带权重的从库是如何作到几率分配的。这里就要使用到 spring-jdbc 自带的 AbstractRoutingDataSource —— 带路由功能的数据源。它能够包含多个子数据源,而后根据必定的策略算法动态挑选出一个数据源来,这里就是使用权重随机。

可是有个问题,我这里只须要这一个类,可是须要引入整个 spring-boot-jdbc-starter 包,有点拖泥带水的感受。我研究了一下 AbstractRoutingDataSource 类的代码,发现它的实现很是简单,若是就仿照它本身实现了一个简单版的,这样就不须要引入整个包代码了。

还需进一步深刻理解其实现代码的能够将 shardino 代码仓库拉到本地跑一跑

里面有单元测试能够运行起来,运行以前须要确保本机安装了 docker 环境

这条指令会启动2对主从库,各1主两从。

在本例中虽然用到了 springboot ,其实也只是用了它方便的依赖注入和单元测试功能,shardino 彻底能够脱离 springboot 而独立存在。

shardino 并非一个完美的开源库,它只是一份实现代码的样板,若是读者使用的是其它数据库或者 MySQL 的其它版本,那就须要本身微调一下代码来适配了。

欢迎工做一到五年的Java工程师朋友们加入个人我的粉丝群Java填坑之路:789337293 群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代!

相关文章
相关标签/搜索