海量存储系列之七

在上一个章节,我们阐述了分布式场景下,事务的问题和一些可能的处理方式后,我们来到了下一章节

Key-value存储

这一章,我们将进入k-v场景,其实,在大部分场景下,如果某个产品宣称自己的写读tps超过其他存储n倍,一般来说都是从k-v这个角度入手进行优化的,主要入手的点是树的数据结构优化和锁的细化,一般都能在一些特定的场景获得5-10倍的性能提升。由此可见key-value存储对于整个数据存储模型是多么的重要。

好吧,那么我们来进入这个章节,用最简单和浅显的话语,阐述这些看起来很高深的理论吧 : )

在未来的几篇中,我们将大概的介绍和分析如下几种比较有特点的数据结构,并探讨其优势劣势以及适用的场景。

让我们先从映射入手吧,所谓映射,就是按照key找到value的过程,这个过程几乎就是我们处理数据的最核心数据结构了。

如何能够根据一个key找到对应的value呢?

一类是hash map.最简单的实现就是算一个key数据的hashCode.然后按照桶的大小取mod.塞到其中的一个桶里面去。如果出现冲突怎么办呢?append到这个桶内链表的尾部就行了。

还有一类呢,我们可以抽象的认为是一个有序结构。之所以把它归类到有序结构原因也很简单,因为只有有序才能做二分查找。。。举些有序结构的例子吧: 1. 数组 2. 各类平衡二叉树 3. B-树类族 4. 链表

这些数据结构如果想进行快速查找,都需要先让他们有序。然后再去做log2N的二分查找找到对应的key。

从原教旨上来说,这就是我们要用的key-value的主要结构了。

那么,hash和有序结构,他们之间有什么样的差别呢?我们来进行一下简单的比较

基本上来说,核心区别就是上面的这点,hash单次查询效率较高,但为了保证O(1)效率,对空间也有一定要求。而有序结构,查询效率基本是O(log2N)这个级别。但有序结构可以支持范围查找,而hash则很难支持。

所以,一般来说我们主要在使用的是有序结构来进行索引构建,因为经常需要查询范围。

不过,所有数据库几乎都支持hash索引,如果你的查询基本都是单值的,那么可以找一找稳定的hash索引,他们能从一定程度上提升查询的效率。

在这里,我们主要讨论有序结构,对于数据库或nosql来说,有序结构主要就是指b-tree或b-tree变种。那么我们先来介绍一下什么叫b-tree作为讨论磁盘结构的入门吧。

先上图(copy的,这是个b+tree。版权方请找我)

首先进行词汇科普:b-tree只有两类,一类叫b-tree,就是btree,还有一类是b+tree,但b-tree不是b”减”树的意思。这个大家不要再跟当年的我犯同样的错误哟 :__0

那么b树的核心是几个关键词

  1. 树高:一般来说,树的高度比较低。三到五层
  2. 数组:每一个node,都是一个“数组”,数组是很关键的决定性因素,我们后面写入和读取分析的时候会讲到。

没了呵呵

然后我们进行一下读取和写入的模拟。

读取来说:如果我要查找28这个数据对应的value是多少,路径大概是:首先走root节点,取出root node后,对该数组进行二分查找,发现35>28>17,所以进入branch节点中的第二个节点,取出该节点后再进行二分查找。发现30>28>26,所以进入branch节点的p2 value,取出该节点,对该三个值的数组进行二分查找,从而定位到28这个数据的对应value。

而写入删除则涉及到分裂和合并这两个btree最重要的操作,比如,要写入37,那么会先找到36所应该被插入的数组[36,60]这个数组,然后判断其是否有空,如果有空,则对该数组进行重新排序。而如果没有空,则必须要进行分裂。分裂的缘由是因为组成b-tree的每一个node,都是一个数组,数组最大的特性是,数组内元素个数是固定的。因此必须要把原有已经满掉的数组里面的一半的数据拿出来,放到新的一个新建立的空数组中,然后把要写入的数据写入到老或新的这两个数组里面的一个里面去。

【这里要留个问题给大家了,我想问一下,为什么b-tree要使用数组来存储数据呢?为什么不选择链表等结构呢?】

对于上面的这个小的b-tree sample里面呢,因为数组[35,60],数组已经满了,所以要进行分裂。于是数组在插入了新值以后,变成了两个[35,36] 和[60] ,然后再改变父节点的指针并依次传导上去即可。

当出现删除的时候,会可能需要进行合并的工作,也就是写入这个操作的反向过程。在一些场景中,因为不断地插入新的id,删除老的id,会造成b-tree的右倾,这时候需要有后台进程对这种倾向进行不断地调整。

基本上,这就是b-tree的运转过程了。

B+tree

B+tree 其实就是在原有b-tree的基础上。增加两条新的规则

  1. Branch节点不能直接查到数据后返回,所有数据必须读穿或写穿到leaf节点后才能返回成功
  2. 子叶节点的最后一个元素是到下一个leaf节点的指针。

这样做的原因是,更方便做范围查询,在b+树种,如果要查询20~56.只需要找到20这个起始节点,然后顺序遍历,不再需要不断重复的访问branch和root节点了。

发现每一种数据结构都需要去进行简介才能够比较方便的了解到他们的特性,所以在后续的章节还会介绍几种有代表性的树的结构都会针对性的加以介绍。

本文来源于"阿里中间件团队播客",原文发表时间"  2011-12-22"

时间: 2024-11-10 11:27:05

海量存储系列之七的相关文章

海量存储系列之八

