zoukankan      html  css  js  c++  java
  • MySQL具体解释(15)-----------海量数据解说

    第1章  引言

    随着互联网应用的广泛普及,海量数据的存储和訪问成为了系统设计的瓶颈问题。

    对于一个大型的互联网应用。每天几十亿的PV无疑对数据库造成了相当高的负载。对于系统的稳定性和扩展性造成了极大的问题。通过数据切分来提高站点性能,横向扩展数据层已经成为架构研发人员首选的方式。水平切分数据库。能够减少单台机器的负载,同一时候最大限度的减少了了宕机造成的损失。通过负载均衡策略,有效的减少了单台机器的訪问负载,减少了宕机的可能性;通过集群方案,攻克了数据库宕机带来的单点数据库不能訪问的问题;通过读写分离策略更是最大限度了提高了应用中读取(Read)数据的速度和并发量。眼下国内的大型互联网应用中。大量的採用了这种数据切分方案,Taobao,Alibaba,Tencent,它们大都实现了自己的分布式数据訪问层(DDAL)。以实现方式和实现的层次来划分,大概分为两个层次(Java应用为例):JDBC层的封装,ORM框架层的实现。

    就JDBC层的直接封装而言。如今国内发展较好的一个项目是被称作“变形虫”(Amoeba)的项目,由阿里集团的研究院开发,如今仍然处于測试阶段(beta版),其执行效率和生产时效性有待考究。

    就ORM框架层的实现而言,比方Taobao的基于ibatis和Spring的的分布式数据訪问层,已有多年的应用,执行效率和生产实效性得到了开发者和用户的肯定。本文就是以ORM框架层为基础而实现的分布式数据訪问层。本课题的难点在于分库后,路由规则的制定和选择以及后期的扩展性,比方:怎样做到用最少的数据迁移量,达到扩充数据库容量(添加机器节点)的目的。核心问题将环绕数据库分库分表的路由规则和负载均衡策略展开。

    第2章 基本原理和概念

    2.1基本原理:

    人类认知问题的过程总是这种:what(什么)-?why(为什么)-?how(怎么

    做),接下来,本文将就这三个问题展开讨论和研究:

    2.1.1什么是数据切分

    "Shard" 这个词英文的意思是"碎片"。而作为数据库相关的技术用语,似乎最早见于大型多人在线角色扮演游戏中。"Sharding" 姑且称之为"分片"。Sharding 不是一门新技术,而是一个相对简朴的软件理念。

    众所周知,MySQL 5 之后才有了数据表分区功能,那么在此之前,非常多 MySQL 的潜在用户都对 MySQL 的扩展性有所顾虑。而是否具备分区功能就成了衡量一个数据库可扩展性与否的一个关键指标(当然不是唯一指标)。数据库扩展性是一个永恒的话题,MySQL 的推广者常常会被问到:如在单一数据库上处理应用数据捉襟见肘而须要进行分区化之类的处理,是怎样办到的呢? 答案是:Sharding。  Sharding 不是一个某个特定数据库软件附属的功能,而是在详细技术细节之上的抽象处理,是水平扩展(Scale Out,亦或横向扩展、向外扩展)的解决方式,其主要目的是为突破单节点数据库server的 I/O 能力限制,解决数据库扩展性问题。

    通过一系列的切分规则将数据水平分布到不同的DB或table中,在通过对应的DB路由 或者 table路由规则找到须要查询的详细的DB或者table,以进行Query操作。这里所说的“sharding”一般是指“水平切分”, 这也是本文讨论的重点。详细将有什么样的切分方式呢和路由方式呢?行文至此,读者难免有所疑问,接下来举个简单的样例:我们针对一个Blog应用中的日志来说明,比方日志文章(article)表有例如以下字段:


    article_id(int),title(varchar(128)),content(varchar(1024)),user_id(int)

    面对这种一个表,我们如何切分呢?如何将这种数据分布到不同的数据库中的表中去呢?事实上分析blog的应用。我们不难得出这种结论:blog的应用中,用户分为两种:浏览者和blog的主人。

    浏览者浏览某个blog。实际上是在一个特定的用户的blog下进行浏览的,而blog的主人管理自己的blog。也相同是在特定的用户blog下进行操作的(在自己的空间下)。

    所谓的特定的用户。用数据库的字段表示就是“user_id”。就是这个“user_id”。它就是我们须要的分库的根据和规则的基础。

    我们能够这样做。将user_id为 1~10000的全部的文章信息放入DB1中的article表中,将user_id为10001~20000的全部文章信息放入DB2中的 article表中。以此类推。一直到DBn。 这样一来,文章数据就非常自然的被分到了各个数据库中。达到了数据切分的目的。接下来要解决的问题就是如何找到详细的数据库呢?事实上问题也是简单明显的,既然分库的时候我们用到了区分字段user_id。那么非常自然,数据库路由的过程当然还是少不了 user_id的。考虑一下我们刚才呈现的blog应用,无论是訪问别人的blog还是管理自己的blog,总之我都要知道这个blog的用户是谁吧。也就是我们知道了这个blog的user_id,就利用这个user_id,利用分库时候的规则。反过来定位详细的数据库,比方user_id是234,利用该才的规则,就应该定位到DB1,假如user_id是12343。利用该才的规则,就应该定位到DB2。以此类推。利用分库的规则,反向的路由到详细的DB,这个过程我们称之为“DB路由”。

    当然考虑到数据切分的DB设计必定是很规,不正统的DB设计。那么什么样的DB设计是正统的DB设计呢?

    我们寻常规规矩矩用的基本都是。寻常我们会自觉的依照范式来设计我们的数据库。负载高点可能考虑使用相关的Replication机制来提高读写的吞吐和性能。这可能已经能够满足非常多需求,但这套机制自身的缺陷还是比較显而易见的(下文会提及)。

    上面提到的“自觉的依照范式设计”。考虑到数据切分的DB设计,将违背这个通常的规矩和约束,为了切分,我们不得不在数据库的表中出现冗余字段。用作区分字段或者叫做分库的标记字段。比方上面的article的样例中的user_id这种字段(当然。刚才的样例并没有非常好的体现出user_id的冗余性,由于user_id这个字段即使就是不分库。也是要出现的,算是我们捡了廉价吧)。当然冗余字段的出现并不仅仅是在分库的场景下才出现的。在非常多大型应用中。冗余也是必须的,这个涉及到高效DB的设计,本文不再赘述。

    2.1.2为什么要数据切分

    上面对什么是数据切分做了个概要的描写叙述和解释,读者可能会疑问。为什么须要数据切分呢?像 Oracle这样成熟稳定的数据库,足以支撑海量数据的存储与查询了?为什么还须要数据切片呢?的确。Oracle的DB确实非常成熟非常稳定,可是高昂的使用费用和高端的硬件支撑不是每个公司能支付的起的。

    试想一下一年几千万的使用费用和动辄上千万元的小型机作为硬件支撑。这是一般公司能支付的起的吗?即使就是能支付的起。假如有更好的方案,有更便宜且水平扩展性能更好的方案,我们为什么不选择呢?

    可是,事情总是不尽人意。

    寻常我们会自觉的依照范式来设计我们的数据库。负载高点可能考虑使用相关的Replication机制来提高读写的吞吐和性能,这可能已经能够满足非常多需求。但这套机制自身的缺陷还是比較显而易见的。

    首先它的有效非常依赖于读操作的比例,Master往往会成为瓶颈所在,写操作须要顺序排队来运行,过载的话Master首先扛不住,Slaves的数据同步的延迟也可能比較大。并且会大大耗费CPU的计算能力,由于write操作在Master上运行以后还是须要在每台slave机器上都跑一次。这时候 Sharding可能会成为鸡肋了。 Replication搞不定,那么为什么Sharding能够工作呢?道理非常easy,由于它能够非常好的扩展。我们知道每台机器不管配置多么好它都有自身的物理上限,所以当我们应用已经能触及或远远超出单台机器的某个上限的时候。我们惟有寻找别的机器的帮助或者继续升级的我们的硬件。但常见的方案还是横向扩展, 通过加入很多其它的机器来共同承担压力。我们还得考虑当我们的业务逻辑不断增长,我们的机器能不能通过线性增长就能满足需求?Sharding能够轻松的将计算,存储。I/O并行分发到多台机器上。这样能够充分利用多台机器各种处理能力。同一时候能够避免单点失败,提供系统的可用性。进行非常好的错误隔离。

    综合以上因素。数据切分是非常有必要的,且我们在此讨论的数据切分也是将MySql作为背景的。基于成本的考虑。非常多公司也选择了Free且Open的MySql。

    对MySql有所了解的开发者可能会知道,MySQL 5 之后才有了数据表分区功能,那么在此之前,非常多 MySQL 的潜在用户都对 MySQL 的扩展性有所顾虑。而是否具备分区功能就成了衡量一个数据库可扩展性与否的一个关键指标(当然不是唯一指标)。数据库扩展性是一个永恒的话题,MySQL 的推广者常常会被问到:如在单一数据库上处理应用数据捉襟见肘而须要进行分区化之类的处理,是怎样办到的呢? 答案也是Sharding,也就是我们所说的数据切分方案。

        我们用免费的MySQL和便宜的Server甚至是PC做集群。达到小型机+大型商业DB的效果,降低大量的资金投入,降低运营成本。何乐而不为呢?所以。我们选择Sharding,拥抱Sharding。

    2.1.3怎么做到数据切分

    说到数据切分,再次我们讲对数据切分的方法和形式进行比較具体的阐述和说明。

    数据切分能够是物理 上的。对数据通过一系列的切分规则将数据分布到不同的DBserver上。通过路由规则路由訪问特定的数据库。这样一来每次訪问面对的就不是单台server了。而是N台server。这样就能够减少单台机器的负载压力。

    据切分也能够是数据库内的 ,对数据通过一系列的切分规则。将数据分布到一个数据库的不同表中,比方将article分为article_001,article_002等子表,若干个子表水平拼合有组成了逻辑上一个完整的article表。这样做的目的事实上也是非常easy的。 举个样例说明,比方article表中如今有5000w条数据。此时我们须要在这个表中添加(insert)一条新的数据,insert完成后。数据库会针对这张表又一次建立索引。5000w行数据建立索引的系统开销还是不容忽视的。可是反过来。假如我们将这个表分成100 个table呢。从article_001一直到article_100。5000w行数据平均下来。每一个子表里边就仅仅有50万行数据,这时候我们向一张仅仅有50w行数据的table中insert数据后建立索引的时间就会呈数量级的下降,极大了提高了DB的执行时效率,提高了DB的并发量。当然分表的优点还不知这些。还有诸如写操作的锁操作等。都会带来非常多显然的优点。

    综上,分库减少了单点机器的负载;分表,提高了数据操作的效率。尤其是Write操作的效率。 行文至此我们依旧没有涉及到怎样切分的问题。接下来。我们将对切分规则进行详尽的阐述和说明。

    上文中提到,要想做到数据的水平切分,在每个表中都要有相冗余字符 作为切分根据和标记字段,通常的应用中我们选用user_id作为区分字段。基于此就有例如以下三种分库的方式和规则: (当然还能够有其它的方式)

    按号段分:

    (1) user_id为区分,1~1000的相应DB1。1001~2000的相应DB2,以此类推。

    长处:可部分迁移

    缺点:数据分布不均

    (2)hash取模分:

    对user_id进行hash(或者假设user_id是数值型的话直接使用user_id 的值也可),然后用一个特定的数字,比方应用中须要将一个数据库切分成4个数据库的话,我们就用4这个数字对user_id的hash值进行取模运算,也就是user_id%4,这种话每次运算就有四种可能:结果为1的时候相应DB1;结果为2的时候相应DB2;结果为3的时候相应DB3;结果为0的时候相应DB4,这样一来就很均匀的将数据分配到4个DB中。

    长处:数据分布均匀

    缺点:数据迁移的时候麻烦。不能依照机器性能分摊数据

    (3)在认证库中保存数据库配置

    就是建立一个DB,这个DB单独保存user_id到DB的映射关系。每次訪问数据库的时候都要先查询一次这个数据库,以得到详细的DB信息,然后才干进行我们须要的查询操作。

    长处:灵活性强,一对一关系

    缺点:每次查询之前都要多一次查询,性能大打折扣

    以上就是通常的开发中我们选择的三种方式。有些复杂的项目中可能会混合使用这三种方式。 通过上面的描写叙述,我们对分库的规则也有了简单的认识和了解。当然还会有更好更完好的分库方式,还须要我们不断的探索和发现。

    第3章 本课题研究的基本轮廓

    上面的文字。我们依照人类认知事物的规律,what?why?

    how这种方式阐述了数据库切分的一些概念和意义以及对一些常规的切分规则做了概要的介绍。

    本课题所讨论的分布数据层并不只如此,它是一个完整的数据层解决方式。它究竟是什么样的呢?接下来的文字。我将具体阐述本研究课题的完整思想和实现方式。

    分布式数据方案提供功能例如以下:

    (1)提供分库规则和路由规则(RouteRule简称RR),将上面的说明中提到的三中切分规则直接内嵌入本系统。具体的嵌入方式在接下来的内容中进行具体的说明和论述;

    (2)引入集群(Group)的概念。保证数据的高可用性;

    (3)引入负载均衡策略(LoadBalancePolicy简称LB);

    (4)引入集群节点可用性探測机制。对单点机器的可用性进行定时的侦測,以保证LB策略的正确实施,以确保系统的高度稳定性。

    (5)引入读/写分离,提高数据的查询速度。

    不过分库分表的数据层设计也是不够完好的。当某个节点上的DBserver出现了宕机的情况的时候,会是什么样的呢?是的,我们採用了数据库切分方案,也就是说有N太机器组成了一个完整的DB ,如果有一台机器宕机的话。也不过一个DB的N分之中的一个的数据不能訪问而已,这是我们能接受的,起码比切分之前的情况好非常多了,总不至于整个DB都不能訪问。一般的应用中,这种机器故障导致的数据无法訪问是能够接受的。如果我们的系统是一个高并发的电子商务站点呢?单节点机器宕机带来的经济损失是非常严重的。也就是说。如今我们这种方案还是存在问题的。容错性能是经不起考验的。当然了。问题总是有解决方式的。我们引入集群的概念。在此我称之为Group。也就是每个分库的节点我们引入多台机器,每台机器保存的数据是一样的,普通情况下这多台机器分摊负载,当出现宕机情况。负载均衡器将分配负载给这台宕机的机器。

    这样一来,

    就攻克了容错性的问题。所以我们引入了集群的概念。并将其内嵌入我们的框架中,成为框架的一部分。

    如上图所看到的。整个数据层有Group1。Group2。Group3三个集群组成。这三个集群就是数据水平切分的结果,当然这三个集群也就组成了一个包括完整数据的DB。每个Group包括1个Master(当然Master也能够是多个)和 N个Slave,这些Master和Slave的数据是一致的。比方Group1中的一个slave发生了宕机现象,那么还有两个slave是能够用的,这种模型总是不会造成某部分数据不能訪问的问题。除非整个 Group里的机器所有宕掉。可是考虑到这种事情发生的概率很小(除非是断电了,否则不易发生吧)。

    在没有引入集群曾经,我们的一次查询的过程大致例如以下:请求数据层。并传递必要的分库区分字段(通常情况下是user_id)?

    数据层依据区分字段Route到详细的DB?在这个确定的DB内进行数据操作。

    这是没有引入集群的情况,当时引入集群会是什么样子的呢?看图一就可以得知。我们的路由器上规则和策略事实上仅仅能路由到详细的Group,也就是仅仅能路由到一个虚拟的Group,这个Group并非某个特定的物理server。

    接下来须要做的工作就是找到详细的物理的DBserver,以进行详细的数据操作。

    基于这个环节的需求。我们引入了负载均衡器的概念(LB)。负载均衡器的职责就是定位到一台详细的DBserver。详细的规则例如以下:负载均衡器会分析当前sql的读写特性,假设是写操作或者是要求实时性非常强的操作的话,直接将查询负载分到Master,假设是读操作则通过负载均衡策略分配一个Slave。我们的负载均衡器的主要研究放向也就是负载分发策略,通常情况下负载均衡包含随机负载均衡和加权负载均衡 随机负载均衡非常好理解,就是从N个Slave中随机选取一个Slave。这种随机负载均衡是不考虑机器性能的,它默觉得每台机器的性能是一样的。

    假如真实的情况是这种,这样做也是无可厚非的。假如实际情况并不是如此呢?每一个Slave的机器物理性能和配置不一样的情况,再使用随机的不考虑性能的负载均衡,是非常不科学的,这样一来会给机器性能差的机器带来不必要的高负载,甚至带来宕机的危急。 同一时候高性能的数据库server也不能充分发挥其物理性能。基于此考虑从。我们引入了加权负载均衡,也就是在我们的系统内部通过一定的接口,能够给每台DBserver分配一个权值,然后再执行时LB依据权值在集群中的比重,分配一定比例的负载给该DBserver。当然这种概念的引入,无疑增大了系统的复杂性和可维护性。有得必有失。我们也没有办法逃过的。

    有了分库,有了集群。有了负载均衡器,是不是就万事大吉了呢? 事情远没有我们想象的那么简单。尽管有了这些东西。基本上能保证我们的数据层能够承受非常大的压力 。可是这种设计并不能全然规避数据库宕机的危害。假如Group1中的slave2 宕机了,那么系统的LB并不能得知,这种话事实上是非常危急的,由于LB不知道,它还会以为slave2为可用状态,所以还是会给slave2分配负载。这样一来。问题就出来了。client非常自然的就会发生数据操作失败的错误或者异常。这样是非常不友好的!如何解决这种问题呢? 我们引入集群节点的可用性探測机制 ,或者是可用性的数据推送机制

    这两种机制有什么不同呢?首先说探測机制吧,顾名思义,探測即使,就是我的数据层client。不定时对集群中各个数据库进行可用性的尝试,实现原理就是尝试性链接。或者数据库port的尝试性訪问,都能够做到,当然也能够用JDBC尝试性链接,利用Java的Exception机制进行可用性的推断,详细的会在后面的文字中提到。那数据推送机制又是什么呢?事实上这个就要放在现实的应用场景中来讨论这个问题了,普通情况下应用的DB 数据库宕机的话我相信DBA肯定是知道的,这个时候DBA手动的将数据库的当前状态通过程序的方式推送到client,也就是分布式数据层的应用端,这个时候在更新一个本地的DB状态的列表。

    并告知LB,这个数据库节点不能使用。请不要给它分配负载。

    一个是主动的监听机制。一个是被动的被告知的机制。两者各有所长。可是都能够达到相同的效果。这样一来刚才如果的问题就不会发生了。即使就是发生了,那么发生的概率也会降到最低。

    上面的文字中提到的Master和Slave ,我们并没有做太多深入的解说。如图一所看到的,一个Group由1个Master和N个Slave组成。为什么这么做呢?当中Master负责写操作的负载,也就是说一切写的操作都在Master上进行,而读的操作则分摊到Slave上进行。这样一来的能够大大提高读取的效率。在一般的互联网应用中,经过一些数据调查得出结论,读/写的比例大概在 10:1左右 ,也就是说大量的数据操作是集中在读的操作。这也就是为什么我们会有多个Slave的原因。可是为什么要分离读和写呢?熟悉DB的研发人员都知道,写操作涉及到锁的问题,无论是行锁还是表锁还是块锁。都是比較减少系统运行效率的事情。

    我们这种分离是把写操作集中在一个节点上,而读操作其其它的N个节点上进行。从还有一个方面有效的提高了读的效率,保证了系统的高可用性。读写分离也会引入新的问题,比方我的Master上的数据如何和集群中其它的Slave机器保持数据的同步和一致呢?这个是我们不须要过多的关注的问题。MySql的Proxy机制能够帮助我们做到这点。因为Proxy机制与本课题相关性不是太强,

    在这里不做具体介绍。

    综上所述。本课题中所研究的分布式数据层的大体功能就是如此。

    以上是对基本原理的一些讨论和阐述。接下来就系统设计层面。进行深入的剖析和研究。

    第4章 系统设计

    4.1系统实现层面的选择

    在引言部分中提到,该系统的实现层面有两种选择,一种是基于JDBC层面上的选择,一种是基于现有数据持久层框架层面上的选择,比方Hibernate,ibatis。两种层面各有好处,也各有不足之处。基于JDBC层面上的系统实现,系统开发难度和后期的使用难度都将大大提高。大大添加了系统的开发费用和维护费用。

    本课题的定位是在成型的ibatis持久层框架的基础上进行上层的封装。而不是对ibatis源代码的直接改动。这样一来使本系统不会对现有框架有太多的侵入性。从而也添加了使用的灵活性。之所以选择ibatis,原因例如以下:

    (1)ibatis的学习成本很低,熟练的Java Programmer可在很的短时间内熟练使用ibatis;

    (2)ibatis是轻量级的ORM。仅仅是简单的完毕了RO。OR的映射,其查询语句也是通过配置文件sql-map.xml文件在原生sql的层面进行简单的配置,也就是说我们没有引入诸如Hibernate那样的HQL的概念,从而增强了 sql的可控性。优秀的DBA能够非常好的从sql的层面对sql进行优化,使数据层的应用有非常强的可控性。Hibernate尽管非常强大,可是因为 Hibernate是OR的一个重型封装,且引入HQL的概念,不便于DBA团队对sql语句的控制和性能的调优。

    基于以上两点理由。本课题在ORM的产品的选择上选择了易学易用且轻量级的持久层框架ibatis。

    以下的讨论也都是特定于ibatis的基础上的讨论。

    4.2其它开源框架的选择

    在一些大型的Java应用中,我们一般会採用Spring这种开源框架,尤其是 IoC(DI)这部分。有效的帮助开发者管理对象的依赖关系和层次,减少系统各层次之间的实体耦合。Spring的长处和用处我相信这是开发者众所周知的。在此不再赘述。本课题的数据层也将採用Spring做为IoC(DI)的框架。

    4.3系统开发技术和工具介绍

    开发语言:Java JDK1.5

    集成开发环境:Eclipse 3.3.4

    Web环境下測试server:JBoss 4.2

    构建工具:淘宝自行研发的构建工具Antx(类似于Maven),当然也能够用Maven

    依赖的开源Jar:Spring2.0,ibaits,commons-configuration(读取配置文件),log4j,junit等

  • 相关阅读:
    【设计模式】模板模式
    【设计模式】策略模式
    【设计模式】空对象模式
    【设计模式】状态模式
    【设计模式】观察者模式
    【设计模式】备忘录模式
    【设计模式】中介者模式
    【设计模式】迭代器模式
    【设计模式】解释器模式
    【设计模式】命令模式
  • 原文地址:https://www.cnblogs.com/mthoutai/p/6911291.html
Copyright © 2011-2022 走看看