上文里我遗留了两个问题,一个问题是数据库作了水平拆分之后,若是咱们对主键的设计采起一种均匀分布的策略,那么它对于被水平拆分出的表后续的查询操做将有何种影响,第二个问题就是水平拆分的扩容问题。这两个问题在深刻下去,本系列就愈来愈技术化了,可能最终不少朋友读完后仍是没有找到解决实际问题的启迪,并且我以为这些问题都是像BAT这样巨型互联网公司才会认真思考的,所以本篇我打算换个角度来阐述本文的后续内容。mysql
这里咱们首先要明确一个问题,究竟是什么因素促使咱们去作数据库的垂直拆分和水平拆分的呢?答案很简单就是业务发展的需求,前文里的水平拆分技术方案基本都是抛弃变幻无穷的业务规则的限制,尽可能将水平拆分的问题归为一个简单的技术实现方案,而纯技术手段时常是看起来很美,可是到了面对现实问题时候,经常会变得那么苍白和无力。sql
水平拆分的难题里我还有个难题没有讲述,就是水平拆分后对查询操做的影响,特别是对单表查询的影响,这点估计也是大伙最为关心的问题,今天我不在延着水平拆分的技术手段演进是阐述上文的遗留问题,而是我要把前面提到的技术手段和一些典型场景结合起来探讨如何解决网站存储的瓶颈问题。数据库
前文中我总结过一个解决存储瓶颈的脉络,具体以下:缓存
单库数据库-->数据库读写分离-->缓存技术-->搜索技术-->数据的垂直拆分-->数据的水平拆分数据结构
这个脉络给一些朋友产生了误解,就是认为这个过程应该是个串行的过程,其实在实际的场景下这个过程每每是并行的,可是里面有一个元素应该是串行的或者说思考时候有个前后问题,那就是对数据库层的操做,具体以下:oop
单库数据库-->数据库读写分离-->数据的垂直拆分-->数据的水平拆分性能
而缓存技术和搜索技术在数据库的任意阶段里均可以根据实际的业务需求随时切入其中帮助数据库减轻没必要要的压力。例如,当网站的后台数据库仍是单库的时候,数据库渐渐出现了瓶颈问题,而这个瓶颈又没有达到须要采起大张旗鼓作读写分离方案的程度,那么我这个时候能够考虑引入缓存机制。不过要合理的使用缓存咱们首先要明确缓存自己的特色,这些特色以下所示:学习
特色一:缓存主要是适用于读操做,而且缓存的读操做的效率要远远高于从数据库以及硬盘读取数据的效率。网站
特色二:缓存的数据是存储在内存当中,所以当系统重启,宕机等等异常场景下,缓存数据就会不可逆的丢失,且没法恢复,所以缓存不能做为可靠存储设备,这就致使一个问题,缓存里的数据必须首先从数据库里同步到内存中,而使用缓存的目的就是为了解决数据库的读操做效率低下的问题,数据库的数据同步到缓存的操做会由于数据库的效率低下而在性能上大打折扣,因此缓存适合的场景是那些固定不变的数据以及业务对实时性变化要求不高的数据。搜索引擎
根据缓存的上述两个特色,咱们能够把数据库里和上述描述相似操做的相关数据迁移到缓存里,那样咱们就从数据库上剥离了那些对数据库价值不高的操做,让数据库专心作有价值的操做,这样也是减轻数据库压力的一种手段。
不过这个手段局限性很强,局限性主要是一台计算机了用于存储缓存的内存的大小都是远远要低于硬盘,而且内存的价格要远贵于硬盘,若是咱们将大规模的数据从硬盘往内存迁移,从资源成本和利用率角度考虑性价比仍是很低的,所以缓存每每都是用于转存那些不会常常变化的数据字典,以及常常会被读,而修改较少的数据,可是这些数据的规模也是有必定限度的,所以当单库数据库出现了瓶颈时候立刻就着手进行读写分离方案的设计性价比仍是很高的。
前文我讲到咱们之因此选择数据库读写分离是主要缘由是由于数据库的读写比例严重失衡所致,可是作了读写分离必然有个问题不可避免,写库向读库同步数据必定会存在必定的时间差,若是咱们想减少读库和写库数据的时间差,那么任然会致使读库由于写的粒度过细而发生部分性能的损失,可是时间差过大,或许又会没法知足实际的业务需求,所以这个时间差的设计必定要基于实际的业务需求合理的设计。
同步的时间差的问题仍是个小问题,也比较好解决,可是如何根据实际的业务需求作读写分离这其实仍是很是有挑战性的,这里我举个很常见的例子来讲明读写分离的难度问题,咱们这里以淘宝为例,淘宝是个C2C的电商网站,它是互联网公司提供一个平台,商家自助接入这个平台,在这个平台上卖东西,这个和线下不少大卖场的模式相似。淘宝是个大平台,它的交易表里必定是要记下全部商户的交易数据,可是针对单个商家他们只会关心本身的网店的销售数据,这就有一个问题了,若是某一个商家要查询本身的交易信息,淘宝就要从成千上万的交易信息里检索出该商家的交易信息,那么若是咱们把全部交易信息放在一个交易表里,确定有商家会有这样的疑问,个人网店天天交易额不大,为何我查询交易数据的速度和那些大商家同样慢了?那么咱们到底该如何是解决这样的场景了?
碰到这样的状况,当网站的交易规模变大后就算咱们把交易表作了读写分离估计也是无法解决实际的问题,就算咱们作的完全点把交易表垂直拆分出来估计仍是解决不了问题,由于一个业务数据库拥有不少张表,可是真正压力大的表毕竟是少数,这个符合28原则,而数据库大部分的关键问题又都是在那些数据压力大的表里,就算咱们把这些表单独作读写分离甚至作垂直拆分,其实只是把数据库最大的问题迁移出原来数据库,而不是在解决该表的实际问题。
若是咱们要解决交易表的问题咱们首先要对交易表作业务级的拆分,那么咱们要为交易表增长一个业务维度:实时交易和历史交易,通常而言实时交易以当天及当天24小时为界,历史交易则是除去当天交易外的全部历史交易数据。实时交易数据和历史交易数据有着很大不一样,实时交易数据读与写是比较均衡的,不少时候估计写的频率会远高于读的频率,可是历史交易表这点上和实时交易就彻底不一样了,历史交易表的读操做频率会远大于写操做频率,若是咱们将交易表作了实时交易和历史交易的拆分后,那么读写分离方案适合的场景是历史交易查询而非实时交易查询,不过历史交易表的数据是从实时交易表里同步过来的,根据这两张表的业务特性,咱们能够按以下方案设计,具体以下:
咱们能够把实时交易表设计成两张表,把它们分别叫作a表和b表,a表和b表按天交替进行使用,例现在天咱们用a表记录实时交易,明天咱们就用b表记录实时交易,固然咱们事先能够用个配置表记录今天到底使用那张表进行实时交易记录,为何要如此麻烦的设计实时交易表了?这么作的目的是为了实时交易数据同步到历史数据时候提供便利,通常咱们会在凌晨0点切换实时交易表,过时的实时交易表数据会同步到历史交易表里,这个时候须要数据迁移的实时交易表是全表数据迁移,效率是很是低下,假如实时交易表的数据量很大的时候,这种导入同步操做会变得十分耗时,因此咱们设计两张实时交易表进行切换来把数据同步的风险降到最低。因而可知,历史交易表天天基本都只作一次写操做,除非同步出了问题,才会重复进行写操做,可是写的次数确定是很低的,因此历史交易表的读写比例失衡是很是严重的。不过实时交易表的切换也是有技术和业务风险的,为了保证明时交易表的高效性,咱们通常在数据同步操做成功后会清空实时交易表的数据,可是咱们很难保证这个同步会不会有问题,所以同步时候咱们最好作下备份,此外,两个表切换的时候确定会碰到这样的场景,就是有人在凌晨0点前作了交易,可是这个交易是在零点后作完,假如实时交易表会记录交易状态的演变过程,那么在切换时候就有可能两个实时表的数据没有作好接力,所以咱们同步到历史交易表的数据必定要保持一个原则就是已经完成交易的数据,没有完成的交易数据两张实时交易还要完成一个业务上的接力,这就是业界常说的数据库日切的问题。
历史交易表自己就是为读使用的,因此咱们从业务角度将交易表拆分红实时交易表和历史交易表自己就是在为交易表作读写分离,竟然了设计了历史交易表咱们就作的干脆点,把历史交易表作垂直拆分,将它从原数据库里拆分出来独立建表,随着历史交易的增大,上文里所说的某个商户想快速检索出本身的数据的难题并无获得根本的改善,为了解决这个难题咱们就要分析下难题的根源在那里。这个根源很简单就是咱们把全部商户的数据不加区别的放进了一张表里,无论是交易量大的商户仍是交易量小的商户,想要查询出本身的数据都要进行全表检索,而关系数据库单表记录达到必定数据量后全表检索就会变的异常低效,例如DB2当数据量超过了1亿多,mysql单表超过了100万条后那么全表查询这些表的记录都会存在很大的效率问题,那么咱们就得对历史交易表进一步拆分,由于问题根源是单表数据量太大了,那咱们就能够对单表的数据进行拆分,把单表分红多表,这个场景就和前面说的水平拆分里把原表变成逻辑表,原表的数据分散到各个独立的逻辑表里的方式一致,不过这里咱们没有一开始作水平拆分,那是会把问题变麻烦,咱们只要在一个数据库下对单表进行拆分便可,这样也能知足咱们的要求,而且避免了水平拆分下的跨库写做的难题。接下来咱们又有一个问题了那就是咱们按什么维度拆分这张单表呢?
咱们按照前文讲到的水平拆分里主键设计方案执行吗?固然不行哦,由于那些方案明显提高不了商户检索数据的效率问题,因此咱们要首先分析下商户检索数据的方式,商户通常会按这几个维度检索数据,这些维度分别是:商户号、交易时间、交易类型,固然还有其余的维度,我这里就以这三个维度为例阐述下面的内容,商户查询数据效率低下的根本缘由是全表检索,其实商户查询至少有一个维度那就是商户号来进行查询,若是咱们把该商户的数据存入到一张单独的表里,天然查询的效率会有很大的提高,可是在实际系统开发里咱们不多经过商户号进行拆分表,这是为何呢?由于一个电商平台的商户是个动态的指标,会常常发生变化,其次,商户号的粒度很细,若是使用商户号拆分表的必然会有这样的后果那就是咱们可能要频繁的建表,随着商户的增长表的数量也会增长,形成数据的碎片化,同时不一样的商户交易量是不同的,按商户建表会形成数据存储的严重不平衡。若是使用交易类型来拆分表,虽然维度的粒度比商户号小,可是会形成数据的分散化,也就是说咱们查询一个商户的所有交易数据会存在很大问题。因而可知拆表时候如何有效的控制维度的粒度以及数据的汇集度是拆分的关键所在,由于使用交易时间这个维度就会让拆分更加合理,不过期间的维度的设计也是颇有学问的,下面咱们看看腾讯分析的维度,以下所示:
腾讯分析的维度是今天这个其实至关于实时交易查询,除此以外都是对历史数据查询,它们分为昨天、最近7天和最近30天,咱们若是要对历史交易表进行拆分也是能够参照腾讯分析的维度进行,不过无论咱们选择什么维度拆分数据,那么都是牺牲该维度成全了其余维度,例如咱们按腾讯分析的维度拆分数据,那么咱们想灵活使用时间查询数据将会受到限制。
咱们把历史交易数据经过交易时间维度进行了拆分,虽然获得了效率提高,可是历史交易数据表是个累积表,随着时间推移,首先是月表,接下来是周表都会由于数据累积产生查询效率低下的问题,这个时候咱们又该如何解决了?这个时候咱们须要再引进一个维度,那么这个时候咱们能够选择商户号这个维度,可是商户号做为拆分维度是有必定问题的,由于会形成数据分布不均衡,那么咱们就得将维度的粒度由小变粗,其实一个电商平台上每每少数商户是完成了大部分电商平台的交易,所以咱们能够根据必定指标把重要商户拆分出来,单独建表,这样就能够平衡了数据的分布问题。
咱们总结下上面的案例,咱们会获得不少的启迪,我将这些启迪总结以下:
启迪一:数据库的读写分离不是简单的把主库数据导入到读库里就能解决问题,读数据库和写数据的分离的目的是为了让读和写操做不能相互影响效率。
启迪二:解决读的瓶颈问题的本质是减小数据的检索范围,数据检索的范围越小,读的效率也就越高;
启迪三:数据库的垂直拆分和水平拆分首先不该该从技术角度进行,而是经过业务角度进行,若是数据库进行业务角度的水平拆分,那么拆分的维度每每是要根据该表的某个字段进行的,这个字段选择要有必定原则,这个原则主要是该字段的维度的粒度不能过细,该字段的维度范围不能常常的动态发生变化,最后就是该维度不能让数据分布严重失衡。
回到现实的开发里,对于一个数据库作拆表,分表的工做实际上是一件很让人恼火的工做,这主要是有如下缘由所形成的,具体以下所述:
缘由一:一个数据库其实容纳多少张表是有必定限制的,就算没有超过这个限制,若是原库原本有30张表,咱们拆分后变成了60张,接着是120张,那么数据库自己管理这么多表也会消耗不少性能,所以公司的DBA每每会控制那些过多分表的行为。
缘由二:每次拆表后,都会牵涉到历史数据的迁移问题,这个迁移风险很大,迁移方案若是设计的不完善可能会致使数据丢失或者损坏,若是关键数据发生了丢失和损坏,结果可能很是致命。所以在设计数据库分表分库方案时候咱们要尽可能让受影响的数据范围变得最小。
缘由三:每次拆表和分表都会让系统的相关方绷紧神经,方案执行后,会有很长时间的监控和观察期,因此拆数据库时常是一件使人讨厌的事情。
缘由四:为了保证新方案执行后确保系统没有问题,咱们经常会让新旧系统并行运行一段时间,这样能够保证若是新方案出现问题,问题的影响面最低,可是这种作法也有一个恶果就是会致使数据迁移方案要进行动态调整,从而增长迁移数据的风险
所以当公司不得不作这件事情时候,公司都会很天然去考虑第三种解决方案,第三种解决方案是指尽可能不改变原数据库的功能,而是另起炉灶,使用新技术来解决咱们的问题,例如前文所说的搜索技术解决数据库like的低效问题就是其中方案之一,该方案只要咱们将数据库的表按必定时间导入到文件系统,而后对文件创建倒排索引,让like查询效率更好,这样就不用改变原数据库的功能,又能减轻数据库的压力。
如今经常使用的第三种解决方案就是使用NoSql数据库,NoSql数据库大多都是针对文件进行的,所以咱们能够和使用搜索引擎那样把数据导入到文件里就好了,NoSql基本都采用Key/Value这种简单的数据结构,这种数据结构和关系数据库比起来更加的灵活,对原始数据的约束最少,因此在NoSql数据库里建表咱们能够很灵活的把列和行的特性交叉起来用,这句话可能不少人不太理解,下面我举个例子解释下,例如hadoop技术体系里的hbase,hbase是一个基于列族的数据库,使用hbase时候咱们就能够经过列来灵活的拆分数据,好比咱们能够把中国的省份做为一个列,将该省份的数据都放入到这个列下面,在省这个维度下咱们能够接着在定义一个列的维度,例如软件行业,属于软件行业的数据放在这个列下面,最终提供用户查询时候咱们就能够减小数据检索的范围,最终达到提高查询效率的目的。因而可知当咱们用惯了关系数据库后,学习像hbase这样的Nosql数据库咱们会很是的不适应,由于关系数据库的表有固定模式,也就是咱们常说的结构化数据,当表的定义好了后,就算里面没有数据,那么这个结构也就固定了,咱们使用表的时候都是按这个模型下面,咱们几乎感受不到它,可是到了hbase的使用就不一样了,hbase使用时候咱们都在不停的为数据增长结构化模型,并且这个维度是以列为维度的,而关系数据库里列肯定后咱们使用时候是没法改变的,这就是学习hbase的最大困难之一。Hbase之因此这么麻烦的设计这样的计算模型,终极目的就是为了让海量数据按不一样维度存储起来,使用时候尽全力检索数据检索的数量,从而达到海量数据快速读取的目的。
好了,今天就写到这里,祝你们生活愉快。