首先来回答一个问题:为什么在磁盘中要使用b+树来进行文件存储呢? 原因还是因为树的高度低得缘故,磁盘本身是一个顺序读写快,随机读写慢的系统,那么如果想高效的从磁盘中找到数据,势必需要满足一个最重要的条件:减少寻道次数. 我们以平衡树为例进行对比,就会发现问题所在了: 先上个图 这是个平衡树,可以看到基本上一个元素下只有两个子叶节点 抽象的来看,树想要达成有效查找,势必需要维持如下一种结构: 树的子叶节点中,左子树一定小于等于当前节点,而当前节点的右子树则一定大于当前节点.只有这样,才能够维持全局

海量存储系列之十一

上一期我们主要在介绍hash相关的切分方式,那么这次我们来看一下有序结构的切分 有序结构的拆分,目前主要就是使用树或类似树的结构进行拆分,这里主要就是指HBase和MongoDB. 使用树结构切分,带来的好处就如hbase和mongoDB的宣传标语一样,可以无缝的实现自由扩展.但反过来,带来的问题其实也不少,下面我们一起来看一看吧. 首先复习B树知识http://qing.weibo.com/1765738567/693f0847330008ii.html 在B树中,最关键的处理逻辑是如果单个节

海量存储系列之九

终于来到了COLA树系,这套东西目前来看呢,确实不如LSM火,不过作为可选方案,也是个值得了解的尝试,不过这块因为只有一组MIT的人搞了个东西出来,所以其实真正的方案也语焉不详的.从性能来说,tokuDB的写入性能很高,但更新似乎不是很给力,查询较好,占用较少的内存. http://www.mysqlperformanceblog.com/2009/04/28/detailed-review-of-tokutek-storage-engine/ 这里有一些性能上的指标和分析性文字.确实看起来很心

海量存储系列之十二

本章,我们主要来讨论数据的管理和扩容中最重要的一个部分,数据迁移. 数据迁移是数据运维中最为重要的一个部分,在前面的文章中已经提到过,作为有状态的数据节点,在互联网行业的主要追求就是,无限的水平扩展能力,这种水平扩展,主要用于解决两类问题,一类是磁盘空间不足的问题,一类是性能不足的问题. 为了达到这种能力,一般来说主要也就是这样一个思路,尽可能的让数据不动,只通过规则变动的方式来完成扩容,如果这种方式无法满足要求,那么再通过移动数据的方式,来满足其他的一些需求. 下面来进行下分析. 只通过变动规

海量存储系列之十三

在上一章中,我们主要介绍了规则引擎中最重要的一个部分,自动扩容,在今天的章节,我们主要还是介绍一下我们在淘宝TDDL中的工程实践吧. 首先从原理开始吧. 规则引擎是什么呢? 对应在上述例子里面,其实就是DBNum = pk % 3 这个规则. 他的变化可能很多,比如对于一致性hash则变为一个if - else 的表达式(见前面) 也可能有其他的变化. 所以,我们要回归本源,问一个问题,什么是规则引擎? 抽象来看,规则引擎在做的事情是,根据一组输入条件(例如主键id,或者用户id+时间,或者一个

海量存储系列之四

单机事务: 其实在上面介绍ACID的时候 我们已经提到了一种最简单的实现方式,就是锁的实现方式. 从原理来看,事务是个变态而复杂的事情.其实如果是序列化的话呢,那么实现起来一定是非常简单的. 但问题就在于,这样性能实在比较低,于是,就有了非常多的方案,为了能哪怕减少一个地方的锁,或者降低一个地方的锁的级别,就付出大量的时间和代码加以实现. 那么,让我们以崇敬的心情,去拜读一下他们的劳动成果吧~ 在上一篇中,我们谈了事务管理的四个核心要素,其中有两个要素是和性能紧密相关的,其实也就是需要涉及到锁的

海量存储系列之一

一个数据库,我们可以抽象的认为由下面的一个逻辑结构组成,刨除意义不大的视图,存储过程,外键限制等之后,我们就剩下了下面的这张图: 从API来说,也就是SQL,结构化查询语言,这个东东我们后面再去细说,先来看看这个关系代数模型. 之所以要从这里开始,主要的原因是因为,这是最受到关注的一个部分,自大从一开始做分布式数据层开始,被人问得最多的问题就是:1. 切分以后如何做join.2.如何进行分布式事务.. 可惜,现在我也没有一个方法能做到100%让您满意..因为,没有银弹,只有取舍. 取舍的原则,也

海量存储系列之六

上次我们讲到,单机事务个我们面临的问题,下面我们来说一些我所知的解决的方法. 在我开始做淘宝数据层的时候,被问得最多的无非也就是:如何做事务,如何做join.至今仍然如此,我一般都会简单而明确的跟对方说:没有高效的实现方法. 虽然没有高效的实现,但实现还是有的.作为引子,我们先来介绍一下这种实现的方式. 我们仍然以上一次讲到的bob和smith为例子来说明好了. 开始的时候.Bob要给smith100块,那么实际上事务中要做的事情是 事务开始时查询bob有多少钱.如果有足够多的钱让bob的账户

海量存储系列之五

在上一章节,我们一起浏览了如何进行单机事务操作.下面我们来看一下分布式场景中我们碰到的问题吧. 需要说明的一点是,这里涉及到的权衡点非常的多.就我短短的工作经验里面,也只是能够简单的涉猎一部分,因为在事务这个领域,目前大家都在尝试提出各种各样的不同的方法,而在taobao,我们目前也没有完美的解决这个问题,更多的是在权衡,在金钱和开发成本之间,做出选择. 那么,我们就先从问题开始,来看一下原来的事务出了什么问题. 在事务中,有ACID四种属性.(见上篇文章) 在分布式场景中,我们看引入了什么因素