如何构建日均千万PV Web站点 (三) Sharding
其实国内许多大型网站为了应对日益复杂的业务场景,通过使用分而治之的手段将整个网站业务分成不同的产品线,比如说国内那些大型购物交易网站它们都将自己的网站首页、商铺、订单、买家、卖家等拆分不同的产品线,分归不同的业务团队负责;
集体到技术,也会根据产品线划分,将一个网站拆分成许多不同的应用,每个应用用独立部署维护。应用之间可以通过一个超链接建立关系(在首页上的导航链接每个都指向不同的应用地址),也可以通过消息队列进行数据分发,当然最多的还是通过访问同一个数据库存储系统来构成一个关联的完整系统 此时的架构如下图所示:
分布式服务,随着业务拆分越来越小,存储系统越来越庞大,应用系统的整体复杂度呈指数级增加,部署维护越来越困难,由于所有应用要和所有数据库系统连接,在数万台服务器规模的网站中,这些连接的数目是服务器规模的平方,导致数据库连接资源不足,拒绝服务。
既然每一个应用系统都需要执行许多相同的业务操作,比如用户管理、商品管理等,那么可以将这些共用的业务提取出来,独立部署。由这些可复用的业务连接数据库,提供共用服务,而应用系统只需要管理用户界面,通过分布式服务调用共用业务服务完成具体业务操作
数据库如何sharding?
数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。一种是按照 不同的表(或者 Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的 垂直(纵向)切分;另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某 种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分。 垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非 常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业 务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也 更小,拆分规则也会比较简单清晰。 水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆 分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂, 后期的数据维护也会更为复杂一些。 当我们某个(或者某些)表的数据量和访问量特别的大,通过垂直切分将其放在独立的 设备上后仍然无法满足性能要求,这时候我们就必须将垂直切分和水平切分相结合,先垂直 切分,然后再水平切分,才能解决这种超大型表的性能问题。 下面我们就针对垂直、水平以及组合切分这三种数据切分方式的架构实现及切分后数据 的整合进行相应的分析。 14.2 数据的垂直切分 我们先来看一下,数据的垂直切分到底是如何一个切分法的。数据的垂直切分,也可以 称之为纵向切分。将数据库想象成为由很多个一大块一大块的“数据块”(表)组成,我们 垂直的将这些 “数据块”切开,然后将他们分散到多台数据库主机上面。这样的切分方法就 是一个垂直(纵向)的数据切分。 一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一 个功能模块所需要的数据对应到数据库中就是一个或者多个表。而在架构设计中,各个功能 模块相互之间的交互点越统一越少,系统的耦合度就越低,系统各个模块的维护性以及扩展 性也就越好。这样的系统,实现数据的垂直切分也就越容易。 当我们的功能模块越清晰,耦合度越低,数据垂直切分的规则定义也就越容易。完全可 以根据功能模块来进行数据的切分,不同功能模块的数据存放于不同的数据库主机中,可以 很容易就避免掉跨数据库的 Join 存在,同时系统架构也非常的清晰.
当然,很难有系统能够做到所有功能模块所使用的表完全独立,完全不需要访问对方的 表或者需要两个模块的表进行 Join 操作。这种情况下,我们就必须根据实际的应用场景进 行评估权衡。决定是迁就应用程序将需要 Join 的表的相关某快都存放在同一个数据库中, 还是让应用程序做更多的事情,也就是程序完全通过模块接口取得不同数据库中的数据, 然 后在程序中完成 Join 操作。 一般来说,如果是一个负载相对不是很大的系统,而且表关联又非常的频繁,那可能数 据库让步,将几个相关模块合并在一起减少应用程序的工作的方案可以减少较多的工作量, 是一个可行的方案。 当然,通过数据库的让步,让多个模块集中共用数据源,实际上也是简介的默许了各模 块架构耦合度增大的发展,可能会让以后的架构越来越恶化。尤其是当发展到一定阶段之后 , 发现数据库实在无法承担这些表所带来的压力,不得不面临再次切分的时候,所带来的架构 改造成本可能会远远大于最初的时候。 所以,在数据库进行垂直切分的时候,如何切分,切分到什么样的程度,是一个比较考 验人的难题。只能在实际的应用场景中通过平衡各方面的成本和收益,才能分析出一个真正 适合自己的拆分方案。 比如在本书所使用示例系统的 example 数据库,我们简单的分析一下,然后再设计一 个简单的切分规则,进行一次垂直垂直拆分。 系统功能可以基本分为四个功能模块:用户,群组消息,相册以及事件,
分别对应为如 下这些表: 1. 用户模块表:user,user_profile,user_group,user_photo_album 2. 群组讨论表:groups,group_message,group_message_content,top_message 3. 相册相关表:photo,photo_album,photo_album_relation,photo_comment
4. 事件信息表:event 初略一看,没有哪一个模块可以脱离其他模块独立存在,模块与模块之间都存在着关系 , 莫非无法切分? 当然不是,我们再稍微深入分析一下,可以发现,虽然各个模块所使用的表之间都有关 联,但是关联关系还算比较清晰,也比较简单。 群组讨论模块和用户模块之间主要存在通过用户或者是群组关系来进行关联。一般 关联的时候都会是通过用户的 id 或者 nick_name 以及 group 的 id 来进行关 联,通过模块之间的接口实现不会带来太多麻烦; 相册模块仅仅与用户模块存在通过用户的关联。这两个模块之间的关联基本就有通 过用户 id 关联的内容,简单清晰,接口明确; 事件模块与各个模块可能都有关联,但是都只关注其各个模块中对象的 ID信息 , 同样可以做到很容易分拆。 所以,我们第一步可以将数据库按照功能模块相关的表进行一次垂直拆分,每个模块所 涉及的表单独到一个数据库中,模块与模块之间的表关联都在应用系统端通过藉口来处理。 如下图所示:
通过这样的垂直切分之后,之前只能通过一个数据库来提供的服务,就被分拆成四个数 据库来提供服务,服务能力自然是增加几倍了。
垂直切分的优点
1、数据库的拆分简单明了,拆分规则明确;
2、应用程序模块清晰明确,整合容易;
3、数据维护方便易行,容易定位;
垂直切分的缺点
1、部分表关联无法在数据库级别完成,需要在程序中完成;
2、对于访问极其频繁且数据量超大的表仍然存在性能平静,不一定能满足要求;
3、事务处理相对更为复杂;
4、切分达到一定程度之后,扩展性会遇到限制;
5、过读切分可能会带来系统过渡复杂而难以维护。
针对于垂直切分可能遇到数据切分及事务问题,在数据库层面实在是很难找到一个较好 的处理方案。实际应用案例中,数据库的垂直切分大多是与应用系统的模块相对应,同一个 模块的数据源存放于同一个数据库中,可以解决模块内部的数据关联问题。而模块与模块之间,则通过应用程序以服务接口方式来相互提供所需要的数据。虽然这样做在数据库的总体 操作次数方面确实会有所增加,但是在系统整体扩展性以及架构模块化方面,都是有益的。 可能在某些操作的单次响应时间会稍有增加,但是系统的整体性能很可能反而会有一定的提 升。而扩展瓶颈问题,就只能依靠下一节将要介绍的数据水平切分架构来解决了。
数据的水平切分
上面一节分析介绍了数据的垂直切分,这一节再分析一下数据的水平切分。数据的垂直 切分基本上可以简单的理解为按照表按照模块来切分数据,而水平切分就不再是按照表或者 是功能模块来切分了。一般来说,简单的水平切分主要是将某个访问极其平凡的表再按照某 个字段的某种规则来分散到多个表之中,每个表中包含一部分数据。 简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些 行切分到一个数据库,而另外的某些行又切分到其他的数据库中。当然,为了能够比较容易的判定各行数据被切分到哪个数据库中了,切分总是都需要按照某种特定的规则来进行的。 如根据某个数字类型字段基于特定数目取模,某个时间类型字段的范围,或者是某个字符类 型字段的 hash 值。如果整个系统中大部分核心表都可以通过某个字段来进行关联,那这个 字段自然是一个进行水平分区的上上之选了,当然,非常特殊无法使用就只能另选其他了。 一般来说,像现在互联网非常火爆的 Web2.0 类型的网站,基本上大部分数据都能够通 过会员用户信息关联上,可能很多核心表都非常适合通过会员 ID 来进行数据的水平切分。 而像论坛社区讨论系统,就更容易切分了,非常容易按照论坛编号来进行数据的水平切分。 切分之后基本上不会出现各个库之间的交互。 如我们的示例系统,所有数据都是和用户关联的,那么我们就可以根据用户来进行水平 拆分,将不同用户的数据切分到不同的数据库中。当然,唯一有点区别的是用户模块中的 groups 表和用户没有直接关系,所以 groups 不能根据用户来进行水平拆分。对于这种特 殊情况下的表,我们完全可以独立出来,单独放在一个独立的数据库中。其实这个做法可以 说是利用了前面一节所介绍的 “数据的垂直切分”方法,我将在下一节中更为详细的介绍这 种垂直切分与水平切分同时使用的联合切分方法。 所以,对于我们的示例数据库来说,大部分的表都可以根据用户 ID 来进行水平的切分 。 不同用户相关的数据进行切分之后存放在不同的数据库中。如将所有用户 ID 通过 2 取模 然后分别存放于两个不同的数据库中。每个和用户 ID 关联上的表都可以这样切分。这样, 基本上每个用户相关的数据,都在同一个数据库中,即使是需要关联,也可以非常简单的关 联上。 我们可以通过下图来更为直观的展示水平切分相关信息:
水平切分的优点
1、表关联基本能够在数据库端全部完成;
2、不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
3、应用程序端整体架构改动相对较少; 事务处理相对简单;
4、只要切分规则能够定义好,基本上较难遇到扩展性限制;
水平切分的缺点
1、切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;
2、后期数据的维护难度有所增加,人为手工定位数据更困难;
3、应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
垂直与水平联合切分的使用
上面两节内容中,我们分别,了解了“垂直”和“水平”这两种切分方式的实现以及切 分之后的架构信息,同时也分析了两种架构各自的优缺点。但是在实际的应用场景中,除了 那些负载并不是太大,业务逻辑也相对较简单的系统可以通过上面两种切分方法之一来解决 扩展性问题之外,恐怕其他大部分业务逻辑稍微复杂一点,系统负载大一些的系统,都无法 通过上面任何一种数据的切分方法来实现较好的扩展性,而需要将上述两种切分方法结合使 用,不同的场景使用不同的切分方法。 在这一节中,我将结合垂直切分和水平切分各自的优缺点,进一步完善我们的整体架构 , 让系统的扩展性进一步提高。 一般来说,我们数据库中的所有表很难通过某一个(或少数几个)字段全部关联起来, 所以很难简单的仅仅通过数据的水平切分来解决所有问题。而垂直切分也只能解决部分问 题,对于那些负载非常高的系统,即使仅仅只是单个表都无法通过单台数据库主机来承担其 负载。我们必须结合“垂直”和“水平”两种切分方式同时使用,充分利用两者的优点,避 开其缺点。 每一个应用系统的负载都是一步一步增长上来的,在开始遇到性能瓶颈的时候,大多数 架构师和 DBA 都会选择先进行数据的垂直拆分,因为这样的成本最先,最符合这个时期所追 求的最大投入产出比。然而,随着业务的不断扩张,系统负载的持续增长,在系统稳定一段 时期之后,经过了垂直拆分之后的数据库集群可能又再一次不堪重负,遇到了性能瓶颈。 这时候我们该如何抉择?是再次进一步细分模块呢,还是寻求其他的办法来解决?如果 我们再一次像最开始那样继续细分模块,进行数据的垂直切分,那我们可能在不久的将来, 又会遇到现在所面对的同样的问题。而且随着模块的不断的细化,应用系统的架构也会越来 越复杂,整个系统很可能会出现失控的局面。 这时候我们就必须要通过数据的水平切分的优势,来解决这里所遇到的问题。而且, 我们完全不必要在使用数据水平切分的时候,推倒之前进行数据垂直切分的成果,而是在其基 础上利用水平切分的优势来避开垂直切分的弊端,解决系统复杂性不断扩大的问题。而水平 拆分的弊端(规则难以统一)也已经被之前的垂直切分解决掉了,让水平拆分可以进行的得心应手。 对于我们的示例数据库,假设在最开始,我们进行了数据的垂直切分,然而随着业务的 不断增长,数据库系统遇到了瓶颈,我们选择重构数据库集群的架构。如何重构?考虑到之 前已经做好了数据的垂直切分,而且模块结构清晰明确。而业务增长的势头越来越猛,即使 现在进一步再次拆分模块,也坚持不了太久。我们选择了在垂直切分的基础上再进行水平拆分。 在经历过垂直拆分后的各个数据库集群中的每一个都只有一个功能模块,而每个功能模 块中的所有表基本上都会与某个字段进行关联。如用户模块全部都可以通过用户ID进行切分,群组讨论模块则都通过群组ID来切分,相册模块则根据相册ID来进切分,最后的事 件通知信息表考虑到数据的时限性(仅仅只会访问最近某个事件段的信息),则考虑按时间 来切分。 下图展示了切分后的整个架构:
实际上,在很多大型的应用系统中,垂直切分和水平切这两种数据的切分方法基本上都 是并存的,而且经常在不断的交替进行,以不断的增加系统的扩展能力。我们在应对不同的 应用场景的时候,也需要充分考虑到这两种切分方法各自的局限,以及各自的优势,在不同的时期(负载压力)使用不同的结合方式。
联合切分的优点
1、可以充分利用垂直切分和水平切分各自的优势而避免各自的缺陷;
2、让系统扩展性得到最大化提升;
联合切分的缺点
1、数据库系统架构比较复杂,维护难度更大;
2、应用程序架构也相对更复杂。
关于数据库如何sharding详情请参考<<MySQL性能调优与架构设计>>