码迷,mamicode.com
首页 > 其他好文 > 详细

关于大型站点技术演进的思考(四)--存储的瓶颈(4)

时间:2017-08-14 23:39:42      阅读:178      评论:0      收藏:0      [点我收藏+]

标签:阶段   uuid   解决   准备   切割   完成   操作   设计原则   div   

假设数据库须要进行水平拆分,这事实上是一件非常开心的事情,由于它代表公司的业务正在迅猛的增长,对于开发者而言那就是有不尽的项目能够做,尽管会感觉非常忙。可是人过的充实,心里也踏实。

  数据库水平拆分简单说来就是先将原数据库里的一张表在做垂直拆分出来放置在单独的数据库和单独的表里后更进一步的把本来是一个总体的表进一步拆分成多张表,每一张表都用独立的数据库进行存储。当表被水平拆分后,原数据表成为了一个逻辑的概念,而这个逻辑表的业务含义须要多张物理表协同完毕。因此数据库的表被水平拆分后。那么我们对这张表的操作已经超出了数据库本身提供给我们现有的手段,换句话说我们对表的操作会超出数据库本身所拥有的处理能力,这个时候我就须要设计相关的方案来弥补数据库缺失的能力。这就是数据库水平拆分最大的技术难点所在。

  数据库的水平拆分是数据库垂直拆分的升级版,它和垂直拆分更像继承机制里的父子关系。因此水平拆分后。垂直拆分所遇到的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都是使用哈希算法的原理计算出一个唯一标示的哈希值,通过哈希值的匹配能够推断数据是否被篡改过。只是大多数哈希算法最后得出的值都是一个字符加数字的组合,这里我使用整数哈希算法,这样计算出的哈希值就是一个整数。接下来我们就要统计下我们用于做水平拆分的server的数量,假如server的数量是3个,那么接着我们将计算的整数哈希值除以server的数量即取模计算,通过得到的余数来选择server,该算法的原理图例如以下所看到的:

 技术分享

  方案二:就是方案一的升级版一致性哈希,一致性哈希最大的作用是保证当我们要扩展物理数据表的数量时候以及物理表集群中某台server失效时候才会体现,这个问题我兴许文章会具体讨论物理数据库扩容的问题,因此这里先不展开讨论了。

  由上所述,我们发如今数据库进行水平拆分时候,我们设定的算法都是通过主键唯一性进行的,依据主键唯一性设计的特点,终于数据落地于哪个物理数据库也是由主键的设计原则所决定的。回到上文里我提到的假设原库的数据表使用联合字段设计主键,那么我们就必须首先合并联合主键字段,然后通过上面的算法来确定数据的落地规则,尽管不合并一个字段看起来也不是太麻烦。可是在我多年开发里,把唯一性的字段切割成多个字段。就等于给主键添加了维度,字段越多,维度也就越大,到了详细的业务计算了我们不得不时刻留心这些维度,结果就非常easy出错,我个人觉得假设数据库已经到了水平拆分阶段了,那么就说明数据库的存储的重要性大大增强,为了让数据库的存储特性变得纯粹干净,我们就得尽力避免添加数据库设计的复杂性,比如去掉外键,还有这里的合并联合字段为一个字段,事实上为了减少难度。哪怕做点必要的冗余也是值得。

  解决数据库表的水平拆分后的主键唯一性问题有一个更加直接的方案,这也是非常多人碰到此类问题非常自然想到的方法。那就是把主键生成规则做成一个主键生成系统。放置在单独一台server上统一生成,每次新增数据主键都从这个server里获取,主键生成的算法事实上非常easy。非常多语言都有计算UUID的功能。UUID是依据所在server的相关的硬件信息计算出的全球唯一的标示,可是这里我并没有首先拿出这个方法。由于它相比方我前面的方案缺点太多了,以下我要细数下它的缺点。详细例如以下:

  缺点一:把主键生成放到外部server进行,这样我们就不得不通过网络通信完毕主键值的传递,而网络是计算机体系里效率最低效的方式。因此它会影响数据新增的效率,特别是数据量非常大时候。新增操作非常频繁时候,该缺点会被放大非常多。

  缺点二:假设我们使用UUID算法做主键生成的算法,由于UUID是依赖单台server进行。那么整个水平拆分的物理数据库集群。主键生成器就变成整个体系的短板,并且是关键短板。主键生成server假设失效。整个系统都会无法使用,而一张表须要被水平拆分,并且拆分的表是业务表的时候。那么这张表在整个系统里的重要度自然非常高。它假设做了水平拆分后出现单点故障,这对于整个系统都是致命的。

当然有人肯定说,既然有单点故障,那么我们就做个集群系统,问题不是攻克了吗?这个想法的确能够解决我上面阐述的问题,可是我前文讲到过,现实的软件系统开发里我们要坚守一个原则那就是有简单方案尽量选择简单的方案解决这个问题。引入集群就是引入了分布式系统。这样就为系统开发添加了开发难度和运维风险,假设我们上文的方案就能解决我们的问题,我们何必自讨苦吃做这么复杂的方案呢?

  缺点三:使用外部系统生成主键使得我们的水平拆分数据库的方案添加了状态性。而我上面提到的方案都是无状态的。有状态的系统会相互影响,比如使用外部系统生成主键,那么当数据操作增大时候,必定会造成在主键系统上资源竞争的事情发生。假设我们对主键系统上的竞争状态处理不好,非常有可能造成主键系统被死锁,这也就会产生我前文里说到的503错误。而无状态的系统是不存在资源竞争和死锁的问题,这洋就提升了系统的健壮性,无状态系统还有一个优势就是水平扩展非常方便。

  这里我列出单独主键生成系统的缺点不是想说明我认为这样的解决方式全然不可取。这个要看详细的业务场景,依据作者我的经验还没有找到一个非常合适使用单独主键生成器的场景。

  上文里我提出的方案还有个特点就是能保证数据在不同的物理表里均匀的分布。均匀分布能保证不同物理表的负载均衡,这样就不会产生系统热点。也不会让某台server比其它server做的事情少而闲置资源。均匀分配资源能够有效的利用资源,减少生产的成本提高生产的效率,可是均匀分布式数据往往会给我们业务运算带来非常多麻烦。

  水平拆分数据库后我们还要考虑水平扩展问题,比如假设我们事先使用了3台server完毕了水平拆分。假设系统执行到一定阶段,该表又遇到存储瓶颈了,我们就得水平扩容数据库,那么假设我们的水平拆分方案開始设计的不好。那么扩容时候就会碰到非常多的麻烦。

  以上问题将是我下篇文章里进行讨论的。今天就写到这里,祝大家生活愉快。

关于大型站点技术演进的思考(四)--存储的瓶颈(4)

标签:阶段   uuid   解决   准备   切割   完成   操作   设计原则   div   

原文地址:http://www.cnblogs.com/claireyuancy/p/7360446.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!