vlambda博客
学习文章列表

自己动手写一个分库分表中间件(一)思考

This browser does not support music or audio playback. Please play it in Weixin or another browser.

最近在参与一个项目的分库分表改造,底层的分库分表中间件完全自研,截止目前为止,主体功能已经完全开发完成,会逐渐进入测试阶段。后面的系列文章会围绕这个中间件对本次分库分表的改造过程做一个记录。

作为系列第一篇文章,主要是谈一些目前这个阶段分库分表改造的个人感想。

为什么要分库分表

其实这个没啥好说的,放在这里主要是保持一个目录的完整性。

之所以要分库分表,很明显是因为库和表都到达了一个瓶颈。至于怎么分,如果是已有项目的改造,一般都是水平拆分,因为垂直拆分会影响现有的业务代码,这个影响程度可能会很大,而且都垂直拆分了,估计也要拆分微服务了。

分库分表还有一个点就是,一般来说,只有部分数据才需要分库分表,以这个项目目前的业务情况来说,单库足够承载一年的数据增长。其实本次改造我们最初是定义为”冷热数据迁移“,目的是将热库中部分大表的数据迁移到历史库,也就是说最终形成:

SQL->热库/冷库1/冷库2...

大部分的数据处理其实集中在热库。

在去年其他同学就做过一次热库中部分大表冷数据迁移,迁移后数据查询链路为:

这里有两个细节:

  1. 之前对不同数据源(冷/热库)的访问是通过配置不同的 SqlSessionFactory(不同的 Mapper package)、 DataSourceTransactionManaager,在具体调用的时候调用不同的 Mapper。相当于数据路由的代理模式为静态代理
  2. 之前的分表路由是在 Proxy 层根据路由规则计算出表名后手动传入表名通过在 MyBatis 动态 SQL 中传入 ${} 来实现的

这样实现的好处是简单,逻辑都在表层,比较清晰明了,但是也有一定的问题:

  1. 相同的 SQL 在不同的 Mapper 中都要写,比如去年迁移数据到 DB1,需要为 DB1 这个数据源再写一套 Mapper 和 SQL,后面如果还有新的库 DB2,那么还要为 DB2 再写一套。同时本次我们要迁移的表更多,那么会有更多的代码工作量
  2. 如果新增数据源,Proxy 静态代理层也要同步更改
  3. 在 MyBatis 动态 SQL 中传入 ${} 来动态传入表名,会污染业务逻辑,比如这样:
@Insert("INSERT INTO ${tableName}("...
int insert(@Param("tableName") String tableName...);
  1. 无法解决跨库事务的问题(视实际情况酌情考虑)

后面经过方案讨论后,决定扩展成分库分表,但其实本质是没有变化的。

技术选型

其实关于分库分表中间件的技术选型没有那么复杂,主要是因为可选的也不多,如果公司有运维可以支持,那么直接使用数据库代理,如 Mycat、Sharding-Proxy 等完全是可以的,如果没有运维支持,那么就只能使用客户端的框架,目前市面上可选的框架就一个:Sharding-JDBC,不用纠结。

在当前这个阶段(自研的中间件主体功能已经完全开发完成,逐渐进入测试阶段),回过头来再看这个问题,其实与 Sharding-JDBC 对比,我们有的功能,它都有,我们的特定业务场景某种程度上 Sharding-JDBC 也能实现。

但是这里有一个点是,虽然我们号称在做“分库分表”,不过这个系统的分库分表其实不是 Sharding-JDBC 的那种分库分表。在分库分表的定义中该项目的数据库操作链路是这样的:

SQL->业务1(master+slave+report)/业务2(master+slave+report)/业务3(master+slave+report)
//这里的report其实也是一个 MySQL 物理上的从库,只是部分报表相关的操作会在这个库进行

只是暂时我们的“业务1”、“业务2”都是根据单号的年份划分的,其实这个系统其实大部分操作都在“业务1”中,我们更需要的是“写+从读+报表读”的数据应用分离的场景,所以 Sharding-JDBC 的很多高级功能其实我们是用不上的,而且 Sharding-JDBC 默认支持的读写分离也与我们期望的有差异,如果使用 Sharding-JDBC 我们只能基于它的 Hint 做一个适配层,从而让整个分库分表层更符合我们的业务定义和业务习惯,但是这个适配会显得很凌乱,后期扩展也会比较麻烦。

同时为了引入 Sharding-JDBC,此时各种历史配置也要做适配。由于各个项目的复杂性和版本问题,如果有其他的项目也要做适配层,我们是很难做到“一次适配,处处可用”的。还有部分小点,比如事务问题,我们的场景需要使用 Sharding-JDBC 的 LOCAL 事务,不过会存在如果自身注入会出现循环依赖的问题。

总的来说,毕竟改造就是“先有项目,后引框架”,优秀的开源框架会提供稳定的功能,但是会存在业务系统需要去为框架做一定适配的问题,即业务为框架适配,但是自研可以做到框架为业务适配。其实说到这里,并不是说 Sharding-JDBC 不好,在这里只是一种取舍,没有最好的,只有更合适的。