分库分表
数据切分
通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。数据的切分同时还能够提高系统的总体可用性,由于单台设备Crash之后,仅仅有总体数据的某一部分不可用,而不是全部的数据。
切分模式
数据的切分(Sharding)依据其切分规则的类型。能够分为两种切分模式。
依照不同的表(或者Schema)来切分到不同的数据库(主机)之上,这样的切能够称之为数据的垂直(纵向)切分。另外一种则是依据表中的数据的逻辑关系,将同一个表中的数据依照某种条件拆分到多台数据库(主机)上面。这样的切分称之为数据的水平(横向)切分。
当我们某个(或者某些)表的数据量和訪问量特别的大,通过垂直切分将其放在独立的设备上后仍然无法满足性能要求,这时候我们就必须将垂直切分和水平切分相结合。先垂直切分,然后再水平切分。才干解决这样的超大型表的性能问题。
垂直切分
垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低。相互影响非常小,业务逻辑非常清晰的系统。在这样的系统中,能够非常easy做到将不同业务模块所使用的表分拆到不同的数据库中。依据不同的表来进行拆分。对应用程序的影响也更小,拆分规则也会比較简单清晰。
数据的垂直切分。也能够称之为纵向切分。将数据库想象成为由非常多个一大块一大块的“数据块”(表)组成。我们垂直的将这些“数据块”切开,然后将他们分散到多台数据库主机上面。这样的切分方法就是一个垂直(纵向)的数据切分。
一个架构设计较好的应用系统。其总体功能肯定是由非常多个功能模块所组成的。而每一个功能模块所须要的数据对应到数据库中就是一个或者多个表。
而在架构设计中,各个功能模块相互之间的交互点越统一、越少,系统的耦合度就越低,系统各个模块的维护性以及扩展性也就越好。这样的系统。实现数据的垂直切分也就越容易。
当我们的功能模块越清晰,耦合度越低,数据垂直切分的规则定义也就越简单。全然能够依据功能模块来进行数据的切分,不同功能模块的数据存放于不同的数据库主机中,能够非常easy就避免掉跨数据库的Join存在。同一时候系统架构也非常的清晰。
当然,很少有系统能够做到全部功能模块所使用的表全然独立,全然不需要访问对方的表或者需要两个模块的表进行Join操作。这样的情况下,我们就必须依据实际的应用场景进行评估权衡。决定是迁就应用程序将需要Join的表的相关模块都存放在同一个数据库中,还是让应用程序做很多其它的事情,也就是程序全然通过模块接口取得不同数据库中的数据,然后在程序中完成Join操作。
一般来说,假设是一个负载相对不是非常大的系统,并且表关联又非常的频繁。那可能数据库让步,将几个相关模块合并在一起降低应用程序工作的方案能够降低较多的工作量,是一个可行的方案。
当然,通过数据库的让步,让多个模块集中共用数据源,实际上也是默许了各模块架构耦合度增大的发展,可能会让以后的架构越来越恶化。尤其是当发展到一定阶段之后,发现数据库实在无法承担这些表所带来的压力。不得不面临再次切分的时候。所带来的架构改造成本可能会远远大于最初的时候。
所以,在数据库进行垂直切分的时候,怎样切分,切分到什么样的程度,是一个比较考验人的难题。只能在实际的应用场景中通过平衡各方面的成本和收益,才能分析出一个真正适合自己的拆分方案。
现在我们使用example数据库,简单的分析一下。然后再设计一个简单的切分规则,进行一次垂直垂直拆分。
系统功能能够基本分为四个功能模块:用户、群组消息、相册以及事件。分别对应为例如以下这些表:
- 用户模块表:user、user_profile、user_group、user_photo_album
- 群组讨论表:groups、group_message、group_message_content、top_message
- 相冊相关表:photo、photo_album、photo_album_relation、photo_comment
- 事件信息表:event
初略一看,没有哪一个模块能够脱离其它模块独立存在,模块与模块之间都存在着关系。莫非无法切分?
当然不是,我们再略微深入分析一下,能够发现,尽管各个模块所使用的表之间都有关联,可是关联关系还算比較清晰,也比較简单。
- 群组讨论模块和用户模块之间主要存在通过用户或者是群组关系来进行关联。一般关联的时候都会是通过用户的id或者nick_name以及group的id来进行关联。通过模块之间的接口实现不会带来太多麻烦。
- 相册模块仅仅与用户模块存在通过用户的关联。这两个模块之间的关联基本就有通过用户id关联的内容。简单清晰,接口明白;
- 事件模块与各个模块可能都有关联,可是都仅仅关注其各个模块中对象的ID信息,能够做到非常简单的分拆。
所以,我们第一步能够将数据库依照功能模块相关的表进行一次垂直拆分。每一个模块所涉及的表单独到一个数据库中,模块与模块之间的表关联都在应用系统端通过接口来处理。例如以下图所看到的:
通过这样的垂直切分之后,之前仅仅能通过一个数据库来提供的服务,就被分拆成四个数据库来提供服务,服务能力自然是增加几倍。
垂直切分的优点:
- 数据库的拆分简单明了,拆分规则明白;
- 应用程序模块清晰明白,整合简单;
- 数据维护方便易行,容易定位。
垂直切分的缺点:
- 部分表关联无法在数据库级别完毕,须要在程序中完毕;
- 对于访问极其频繁且数据量超大的表仍然存在性能瓶颈,不一定能满足要求;
- 事务处理相对更为复杂;
- 切分达到一定程度之后,扩展性会遇到限制;
- 过度切分可能会带来系统过度复杂而难以维护。
针对于垂直切分可能遇到数据切分及事务问题,在数据库层面很难找到一个较好的处理方案。实际应用案例中,数据库的垂直切分大多是与应用系统的模块相对应,同一个模块的数据源存放于同一个数据库中,能够解决模块内部的数据关联问题。而模块与模块之间,则通过应用程序以服务接口方式来相互提供所须要的数据。
尽管这样做在数据库的总体操作次数方面确实会有所添加,可是在系统总体扩展性以及架构模块化方面,都是故意的。可能在某些操作的单次响应时间会稍有添加。可是系统的总体性能非常可能反而会有一定的提升。而扩展瓶颈问题,就只能依靠数据水平切分架构来解决了。
数据的水平切分
水平切分与垂直切分相比, 相对来说略微复杂一些。由于要将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较依据表名来拆分更为复杂,后期的数据维护也会更为复杂一些。
数据的垂直切分基本上能够简单的理解为依照表依照模块来切分数据,而水平切分就不再是依照表或者是功能模块来切分了。一般来说,简单的水平切分主要是将某个訪问极其平庸的表再依照某个字段的某种规则来分散到多个表之中,每一个表中包括一部分数据。
简单来说,我们能够将数据的水平切分理解为是依照数据行的切分。就是将表中的某些行切分到一个数据库,而另外的某些行又切分到其它的数据库中。当然,为了能够比较容易的判定各行数据被切分到哪个数据库中了,切分总是都须要依照某种特定的规则来进行的。
如依据某个数字类型字段基于特定数目取模,某个时间类型字段的范围。或者是某个字符类型字段的hash值。假设整个系统中大部分核心表都能够通过某个字段来进行关联。那这个字段自然是一个进行水平分区的上上之选了,当然,非常特殊无法使用就仅仅能另选其它了。
一般来说,像如今互联网非常火爆的Web2.0类型的站点。基本上大部分数据都能够通过会员用户信息关联上,可能非常多核心表都非常适合通过会员ID来进行数据的水平切分。
而像论坛社区讨论系统,就更容易切分了,非常简单,依照论坛编号来进行数据的水平切分。
切分之后基本上不会出现各个库之间的交互。
如我们的演示样例系统。全部数据都是和用户关联的。那么我们就能够依据用户来进行水平拆分,将不同用户的数据切分到不同的数据库中。当然,唯一有点差别的是用户模块中的groups表和用户没有直接关系。所以groups不能依据用户来进行水平拆分。对于这样的特殊情况下的表,我们全然能够独立出来。单独放在一个独立的数据库中。事实上这个做法能够说是用了“数据的垂直切分”方法。
所以,对于我们的演示样例数据库来说,大部分的表都能够依据用户ID来进行水平的切分。不同用户相关的数据进行切分之后存放在不同的数据库中。如将全部用户ID通过2取模然后分别存放于两个不同的数据库中。
每一个和用户ID关联上的表都能够这样切分。这样,基本上每一个用户相关的数据。都在同一个数据库中,即使是须要关联,也能够非常简单的关联上。
我们能够通过下图来更为直观的展示水平切分相关信息:
水平切分的长处
- 表关联基本能够在数据库端全部完毕;
- 不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
- 应用程序端总体架构修改相对较少;
- 事务处理相对简单;
- 只要切分规则定义好,基本上不会遇到扩展性限制;
水平切分的缺点
- 切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;
- 后期数据的维护难度有所增加,人为手工定位数据更困难;
- 应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
组合切分
一般来说,我们数据库中的全部表非常难通过某一个(或少数几个)字段全部关联起来,所以非常难简单的只通过数据的水平切分来解决全部问题。而垂直切分也仅仅能解决部分问题,对于那些负载非常高的系统,即使只是单个表都无法通过单台数据库主机来承担其负载。
我们必须结合“垂直”和“水平”两种切分方式同一时候使用,充分利用两者的长处,避开其缺点。
每一个应用系统的负载都是一步一步增长上来的,在开始遇到性能瓶颈的时候,大多数架构师和DBA都会选择先进行数据的垂直拆分,由于这样的成本最小,最符合这个时期所追求的最大投入产出比。然而,随着业务的不断扩张,系统负载的持续增长,在系统稳定一段时期之后,经过了垂直拆分之后的数据库集群可能又再一次不堪重负,遇到了性能瓶颈。
这时候我们该怎样抉择?是再次进一步细分模块呢,还是寻求其它的办法来解决?假设我们再一次像最开始那样继续细分模块,进行数据的垂直切分,那我们可能在不久的将来,又会遇到如今所面对的相同的问题。并且随着模块的不断的细化,应用系统的架构也会越来越复杂,整个系统非常可能会出现失控的局面。
这时候我们就必须要通过数据的水平切分的优势,来解决这里所遇到的问题。并且,我们全然不需要在使用数据水平切分的时候,推倒之前进行数据垂直切分的成果,而是在其基础上利用水平切分的优势来避开垂直切分的弊端,解决系统复杂性不断扩大的问题。
而水平拆分的弊端(规则难以统一)也已经被之前的垂直切分解决掉了。让水平拆分能够进行的得心应手。
对于我们的演示样例数据库,假设在最开始,我们进行了数据的垂直切分,然而随着业务的不断增长,数据库系统遇到了瓶颈,我们选择重构数据库集群的架构。怎样重构?考虑到之前已经做好了数据的垂直切分,并且模块结构清晰明白。
而业务增长的势头越来越猛。即使如今进一步再次拆分模块,也坚持不了太久。
我们选择了在垂直切分的基础上再进行水平拆分。
在经历过垂直拆分后的各个数据库集群中的每一个都仅仅有一个功能模块。而每一个功能模块中的全部表基本上都会与某个字段进行关联。如用户模块全部都能够通过用户ID进行切分,群组讨论模块则都通过群组ID来切分。相册模块则依据相册ID来进切分。最后的事件通知信息表考虑到数据的时限性(只会访问近期某个时间段的信息),则考虑按时间来切分。
下图展示了切分后的整个架构:
实际上,在非常多大型的应用系统中,垂直切分和水平切分这两种数据的切分方法基本上都是并存的。并且经常在不断的交替进行,以不断的添加系统的扩展能力。我们在应对不同的应用场景的时候,也须要充分考虑到这两种切分方法各自的局限,以及各自的优势。在不同的时期(负载压力)使用不同的结合方式。
联合切分的长处
- 能够充分利用垂直切分和水平切分各自的优势而避免各自的缺陷;
- 让系统扩展性得到最大化提升。
联合切分的缺点
- 数据库系统架构比較复杂。维护难度更大。
- 应用程序架构也相对更复杂。
数据切分及整合方案
数据库中的数据在经过垂直和(或)水平切分被存放在不同的数据库主机之后,应用系统面临的最大问题就是怎样来让这些数据源得到较好的整合。接下来我们主要分析能够使用的各种能够帮助我们实现数据切分以及数据整合的总体解决方式。
数据的整合非常难依靠数据库本身来达到这个效果,尽管MySQL存在Federated存储引擎,能够解决部分相似的问题。可是在实际应用场景中却非常难较好的运用。那我们该怎样来整合这些分散在各个MySQL主机上面的数据源呢?
总的来说,存在两种解决思路:
- 在每一个应用程序模块中配置管理自己须要的一个(或者多个)数据源。直接访问各个数据库,在模块内完毕数据的整合;
- 通过中间代理层来统一管理全部的数据源。后端数据库集群对前端应用程序透明;
可能大多数人在面对上面这两种解决思路的时候都会倾向于选择第二种,尤其是系统不断变得庞大复杂的时候。
确实,这是一个非常正确的选择,尽管短期内须要付出的成本可能会相对更大一些,可是对整个系统的扩展性来说,是非常有帮助的。
所以,这里重点分析一下在第二种解决思路中的一些解决方式。
自行开发中间代理层
在决定选择通过数据库的中间代理层来解决数据源整合的架构方向之后,有不少公司(或者企业)选择了通过自行开发符合自身应用特定场景的代理层应用程序。
通过自行开发中间代理层能够最大程度的应对自身应用的特点。最大化的定制非常多个性化需求,在面对变化的时候也能够灵活的应对。这应该说是自行开发代理层最大的优势了。
当然,选择自行开发,享受让个性化定制最大化的乐趣的同时,自然也须要投入很多其它的成本来进行前期研发以及后期的持续升级改进工作。并且本身的技术门槛可能也比简单的Web应用要更高一些。所以,在决定选择自行开发之前,还是须要进行比较全面的评估为好。
由于自行开发很多时候考虑的是怎样更好的适应自身应用系统,应对自身的业务场景,所以这里也不好分析太多。
利用MySQLProxy实现数据切分及整合
MySQLProxy是MySQL官方提供的一个数据库代理层产品,和MySQLServer一样,相同是一个基于GPL开源协议的开源产品。可用来监视、分析或者传输他们之间的通讯信息。他的灵活性使得你可以最大限度的使用它,眼下具备的功能主要有连接路由,Query分析,Query过滤和修改,负载均衡。以及主要的HA机制等。
实际上,MySQLProxy本身并不具有上述全部的这些功能。而是提供了实现上述功能的基础。
要实现这些功能,还须要通过自行编写LUA脚本来实现。
MySQLProxy实际上是在Client请求与MySQLServer之间建立了一个连接池。全部Client请求都是发向MySQLProxy,然后经由MySQLProxy进行对应的分析。推断出是读操作还是写操作,分发至对应的MySQLServer上。对于多节点Slave集群,也能够起做到负载均衡的效果。以下是MySQLProxy的基本架构图:
通过上面的架构简图,我们能够非常清晰的看出MySQLProxy在实际应用中所处的位置,以及能做的基本事情。
关于MySQLProxy更为具体的实施细则在MySQL官方文档中有非常具体的介绍和演示样例。
数据切分与整合可能存在的问题
在实施数据切分方案之前,我们可能遇到的问题主要会有以下几点:
引入分布式事务的问题
一旦数据进行切分被分别存放在多个MySQLServer中之后,无论我们的切分规则设计的多么的完美(实际上并不存在完美的切分规则),都可能造成之前的某些事务所涉及到的数据已经不在同一个MySQLServer中了。
在这样的场景下,假设我们的应用程序仍然依照老的解决方式。那么势必须要引入分布式事务来解决。而在MySQL各个版本号中,仅仅有从MySQL5.0开始以后的各个版本号才开始对分布式事务提供支持,并且眼下仅有Innodb提供分布式事务支持。不仅如此。即使我们刚好使用了支持分布式事务的MySQL版本号。同时也是使用的Innodb存储引擎,分布式事务本身对于系统资源的消耗是非常大的,性能本身也并非太高,并且引入分布式事务本身在异常处理方面就会带来较多比较难控制的因素。
事实上我们能够能够通过一个变通的方法来解决这样的问题。首先须要考虑的一件事情就是:是否数据库是唯一一个能够解决事务的地方呢?事实上并非这样的,我们完全可以结合数据库以及应用程序两者来共同解决。各个数据库解决自己身上的事务,然后通过应用程序来控制多个数据库上面的事务。
只要我们愿意,完全能够将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务。并通过应用程序来总控各个小事务。
当然,这样作的要求就是应用程序必须要有足够的健壮性。当然也会给应用程序带来一些技术难度。
跨节点Join的问题
数据切分之后,可能会造成有些老的 Join 语句无法继续使用。由于Join使用的数据源可能被切分到多个 MySQLServer 中了。
这个问题从MySQL数据库角度来看,假设必须要在数据库端来直接解决的话,只能通过MySQL一种特殊的存储引擎Federated来解决了。Federated存储引擎是MySQL解决相似于Oracle的DBLink之类问题的解决方式。
和 Oracle DBLink 的主要差别在于 Federated 会保存一份远端表结构的定义信息在本地。咋一看,Federated确实是解决跨节点Join非常好的解决方式。可是我们还应该清晰一点,假设远端的表结构发生了变更,本地的表定义信息是不会跟着发生对应变化的。假设在更新远端表结构的时候并没有更新本地的Federated表定义信息,就可能造成Query执行出错,无法得到正确的结果。
针对这类问题,主要通过应用程序来进行处理,先在驱动表所在的 MySQLServer 中取出对应的驱动结果集,然后依据驱动结果集再到被驱动表所在的 MySQLServer 中取出对应的数据,但是会对性能有一定的负面影响。可是除了此法,基本上没有太多其它更好的解决的方法了。
并且,由于数据库通过较好的扩展之后,每台 MySQLServer 的负载就能够得到较好的控制。单纯针对单条Query来说,其响应时间可能比不切分之前要提高一些,所以性能方面所带来的负面影响也并非太大。更何况,像这样的需要跨节点Join的需求也并非太多。相对于总体性能而言,可能也仅仅是非常小一部分而已。所以为了总体性能的考虑,偶尔牺牲那么一点点,事实上是值得的。毕竟系统优化本身就是存在非常多取舍和平衡的过程。
跨节点合并排序分页问题
一旦进行了数据的水平切分之后,可能就并不只有跨节点Join无法正常执行,有些排序分页的Query语句的数据源可能也会被切分到多个节点。这样造成的直接后果就是这些排序分页Query无法继续正常执行。这和跨节点Join是一个道理。数据源存在于多个节点上,要通过一个Query来解决,就和跨节点Join是一样的操作。相同Federated也能够部分解决。当然存在的风险也一样。
怎样解决?解决的思路大体上和跨节点Join的解决相似,可是有一点和跨节点Join不太一样。Join非常多时候都有一个驱动与被驱动的关系。所以Join本身涉及到的多个表之间的数据读取一般都会存在一个顺序关系。可是排序分页就不太一样了,排序分页的数据源基本上能够说是一个表(或者一个结果集)。本身并不存在一个顺序关系,所以在从多个数据源取数据的过程是全然能够并行的。
排序分页数据的取数效率我们能够做的比跨库Join更高,带来的性能损失相对的要更小,在有些情况下可能比在原来未进行数据切分的数据库中效率更高了。
当然,不论是跨节点Join还是跨节点排序分页。都会使我们的应用server消耗很多其它的资源,尤其是内存资源,由于我们在读取访问以及合并结果集的这个过程须要比原来处理很多其它的数据。
通过数据切分技术将一个大的 MySQL Server 切分成多个小的 MySQL Server ,既攻克了写入性能瓶颈问题,同时也再一次提升了整个数据库集群的扩展性。不论是通过垂直切分,还是水平切分。都能够让系统遇到瓶颈的可能性更小。尤其是当我们使用垂直和水平相结合的切分方法之后,理论上将不会再遇到扩展瓶颈了。
参考连接