映射的存储模型

http://blog.sina.com.cn/s/blog_693f08470101ovfp.html

http://blog.sina.com.cn/s/blog_693f08470101omuq.html

映射的存储模型  面向列的存储和行存储

 


1个月没跟大家见面了,这个月真的是很累,做了很多事,天天加班,不过结还都不错,以后调休吧 ~

11不知道各位感觉如何?是不是觉得很平稳呢? 在这次双11 里面,中间件团队配合业务团队使用了一种全新的全链路压测方式进行了线上性能验证,提前就预演了所有可能的压力,因此这次双 11是我五年双11 支持过程中最淡定的一次 ~

 


然后,又使用了阿里中间件的全部技术支持了某大型公司的企业的业务逻辑重构。让他们的系统业务架构有了一次全新的变化,让他们的系统具备了自由扩展的能力,从此不再担心系统无法支持用户增长了。顺便也就验证了我们解决问题的整体思路是一个可复制的思路,将会在未来有更广阔的发展空间  玩法变了啊,各位感受到了么?映射的存储模型 <wbr>– <wbr>面向列的存储和行存储

 


好,话题回到模型上,今天我们开启一个新的主题,就是映射的存储模型。换句话说,在了解了 k-v的基本机制之后,下一步我们研究一下我们的数据应该如何使用映射。


回顾一下映射,映射的本质就是一个 map,给出map key map就返回key 所对应的 value。我们也介绍过,在计算机里的大部分事情其实都可以被表示为一个 map,比如,给定扇区号,返回给定扇区的数据等等。

 


我们已经知道了 Map,而在这里我们最需要解决的问题就是,给定一组数据,应该如何存放到我们的映射里呢?其实存放的方法非常多样,而核心的问题是用户的实际需求。

 


这里我们需要举个例子,假定目前我们需要存储的数据是一组数据。

bizOrderID

sellerID

buyerId

content

0

0

4

‘a’

1

0

3

‘c’

2

0

2

‘I’

3

0

1

‘d’

4

3

4

‘b’

5

2

4

‘f’

 

 

针对这样一组数据,我们其实是有很多种不同的存储方式的。

 

第一种,以pk作为key,以其他数据作为value 

Map

Key

Value

{bizOrderId:0}

{sellerID:0,buyerId:4,content:’a’}

{bizOrderId:1}

{sellerID:0,buyerId:3,content:’c’}

{bizOrderId:2}

{sellerID:0,buyerId:2,content:’i’}

{bizOrderId:3}

{sellerID:0,buyerId:1,content:’d’}

{bizOrderId:4}

{sellerID:3,buyerId:4,content:’b’}

{bizOrderId:5}

{sellerID:2,buyerId:4,content:’f’}

 

可以看出,在这种存储的实现中,每一行的数据都冗余了列的名字和该列所对应的类型信息。

这种方式有个专用的名词,就叫面向列的存储,当然,虽然出现了“列”字,但各位可千万不要望文生义,这面向列的存储,跟我们后面要看到的列存基本不沾边。。

这种存储方式,最大的好处就是每个value里面的数据可以完全自己定义,目前主流的实现是使用json来存储value数据,这样,如果业务要求增加一个列,那就在json拼装的时候额外增加一个列就行了。而如果业务需要减少一个列,也可以直接在代码里拼装,减少了运维的成本。

不过这样做也不是完全没有代价,额外的冗余数据就意味着额外的空间消耗,所以目前通用的优化方案,利用了列的个数本身是比较有限的,这个特性,于是利用一个新的map_bit. Key为列的名字,value为一个bit . 从而减少冗余数据带来的过多空间消耗。如下:

Map_bit

Key

Value(bit)

sellerID

‘a’

bizOrderId

‘b’

buyerId

‘c’

content

‘d’

 

Map

Key

Value

{b:0}

{a:0,c:4,d:’a’}

{b:1}

{a:0,c:3,d:’c’}

{b:2}

{a:0,c:2,d:’i’}

