什么是大型网站,从网站的技术角度考虑这个问题人们很容易犯一个毛病就是认为网站的访问量是衡量的指标,懂点行的人也许会认为是网站在单位时间里的并发量的大小来作为指标,如果按这些标准那么像hao123网站就是了。
如果数据库需要进行水平拆分,这其实是一件很开心的事情,因为它代表公司的业务正在迅猛的增长,对于开发人员而言那就是有不尽的项目可以做,虽然会感觉很忙,但是人过的充实,心里也踏实。
数据库水平拆分简单说来就是先将原数据库里的一张表在做垂直拆分出来放置在单独的数据库和单独的表里后更进一步的把本来是一个整体的表进一步拆分成多张表,每一张表都用独立的数据库进行存储。当表被水平拆分后,原数据表成为了一个逻辑的概念,而这个逻辑表的业务含义需要多张物理表协同完成,因此数据库的表被水平拆分后,那么我们对这张表的操作已经超出了数据库本身提供给我们现有的手段,换句话说我们对表的操作会超出数据库本身所拥有的处理能力,这个时候我就需要设计相关的方案来弥补数据库缺失的能力,这就是数据库水平拆分最大的技术难点所在。
数据库的水平拆分是数据库垂直拆分的升级版,它和垂直拆分更像继承机制里的父子关系,因此水平拆分后,垂直拆分所遇到的join查询的问题以及分布式事务的问题任然存在,由于表被物理拆解增加了逻辑表的维度,这也给垂直拆分里碰到的两个难题增加了更多的维度,因此水平拆分里join查询的问题和分布式事务会变得更加复杂。水平拆分除了垂直拆分两个难题外,它还会产生新的技术难题,这些难题具体如下:
难题一:数据库的表被水平拆分后,该表的主键设计会变得十分困难;
难题二:原来单表的查询逻辑会面临挑战。
在准备本篇文章时候,我看到一些资料里还提到了一些难题,这些难题是:
难题三:水平拆分表后,外键的设计也会变得十分困难;
难题四:这个难题是针对数据的新增操作的,大致的意思是,我们到底按什么规则把需要存储的数据存储在拆分出的那个具体的物理数据表里。
难题三的问题,我在上篇已经给出了解答,这里我进行一定的补充,其实外键问题在垂直拆分就已经存在,不过在讲垂直拆分时候我们没有讲到这个问题,这主要是我设定了一个前提,就是数据表在最原始的数据建模阶段就要抛弃所有外键的设计,并将外键的逻辑抛给服务层去完成,我们要尽全力减轻数据库承担的运算压力,其实除了减轻数据库运算压力外,我们还要将作为存储原子的表保持相对的独立性,互不关联,那么要做到这点最直接的办法就是去掉表与表之间关联的象征:外键,这样我们就可以从根基上为将来数据库做垂直拆分和水平拆分打下坚实的基础。
至于难题四,其实问题的本质是分库分表后具体的数据在哪里落地的问题,而数据存储在表里的关键障碍其实就是主键,试想一下,我们设计张表,所有字段我们都准许可以为空,但是表里有个字段是绝对不能为空的,那就是主键,主键是数据在数据库里身份的象征,因此我们在主键设计上是可以体现出该数据的落地规则,那么难题四也会随之解决。因此下文我会重点讲解前两个水平拆分的难题。
首先是水平拆分里的主键设计问题,抛开所有主键所能代表的业务含义,数据库里标的主键本质是表达表里的某一条记录的唯一性,在设计数据库的时候我们可以由一个绝对不可重复的字段表示主键,也可以使用多个字段组合起来表达这种唯一性,使用一个字段表示主键,这已经是很原子级的操作,没法做进一步的修改,但是如果使用多个字段表示一个主键对于水平拆分而言就会碰到问题了,这个问题主要是体现在数据到底落地于哪个数据库,关于主键对数据落地的影响我会在把相关知识讲解完毕后再着重阐述,这里要提的是当碰到联合主键时候我们可以设定一个没有任何业务含义的字段来替代,不过这个要看场景了,我倾向于将联合主键各个字段里的值合并为一个字段来表示主键,如果有的朋友认为这样会导致数据冗余,那么可以干脆去掉原来做联合主键的相关字段就是用一个字段表示,只不过归并字段时候使用一个分隔符,这样方便服务层进行业务上的拆分。
由上所述,这里我给出水平拆分主键设计的第一个原则:被水平拆分的表的主键设计最好使用一个字段表示。
如果我们的主键只是表达记录唯一性的话,那么水平拆分时候相对要简单的多,例如在Oracle数据库里有一个sequence机制,这其实就是一个自增数的算法,自增机制几乎所有关系数据库都有,也是我们平时最喜欢使用的主键字段设计方案,如果我们要拆分的表,使用了自增字段,同时这个自增字段只是用来表达记录唯一性,那么水平拆分时候处理起来就简单多了,我这里给出两个经典方案,方案如下:
方案一:自增列都有设定步长的特性,假如我们打算把一张表只拆分为两个物理表,那么我们可以在其中一张表里把主键的自增列的步长设计为2,起始值为1,那么它的自增规律就是1,3,5,7依次类推,另外一张物理表的步长我们也可以设置为2,如果起始值为2,那么自增规律就是2,4,6,8以此类推,这样两张表的主键就绝对不会重复了,而且我们也不用另外做两张物理表相应的逻辑关联了。这种方案还有个潜在的好处,那就是步长的大小和水平数据拆分的粒度关联,也是我们为水平拆分的扩容留有余量,例如我们把步长设计为9,那么理论上水平拆分的物理表可以扩容到9个。
方案二:拆分出的物理表我们允许它最多存储多少数据,我们其实事先通过一定业务技术规则大致估算出来,假如我们估算一张表我们最多让它存储2亿条,那么我们可以这么设定自增列的规律,第一张物理表自增列从1开始,步长就设为1,第二种物理表的自增列则从2亿开始,步长也设为1,自增列都做最大值的限制,其他的依次类推。
那么如果表的主键不是使用自增列,而是业务设计的唯一字段,那么我们又如何处理主键分布问题了?这种场景很典型,例如交易网站里一定会有订单表,流水表这样的设计,订单表里有订单号,流水表里有流水号,这些编号都是按一定业务规则定义并且保证它的唯一性,那么前面的自增列的解决方案就没法完成它们做水平拆分的主键问题,那么碰到这个情况我们又该如何解决了?我们仔细回味下数据库的水平拆分,它其实和分布式缓存何其的类似,数据库的主键就相当于分布式缓存里的键值,那么我们可以按照分布式缓存的方案来设计主键的模型,方案如下:
方案一:使用整数哈希求余的算法,字符串如果进行哈希运算会得出一个值,这个值是该字符串的唯一标志,如果我们稍微改变下字符串的内容,计算的哈希值肯定是不同,两个不同的哈希值对应两个不同字符串,一个哈希值有且只对应唯一一个字符串,加密算法里的MD5,SHA都是使用哈希算法的原理计算出一个唯一标示的哈希值,通过哈希值的匹配可以判断数据是否被篡改过。不过大多数哈希算法最后得出的值都是一个字符加数字的组合,这里我使用整数哈希算法,这样计算出的哈希值就是一个整数。接下来我们就要统计下我们用于做水平拆分的服务器的数量,假如服务器的数量是3个,那么接着我们将计算的整数哈希值除以服务器的数量即取模计算,通过得到的余数来选择服务器,该算法的原理图如下所示:
方案二:就是方案一的升级版一致性哈希,一致性哈希最大的作用是保证当我们要扩展物理数据表的数量时候以及物理表集群中某台服务器失效时候才会体现,这个问题我后续文章会详细讨论物理数据库扩容的问题,因此这里先不展开讨论了。
由上所述,我们发现在数据库进行水平拆分时候,我们设定的算法都是通过主键唯一性进行的,根据主键唯一性设计的特点,最终数据落地于哪个物理数据库也是由主键的设计原则所决定的,回到上文里我提到的如果原库的数据表使用联合字段设计主键,那么我们就必须首先合并联合主键字段,然后通过上面的算法来确定数据的落地规则,虽然不合并一个字段看起来也不是太麻烦,但是在我多年开发里,把唯一性的字段分割成多个字段,就等于给主键增加了维度,字段越多,维度也就越大,到了具体的业务计算了我们不得不时刻留心这些维度,结果就很容易出错,我个人认为如果数据库已经到了水平拆分阶段了,那么就说明数据库的存储的重要性大大增强,为了让数据库的存储特性变得纯粹干净,我们就得尽力避免增加数据库设计的复杂性,例如去掉外键,还有这里的合并联合字段为一个字段,其实为了降低难度,哪怕做点必要的冗余也是值得。
解决数据库表的水平拆分后的主键唯一性问题有一个更加直接的方案,这也是很多人碰到此类问题很自然想到的方法,那就是把主键生成规则做成一个主键生成系统,放置在单独一台服务器上统一生成,每次新增数据主键都从这个服务器里获取,主键生成的算法其实很简单,很多语言都有计算UUID的功能,UUID是根据所在服务器的相关的硬件信息计算出的全球唯一的标示,但是这里我并没有首先拿出这个方案,因为它相比如我前面的方案缺点太多了,下面我要细数下它的缺点,具体如下:
缺点一:把主键生成放到外部服务器进行,这样我们就不得不通过网络通信完成主键值的传递,而网络是计算机体系里效率最低效的方式,因此它会影响数据新增的效率,特别是数据量很大时候,新增操作很频繁时候,该缺点会被放大很多;
缺点二:如果我们使用UUID算法做主键生成的算法,因为UUID是依赖单台服务器进行,那么整个水平拆分的物理数据库集群,主键生成器就变成整个体系的短板,而且是关键短板,主键生成服务器如果失效,整个系统都会无法使用,而一张表需要被水平拆分,而且拆分的表是业务表的时候,那么这张表在整个系统里的重要度自然很高,它如果做了水平拆分后出现单点故障,这对于整个系统都是致命的。当然有人肯定说,既然有单点故障,那么我们就做个集群系统,问题不是解决了吗?这个想法的确可以解决我上面阐述的问题,但是我前文讲到过,现实的软件系统开发里我们要坚守一个原则那就是有简单方案尽量选择简单的方案解决问题,引入集群就是引入了分布式系统,这样就为系统开发增加了开发难度和运维风险,如果我们上文的方案就能解决我们的问题,我们何必自讨苦吃做这么复杂的方案呢?
缺点三:使用外部系统生成主键使得我们的水平拆分数据库的方案增加了状态性,而我上面提到的方案都是无状态的,有状态的系统会相互影响,例如使用外部系统生成主键,那么当数据操作增大时候,必然会造成在主键系统上资源竞争的事情发生,如果我们对主键系统上的竞争状态处理不好,很有可能造成主键系统被死锁,这也就会产生我前文里说到的503错误,而无状态的系统是不存在资源竞争和死锁的问题,这洋就提升了系统的健壮性,无状态系统另一个优势就是水平扩展很方便。
这里我列出单独主键生成系统的缺点不是想说明我觉得这种解决方案完全不可取,这个要看具体的业务场景,根据作者我的经验还没有找到一个很合适使用单独主键生成器的场景。
上面里我提出的方案还有个特点就是能保证数据在不同的物理表里均匀的分布,均匀分布能保证不同物理表的负载均衡,这样就不会产生系统热点,也不会让某台服务器比其他服务器做的事情少而闲置资源,均匀分配资源可以有效的利用资源,降低生产的成本提高生产的效率,但是均匀分布式数据往往会给我们业务运算带来很多麻烦。
水平拆分数据库后我们还要考虑水平扩展问题,例如如果我们事先使用了3台服务器完成了水平拆分,如果系统运行到一定阶段,该表又遇到存储瓶颈了,我们就得水平扩容数据库,那么如果我们的水平拆分方案开始设计的不好,那么扩容时候就会碰到很多的麻烦。
上文里我遗留了两个问题,一个问题是数据库做了水平拆分以后,如果我们对主键的设计采取一种均匀分布的策略,那么它对于被水平拆分出的表后续的查询操作将有何种影响,第二个问题就是水平拆分的扩容问题。这两个问题在深入下去,本系列就越来越技术化了,可能最终很多朋友读完后还是没有找到解决实际问题的启迪,而且我觉得这些问题都是像BAT这样巨型互联网公司才会认真思考的,因此本篇我打算换个角度来阐述本文的后续内容。
这里我们首先要明确一个问题,到底是什么因素促使我们去做数据库的垂直拆分和水平拆分的呢?答案很简单就是业务发展的需求,前文里的水平拆分技术方案基本都是抛弃千变万化的业务规则的限制,尽量将水平拆分的问题归为一个简单的技术实现方案,而纯技术手段时常是看起来很美,但是到了面对现实问题时候,常常会变得那么苍白和无力。
水平拆分
水平拆分的难题里我还有个难题没有讲述,就是水平拆分后对查询操作的影响,特别是对单表查询的影响,这点估计也是大伙最为关心的问题,今天我不在延着水平拆分的技术手段演进是阐述上文的遗留问题,而是我要把前面提到的技术手段和一些典型场景结合起来探讨如何解决网站存储的瓶颈问题。
前文中我总结过一个解决存储瓶颈的脉络,具体如下:
单库数据库-->数据库读写分离-->缓存技术-->搜索技术-->数据的垂直拆分-->数据的水平拆分
这个脉络给一些朋友产生了误解,就是认为这个过程应该是个串行的过程,其实在实际的场景下这个过程往往是并行的,但是里面有一个元素应该是串行的或者说思考时候有个先后问题,那就是对数据库层的操作,具体如下:
单库数据库-->数据库读写分离-->数据的垂直拆分-->数据的水平拆分
而缓存技术和搜索技术在数据库的任意阶段里都可以根据实际的业务需求随时切入其中帮助数据库减轻不必要的压力。例如,当网站的后台数据库还是单库的时候,数据库渐渐出现了瓶颈问题,而这个瓶颈又没有达到需要采取大张旗鼓做读写分离方案的程度,那么我这个时候可以考虑引入缓存机制。不过要合理的使用缓存我们首先要明确缓存本身的特点,这些特点如下所示:
特点一:缓存主要是适用于读操作,并且缓存的读操作的效率要远远高于从数据库以及硬盘读取数据的效率。
特点二:缓存的数据是存储在内存当中,因此当系统重启,宕机等等异常场景下,缓存数据就会不可逆的丢失,且无法恢复,因此缓存不能作为可靠存储设备,这就导致一个问题,缓存里的数据必须首先从数据库里同步到内存中,而使用缓存的目的就是为了解决数据库的读操作效率低下的问题,数据库的数据同步到缓存的操作会因为数据库的效率低下而在性能上大打折扣,所以缓存适合的场景是那些固定不变的数据以及业务对实时性变化要求不高的数据。
根据缓存的上述两个特点,我们可以把数据库里和上述描述类似操作的相关数据迁移到缓存里,那样我们就从数据库上剥离了那些对数据库价值不高的操作,让数据库专心做有价值的操作,这样也是减轻数据库压力的一种手段。
不过这个手段局限性很强,局限性主要是一台计算机了用于存储缓存的内存的大小都是远远要低于硬盘,并且内存的价格要远贵于硬盘,如果我们将大规模的数据从硬盘往内存迁移,从资源成本和利用率角度考虑性价比还是很低的,因此缓存往往都是用于转存那些不会经常变化的数据字典,以及经常会被读,而修改较少的数据,但是这些数据的规模也是有一定限度的,因此当单库数据库出现了瓶颈时候马上就着手进行读写分离方案的设计性价比还是很高的。
前文我讲到我们之所以选择数据库读写分离是主要原因是因为数据库的读写比例严重失衡所致,但是做了读写分离必然有个问题不可避免,写库向读库同步数据一定会存在一定的时间差,如果我们想减小读库和写库数据的时间差,那么任然会导致读库因为写的粒度过细而发生部分性能的损失,但是时间差过大,或许又会无法满足实际的业务需求,因此这个时间差的设计一定要基于实际的业务需求合理的设计。
同步的时间差的问题还是个小问题,也比较好解决,但是如何根据实际的业务需求做读写分离这其实还是非常有挑战性的,这里我举个很常见的例子来说明读写分离的难度问题,我们这里以淘宝为例,淘宝是个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之所以这么麻烦的设计这样的计算模型,终极目的就是为了让海量数据按不同维度存储起来,使用时候尽全力检索数据检索的数量,从而达到海量数据快速读取的目的。
|