业界通用数据库分库分表(sharding)研究
Posted 懂一点架构
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了业界通用数据库分库分表(sharding)研究相关的知识,希望对你有一定的参考价值。
一、基本思想
Sharding的基本思想就要把一个数据库切分成多个部分放到不同的数据库(server)上,从而缓解单一数据库的性能问题。不太严格的讲,对于海量数据的数据库,如果是因为表多而数据多,这时候适合使用垂直切分,即把关系紧密(比如同一模块)的表切分出来放在一个server上。如果表并不多,但每张表的数据非常多,这时候适合水平切分,即把表的数据按某种规则(比如按ID散列)切分到多个数据库(server)上。当然,现实中更多是这两种情况混杂在一起,这时候需要根据实际情况做出选择,也可能会综合使用垂直与水平切分,从而将原有数据库切分成类似矩阵一样可以无限扩充的数据库(server)阵列。下面分别详细地介绍一下垂直切分和水平切分。
垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰。
水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,后期的数据维护也会更为复杂一些。
让我们从普遍的情况来考虑数据的切分:一方面,一个库的所有表通常不可能由某一张表全部串联起来,这句话暗含的意思是,水平切分几乎都是针对一小搓一小搓(实际上就是垂直切分出来的块)关系紧密的表进行的,而不可能是针对所有表进行的。另一方面,一些负载非常高的系统,即使仅仅只是单个表都无法通过单台数据库主机来承担其负载,这意味着单单是垂直切分也不能完全解决问明。因此多数系统会将垂直切分和水平切分联合使用,先对系统做垂直切分,再针对每一小搓表的情况选择性地做水平切分。从而将整个数据库切分成一个分布式矩阵。
二、切分策略
如前面所提到的,切分是按先垂直切分再水平切分的步骤进行的。垂直切分的结果正好为水平切分做好了铺垫。垂直切分的思路就是分析表间的聚合关系,把关系紧密的表放在一起。多数情况下可能是同一个模块,或者是同一“聚集”。在垂直切分出的表聚集内,找出“根元素”,按“根元素”进行水平切分,也就是从“根元素”开始,把所有和它直接与间接关联的数据放入一个shard里。这样出现跨shard关联的可能性就非常的小。应用程序就不必打断既有的表间关联。比如:对于社交网站,几乎所有数据最终都会关联到某个用户上,基于用户进行切分就是最好的选择。
对于共享数据数据,如果是只读的字典表,每个shard里维护一份应该是一个不错的选择,这样不必打断关联关系。如果是一般数据间的跨节点的关联,就必须打断。
总体实施策略:
参考文献:http://blog.csdn.net/bluishglc/article/details/6161475
三、全局主键生成策略
一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由。目前几种可行的主键生成策略有:
1,UUID:使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。
2,结合数据库维护一个Sequence表:此方案的思路也很简单,在数据库中建立一个Sequence表,表的结构类似于:
CREATE TABLE `SEQUENCE` (
`tablename` varchar(30) NOT NULL,
`nextid` bigint(20) NOT NULL,
PRIMARY KEY (`tablename`)
) ENGINE=InnoDB
每当需要为某个表的新纪录生成ID时就从Sequence表中取出对应表的nextid,并将nextid的值加1后更新到数据库中以备下次使用。此方案也较简单,但缺点同样明显:由于所有插入任何都需要访问该表,该表很容易成为系统性能瓶颈,同时它也存在单点问题,一旦该表数据库失效,整个应用程序将无法工作。有人提出使用Master-Slave进行主从同步,但这也只能解决单点问题,并不能解决读写比为1:1的访问压力问题。
3, 一种极为优秀的主键生成策略
flickr这一方案的整体思想是:建立两台以上的数据库ID生成服务器,每个服务器都有一张记录各表当前ID的Sequence表,但是Sequence中ID增长的步长是服务器的数量,起始值依次错开,这样相当于把ID的生成散列到了每个服务器节点上。例如:如果我们设置两台数据库ID生成服务器,那么就让一台的Sequence表的ID起始值为1,每次增长步长为2,另一台的Sequence表的ID起始值为2,每次增长步长也为2,那么结果就是奇数的ID都将从第一台服务器上生成,偶数的ID都从第二台服务器上生成,这样就将生成ID的压力均匀分散到两台服务器上,同时配合应用程序的控制,当一个服务器失效后,系统能自动切换到另一个服务器上获取ID,从而保证了系统的容错。
实现该方案,应用程序同样需要做一些处理,主要是两方面的工作:
1. 自动均衡数据库ID生成服务器的访问
2. 确保在某个数据库ID生成服务器失效的情况下,能将请求转发到其他服务器上执行。
四、关于使用框架还是自主开发以及sharding实现层面的考量
1、sharding逻辑的实现层面
从一个系统的程序架构层面来看,sharding逻辑可以在DAO层、JDBC API层、介于DAO与JDBC之间的Spring数据访问封装层(各种spring的template)以及介于应用服务器与数据库之间的sharding代理服务器四个层面上实现。
在DAO层实现
当团队决定自行实现sharding的时候,DAO层可能是嵌入sharding逻辑的首选位置,因为在这个层面上,每一个DAO的方法都明确地知道需要访问的数据表以及查询参数,借助这些信息可以直接定位到目标shard上,而不必像框架那样需要对SQL进行解析然后再依据配置的规则进行路由。另一个优势是不会受ORM框架的制约。由于现在的大多数应用在数据访问层上会依赖某种ORM框架,而多数的shrading框架往往无法支持或只能支持一种orm框架,这使得在选择和应用框架时受到了很大的制约,而自行实现sharding完全没有这方面的问题,甚至不同的shard使用不同的orm框架都可以在一起协调工作。
简单总结一下,在DAO层自行实现sharding的优势在于:不受ORM框架的制约、实现起来较为简单、易于根据系统特点进行灵活的定制、无需SQL解析和路由规则匹配,性能上表现会稍好一些;劣势在于:有一定的技术门槛,工作量比依靠框架实现要大(反过来看,框架会有学习成本)、不通用,只能在特定系统里工作。当然,在DAO层同样可以通过XML配置或是注解将sharding逻辑抽离到“外部”,形成一套通用的框架. 不过目前还没有出现此类的框架。
2、使用框架还是自主开发?
基于代理方式的有mysql Proxy,基于Hibernate框架的是Hibernate Shards,通过重写spring的ibatis template类似Cobar Client,这些框架各有各的优势与短板,架构师可以在深入调研之后结合项目的实际情况进行选择,但是总的来说,我个人对于框架的选择是持谨慎态度的。一方面多数框架缺乏成功案例的验证,其成熟性与稳定性值得怀疑。另一方面,一些从成功商业产品开源出框架是否适合你的项目是需要架构师深入调研分析的。
五、多数据源的事务处理
事务太复杂,尽量避免使用事务。
六、一种支持自由规划无须数据迁移和修改路由代码的Sharding扩容方案
简单地说,Sharding就是将原来单一数据库按照一定的规则进行切分,把数据分散到多台物理机(我们称之为Shard)上存储,从而突破单机限制,使系统能以Scale-Out的方式应对不断上涨的海量数据,但是这种切分对上层应用来说是透明的,多个物理上分布的数据库在逻辑上依然是一个库。实现Sharding需要解决一系列关键的技术问题,这些问题主要包括:切分策略、节点路由、全局主键生成、跨节点排序/分组/表关联、多数据源事务处理和数据库扩容等。
任何Sharding系统,在上线运行一段时间后,数据就会积累到当前节点规模所能承载的上限,此时就需要对数据库进行扩容了,也就是增加新的物理结点来分摊数据。如果系统使用的是基于ID进行散列的路由方式,那么团队需要根据新的节点规模重新计算所有数据应处的目标Shard,并将其迁移过去,这对团队来说无疑是一个巨大的维护负担;而如果系统是按增量区间进行路由(如每1千万条数据或是每一个月的数据存放在一个节点上),虽然可以避免数据的迁移,却有可能带来“热点”问题,也就是近期系统的读写都集中在最新创建的节点上,从而影响了系统性能。面对这种两难的处境,Sharding扩容显得异常困难。
一般来说,“理想”的扩容方案应该努力满足以下几个要求:
1)最好不迁移数据 (无论如何,数据迁移都是一个让团队压力山大的问题);
2)允许根据硬件资源自由规划扩容规模和节点存储负载;
3)能均匀的分布数据读写,避免“热点”问题;
4)保证对已经达到存储上限的节点不再写入数据;
目前,能够避免数据迁移的优秀方案并不多,相对可行的有两种,一种是维护一张记录数据ID和目标Shard对应关系的映射表,写入时,数据都写入新扩容的Shard,同时将ID和目标节点写入映射表,读取时,先查映射表,找到目标Shard后再执行查询。该方案简单有效,但是读写数据都需要访问两次数据库,且映射表本身也极易成为性能瓶颈。为此系统不得不引入分布式缓存来缓存映射表数据,但是这样也无法避免在写入时访问两次数据库,同时大量映射数据对缓存资源的消耗以及专门为此而引入分布式缓存的代价都是需要权衡的问题。另一种方案来自淘宝综合业务平台团队,它利用对2的倍数取余具有向前兼容的特性(如对4取余得1的数对2取余也是1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了Sharding扩容的难度。
一种理想的Sharding扩容方案
全局按增量区间分布数据,使用增量扩容,无数据迁移,局部使用散列方式分散数据读写,解决“热点”问题,同时对Sharding拓扑结构进行建模,使用一致的路由算法,扩容时只需追加节点数据,不再修改散列逻辑代码。
参考文献:http://blog.csdn.net/bluishglc/article/details/7970268
七、业界分库分表的一些产品
I、TDDL
淘宝团队开源的产品,基于java开发,无server,client-jar形式存在,直连数据库。
优点:
1、数据库主备和动态切换;
2、带权重的读写分离;
3、集中式数据源信息管理和动态变更;
4、基于jdbc规范,很容易扩展支持实现jdbc规范的数据源;
5、读写次数,并发度流程控制,动态变更;
6、支持单个数据源事务,不保证全局的分布式事务;
约束:
1、目前是半开源状态,开源版本目前不支持分库分表;
2、强依赖淘宝内部系统diamond配置中心;
II、cobar
alibaba团队开源的产品,基于java开发的分布式数据库中间件,它实现了MySQL的客户端与服务端协议,作为服务端与应用程序通讯,同时作为客户端与MySQL通讯。
优点:
1、支持垂直和水平数据切分;
2、支持双机热备的HA解决方案;
3、支持单个数据源事务,采用Best Efforts 1PC模式的事务管理,不保证全局的分布式事务;
约束:
1、proxy的模式太重,多了一层网络开销,运维成本高;
III、cobar-client
基于iBatis和Spring的轻量级分布式数据访问框架(DDAL),无server,client-jar形式存在,应用直连数据库。
优点:
1、支持简单的数据合并;
2、支持垂直和水平数据切分;
3、支持双机热备的HA解决方案;
4、数据库本地事务的支持, 目前采用Best Efforts 1PC模式的事务管理。
约束:
1、对于mybatis和Spring等框架的版本号有约束;
总结
分库分表主要有如下几点考虑:
1,如何选择切分策略?是水平切分还是垂直切分?
2,全局主键的生成策略;
3,多数据源的事务处理(注:哥的代码世界里尽量避免使用事务)
4,Sharding逻辑的实现层面考量;
5,后期扩容方案的考量;
最后,分库分表后的merge工作亦是一个令人头疼的问题:
1,针对in ( ) 的merge, 直接merge数据;
2,针对 order by 的merge,采用堆排序优化性能;
3,针对group by的merge,采用分表查询增加order by,合并时用堆合并;
4,针对 limit(a,b)的merge, 分层处理。
关于运维上需要考虑以下几点:
1、支持数据库fail-over动态切换;
2、支持读写分离;
3、支持垂直和水平数据切分,即分库分表;
4、支持双机热备的HA解决方案;
5、是否支持全局的分布式事务;
6、是否支持后端数据合并;
7、是否支持集中式数据源信息管理和动态变更;
8、支持的协议是jdbc or mysql,是否支持后端orm框架;
9、是否支持高并发下的计数保护措施,即限流;
对数据库进行分库分表(Sharding化)前,需要开发人员充分了解系统业务逻辑和数据库schema.一个好的建议是绘制一张数据库ER图或领域模型图,以这类图为基础划分shard,直观易行,可以确保开发人员始终保持清醒思路。
以上是关于业界通用数据库分库分表(sharding)研究的主要内容,如果未能解决你的问题,请参考以下文章