{b:3}

{a:0,c:1,d:’d’}

{b:4}

{a:3,c:4,d:’b’}

{b:5}

{a:2,c:4,d:’f’}

 

有了这么个结构,面向列的存储里面数据冗余的大问题得到了部分的解决,然而这种模式还有优化的空间。在大部分情况下,我们的业务模型基本上是稳定的,不会过于频繁的发生变化。如果我们能够有这样的假定,那么我们就可以将列所对应的位置固定下来,用一个叫map_schema的表格来存储,不就不需要冗余上面的那些’a’,’b’,’c’,’d’bit信息了。可以更多地节省存储空间啊。

map_schema

Key

Value

sellerID

表格内的位置:1

bizOrderId

表格内的位置:0

buyerId

表格内的位置:2

content

表格内的位置:3

 

Map

0

0

4

‘a’

1

0

3

‘c’

2

0

2

‘I’

3

0

1

‘d’

4

3

4

‘b’

5

2

4

‘f’

 

这种方式就是我们最常见的行存了,这种方式的比较于面向列的存储,最主要的优势就是空间消耗更少,而且申请空间的效率更高,因为用户在开始的时候就已经指定了所有数据所需要的数据类型(空间消耗),因此消耗的空间是相对比较固定的。

而对于面向列的存储而言,由于不能提前假定每一行数据的大小,所以只能使用变长数据存储格式来存储数据。 因此也会有更多的空间浪费,并且提高申请存储空间的代价。

无论是面向列的存储,还是行存,他们的写入都可以只通过一次map.put(key,value)就可以完成。所以写入的效率比较高,如果按照pk这一列做查询,速度也会非常快,因为也只需要一个map.get(key)就可以取出需要的数据,速度自然也就是很快的。


上上周我们讨论了行存储,那么现在呢我们来看看列存的存储方式

 

在一个文件块内是能够放多个数据的,行存的存储方式是一个文件块内放置多行的数据。而列存,顾名思义,就是一个文件块内放置多列的数据了呗~~

 

举个例子:

对上面的那张表,如果我们按照列的方式存储到映射里,应该如何存储呢?

首先,我们需要一个映射map_columns来存储每个列的具体映射表是哪个。

 

 

Map_columns

Key

Value(bit)

sellerID

Map_sellerID

bizOrderID

Map_bizOrderID

buyerID

Map_buyerID

content

Map_content

 

第一组数据,用于存储sellerID的映射关系。当然真实存储的时候,不需要去存储keybizOrderID这个信息的,因为这是所有列存所必须遵守的,而对于value而言,Map的名字其实就标记了这组映射的value值的所有信息,所以也不需要记录的,这里用()标记出来,主要是为了方便理解

Map_sellerID

Key(bizOrderID)

Value(sellerID)

0

0

1

0

2

0

3

0

4

3

 

第二组数据(buyerID),同样的,数据的类型信息在Map_columns里面就记录好了。这里直接记录真正的数据就行了。

Map_buyerID

Key(bizOrderID)

Value(buyerID)

0

4

1

3

2

2

3

1

4

4

5

4

 

第三组数据(content)

Map_content

Key(bizOrderID)

Value(buyerID)

0

‘a’

1

‘c’

2

‘I’

3

‘d’

4

‘b’

5

‘f’

 

 

在介绍了列存这种数据结构之后,我们来与行存做一个比较分析,来去看看为什么列存和行存会有完全不同的一些技术特性。

 

数据结构这种东西,没有一种能够解决所有的问题,更多地时候是在各种权衡和妥协中找到一个平衡点的过程,对于行存和列存,这东西也是一样的。。

 

古人云~任何不带使用场景讨论的数据结构优缺点分析都是耍流氓。

我们需要先看看用户怎么用我们的存储的。

应用总是希望快速的完成数据的写入得,而用户一般来说一定是按照整行的方式进行数据的写入,因为这是用户正常组织数据的方式嘛。。。

 

