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

若是数据库须要进行水平拆分,这实际上是一件很开心的事情,由于它表明公司的业务正在迅猛的增加,对于开发人员而言那就是有不尽的项目能够作,虽然会感受很忙,可是人过的充实,内心也踏实。html

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

  数据库的水平拆分是数据库垂直拆分的升级版,它和垂直拆分更像继承机制里的父子关系,所以水平拆分后,垂直拆分所遇到的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台服务器完成了水平拆分,若是系统运行到必定阶段,该表又遇到存储瓶颈了,咱们就得水平扩容数据库,那么若是咱们的水平拆分方案开始设计的很差,那么扩容时候就会碰到不少的麻烦。

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

 

 

 

 转自 http://www.cnblogs.com/sharpxiajun/p/4262983.html

相关文章
相关标签/搜索