透明的分库分表方案

问题提出

随着应用规模的不断扩大,单机数据库就慢慢无法满足应用的需要了,这主要表现在如下方面:

  1. 存量数据越来越大,查询速度越来越慢
  2. 访问并发越来越大,磁盘IO、网络IO、CPU都慢慢成为瓶颈
  3. 事务数越来越多,事务冲突越来越严重,导致TPS越来越少

这个时候,有的人采用了换商用数据库的方案比如Oracle,然后用Oracle的RAC方式进行水平扩展。但是带来的缺点也比较明显,第一是成本太高,一般人吃不消;第二,管理复杂度较单节点有非常大的提升,风险及管理成本也相应增加;第三,对人员的水平要求更高,如果做不好,在某些场景下甚至不如单节点来得快;第四,随着集群规模的变大,性能提升幅度与加入的节点数非正比关系,所以经济性也不太好。

因此,本人的结论是,短时间内采用Oracle等商用大型数据库阶段性的解决一段问题是可以的,但是从长久来说,还是治标不治本的。

因此当数据库处理能力不足的时候,还是要想办法对现有数据库进行的扩展为好。

解决方案

数据库处理性能不足的时候,一般有两种解决方案,一种是垂直扩展,一种是水平扩展。

  • 垂直扩展方案

        优势:业务代码不需要进行任何附加处理

        劣势:费用增加远超过扩展的处理能力
                通过增加硬件扩展的性能最终有其极
                限
网络IO处理瓶颈无法解决
                随着数据量的增加,性能下降加快

  • 水平扩展方案

        优势:磁盘IO、网络IO、CPU、内存分散到不同的机器上

                水平扩展处理能力与花费的成本成正比
                可以接近无限的对处理能力进行扩展

        劣势:对于业务有一定的限制条件
                跨库关联查询不被支持
                对于分库分表及应用需求要有系统的分析

简单的总结一下就是,采用垂直扩展方式,只能短时间解决问题,由于其能增加的性能最终是有极限的,因此不是终极解决方案;而水平扩展方案则几乎有无限的扩展能力,但是对设计人员的设计能力有要求,对于数据库中一些极特殊的SQL语句不支持。

需求分析

Tiny框架设计者当然不会做暂时的方案,当然想得是长久之计,于是毫无疑问要做水平扩展方案(当然,垂直扩展方案和软件几乎没有什么关系,咱也做不了),做之前首先扩展以下的需求:

  • 采用Java技术来实现
  • 要支持常见数据库,主要是能支持所有有JDBC Driver的数据库
  • 支持自增长主键,这样原来依赖自增长主键的数据库应用就不需要在这方面做特殊处理了
  • 支持数据库分页语句,这样原来依赖数据库分页语句的数据库应用就不需要在这方面做特殊处理了
  • 能支持绝大多数的SQL语句
  • 在性能方面最好能接近JDBC驱动
  • 有良好的扩展性,数据库设计者可以方便的进行定制扩展
  • 支持读写分离,负载均衡实现算法可定制
  • 支持分库
  • 支持分表
  • 对事务有良好的支持
  • 对统计及排序有良好的支持

系统设计与实现

实现方案及比较

数据库分区分表方案的实现方式有多种:

  • DAO层:实现难度低、业务代码耦合程度高、业务开发成本高、重构成本高,可复用性较差
  • DataSource层:实现难度中,业务代码耦合程度低、业务开发成本低、重构成本低、可复用性中
  • JDBC层:实现难度高,业务代码耦合程度低、业务开发成本低、重构成本、可复用性好
  • 代理层:实现难度高,业务代码耦合程度低、业务开发成本低、重构成本、可复用性好

Tiny框架采用了在JDBC层实现的方案,这种方案较代理层方面,可以少了网络通信方面的实现,所以代码量少、稳定性高,同时性能方面少一倍的网络通信,所以性能更高。

Tiny分库分表设计方案

透明的分库分表方案

稍解释一下:

当TinyDBRouter收到一个SQL时,首先对SQL进行解析,然后根据分库和分表规则路由到合适的真实数据库去执行,然后把执行的结果进行处理,然后把结果正确的提供给数据库请求者,一次交互就完整的完成了。

当然,实际的处理过程则远比这个要复杂得多,因为要考虑到事务的一致性、处理的高效性、结果的正确性,这里面的道道就多了,说起来比较漫长,这里就简单略过。

应用实例

使用方法

1
2
3
4
5
6
7
8
Class.forName("org.tinygroup.dbRouterjdbc3.jdbc.TinyDriver");
Connection conn = DriverManager.getConnection("jdbc:dbRouter://Router1", “username", “password");
Statement stmt = conn.createStatement();
String sql;
for (int i = 0; i < 10; i++) {
    sql = "insert into aaa(aaa) values ('ppp')";
    stmt.execute(sql);
}



嗯嗯,上面就是使用方式了,熟悉JDBC的同学们马上就可以发现,这个和普通的JDBC程序没有什么不同么?确实是这样,使用TinyDBRouter,实际开发过程,与原来没有任何不同,不管是你用JDBC,还是Spring JDBC Template,还是iBatis、Hibernate,不管是任何的Java ORM框架,统统都可以使用。

唯一需要注意的就是,要把原来的URL和Driver改成Tiny的。

那其它的数据库管理工具,可以使用么?当然可以,只要是基于Java做的数据库管理工具,只要把Tiny的Driver相关的Jar包放入其ClassPath路径,就可以使用了。

其实对于统计方面的支持,对于所有的分库分表框架来说都是极具挑战性的,比如许多的分库分表框架要求每次SQL只能落在一个分片上执行,才能保证结果的正确性;比如许多的分库分表框架都要求统计时不能有排序等等,甚至有的直接就不支持,欢迎同学们让他们对号入座。

TinyDBRouter唯一的限制是:

不支持跨库关联查询

当然,几乎所有的都不支持这个特性,少量号称支持,实际上没有可用性---因为性能实在是太慢了,数据量如果大一点就死翘翘了。

常见问题问答

  1. 像MySQL、SQL Server中的自增长主键需要特殊处理么?答案:不需要,原有程序照样使用就好
  2. 像M ySQL、SQL Server中的分页SQL可以原样使用么?答案:必须可以
  3. TinyDBRouter支持的SQL语句支持的多么?答案:支持SQL92规范的绝大部分SQL语句(极少量不支持)
  4. 支持Having语句不?答案:支持
  5. 原有项目的代码重构成本高么?答案:这个和DBA制订的数据分区方案有关,只要不违反上面的限制条件都可以不修改。
  6. 我把主表和从表都分到一个分片中,关联只在相同分片中发生,这种情况下,代码需要修改吗?答案:不需要。

总结

Tiny框架的所有部件或子项目,我们从来不加吸引眼球的“最”字,我们相信只要我们扎扎实实的努力加上轻灵优雅的设计,一定会是相关问题领域中一个相当不错的解。

上一篇:Tiny框架2.0版火热推出


下一篇:悠然乱弹:一段SQL引发的性能危机及其背后隐藏的设计缺陷