空间分析:

         对行存储来说,压缩一般是按照块来进行的,常见的压缩方式的核心思想其实也并不复杂。

         第一个思路是字典压缩,也就是将固定的bytes序列换成更小的一个bit来表示,这样就可以用更小的空间来存储更多地内容了,比如人类在这世界上只有三种性别,所以字典只需要2bit(存储四个type),所以就算人类人口成长到了100亿,每一个人还是只需要两个bit就可以存储嘛。

         第二个思路就是bitMap(目前很热的bloomFilter其实也是bitmap思想的一个特例),所谓bitMap,是利用bit数组的下标的true(1)/false(0)来标记某个数字是否已经存入到这个数组内。

比如,要存储0,3,5,8四个数字,所有数字的最大值是8.那么我们可以建立一个bitMap来存储这组数字:

true(1)/false(0)

1

0

0

1

0

1

0

0

1

位置

0

1

2

3

4

5

6

7

8

 

这样,我们只需要一个bit数组就可以存储原来需要多个bytes才能存储的多组数字了。

 

 

基本上来说,压缩的核心思路就是有机的结合上面这两个元素,结合各种映射关系来完成数据压缩的。

在这个压缩核心思想的基础上,还有很多扩展的方法,最主要的方式就是把bitMapBtree结合进行压缩,bitMap用来表示某个数据在整个数组内的重复出现的具体位置是哪些,又因为有多个数据,所以在bitMap上面再套一个btree,用来帮助寻找某个key是否存在,以及如果存在的话,具体位置有哪些。

 

可以发现,压缩比率很高的场景,一般来说都是数值类型的东西。所以如果你把一大组数值类的数据放在一起,并且这个数值类的数据重复度很高,或者比较有规律,那么做压缩的效果一般来说就会很好。

 

然而,对于行存储来说就以我们的一行数据为例:

bizOrderID

sellerID

buyerId

content

0

0

4

‘a’

这行数据里又出现了数字,又有字符,那么这种场景下就很难选择完全使用数值类压缩方式来做,而只能选择字典压缩了,很容易想象,字典压缩因为是通用压缩法,所以压缩比率也就比较一般了。。

而对于行存储而言,这方面就能获得很大的好处,因为数值类的数据被物理的放在一起了,而且性别啊,门牌号啊,电话号码啊这类明显有一定规律的数据被物理上的放在了一起,所以压缩效果好那就是自然而然的事情咯~

 

一般来说,行存储的压缩比率大概是1:1.5~2左右顶头了。 也就是能减少一倍的数据量。而对于列存储而言,大部分情况下列存储的压缩都能在1:20 ~ 1:30 以上,极大的减少了空间消耗。

空间消耗的减少意味着什么?原来必须存在磁盘的“大数据”现在可以用更少的机器,甚至是单机就可以搞定,节省机器成本,同时还能提升一个重要的指标,我们下面就立刻分析一下。

 

写入效率分析:

         对于行存储来说,一行数据的写入就对应了一个map.put操作,所以效率自然是很高。

         而对于列存储来说,一行数据的写入,假设这个数据列有4列,那么就会对应4map.put操作,如果有6列,那么就对应6map.put操作。效率低于列存。

 

         然而,这并不是绝对的,让我们把条件稍微做个变换,效果就会立刻不同,如果用户不需要单次写入后立刻返回,而是可以一次写入100条以上的数据,那么形势就会立刻发生逆转!

         因为列存是将压缩后的数据写入磁盘的,联想压缩比率,原来需要写20mb的数据,现在可能只写1mbcpu和内存相比,磁盘目前仍然是整个系统的核心瓶颈,而且差距很大,因此减少硬盘写入就直接的减少了写入一组数据所需要的时间。

         因此,列存在数据导入的场景下拥有远远快于行存储的数据写入速度也就不足为奇了。

 

那么,想不想知道行存,列存是如何解决各类用户奇葩的多维度查询需求的? 下一篇我们将进入这一领域进行探索。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值