http://blog.sina.com.cn/s/blog_693f08470101ovfp.html
http://blog.sina.com.cn/s/blog_693f08470101omuq.html
映射的存储模型 – 面向列的存储和行存储
1个月没跟大家见面了,这个月真的是很累,做了很多事,天天加班,不过结还都不错,以后调休吧
双11不知道各位感觉如何?是不是觉得很平稳呢?
然后,又使用了阿里中间件的全部技术支持了某大型公司的企业的业务逻辑重构。让他们的系统业务架构有了一次全新的变化,让他们的系统具备了自由扩展的能力,从此不再担心系统无法支持用户增长了。顺便也就验证了我们解决问题的整体思路是一个可复制的思路,将会在未来有更广阔的发展空间
好,话题回到模型上,今天我们开启一个新的主题,就是映射的存储模型。换句话说,在了解了
回顾一下映射,映射的本质就是一个
我们已经知道了
这里我们需要举个例子,假定目前我们需要存储的数据是一组数据。
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的映射关系。当然真实存储的时候,不需要去存储key是bizOrderID这个信息的,因为这是所有列存所必须遵守的,而对于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’ |
在介绍了列存这种数据结构之后,我们来与行存做一个比较分析,来去看看为什么列存和行存会有完全不同的一些技术特性。
数据结构这种东西,没有一种能够解决所有的问题,更多地时候是在各种权衡和妥协中找到一个平衡点的过程,对于行存和列存,这东西也是一样的。。
古人云~任何不带使用场景讨论的数据结构优缺点分析都是耍流氓。
我们需要先看看用户怎么用我们的存储的。
应用总是希望快速的完成数据的写入得,而用户一般来说一定是按照整行的方式进行数据的写入,因为这是用户正常组织数据的方式嘛。。。
空间分析:
比如,要存储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才能存储的多组数字了。
基本上来说,压缩的核心思路就是有机的结合上面这两个元素,结合各种映射关系来完成数据压缩的。
在这个压缩核心思想的基础上,还有很多扩展的方法,最主要的方式就是把bitMap和Btree结合进行压缩,bitMap用来表示某个数据在整个数组内的重复出现的具体位置是哪些,又因为有多个数据,所以在bitMap上面再套一个btree,用来帮助寻找某个key是否存在,以及如果存在的话,具体位置有哪些。
可以发现,压缩比率很高的场景,一般来说都是数值类型的东西。所以如果你把一大组数值类的数据放在一起,并且这个数值类的数据重复度很高,或者比较有规律,那么做压缩的效果一般来说就会很好。
然而,对于行存储来说—就以我们的一行数据为例:
bizOrderID | sellerID | buyerId | content |
0 | 0 | 4 | ‘a’ |
这行数据里又出现了数字,又有字符,那么这种场景下就很难选择完全使用数值类压缩方式来做,而只能选择字典压缩了,很容易想象,字典压缩因为是通用压缩法,所以压缩比率也就比较一般了。。
而对于行存储而言,这方面就能获得很大的好处,因为数值类的数据被物理的放在一起了,而且性别啊,门牌号啊,电话号码啊这类明显有一定规律的数据被物理上的放在了一起,所以压缩效果好那就是自然而然的事情咯~
一般来说,行存储的压缩比率大概是1:1.5~2左右顶头了。 也就是能减少一倍的数据量。而对于列存储而言,大部分情况下列存储的压缩都能在1:20 ~ 1:30 以上,极大的减少了空间消耗。
空间消耗的减少意味着什么?原来必须存在磁盘的“大数据”现在可以用更少的机器,甚至是单机就可以搞定,节省机器成本,同时还能提升一个重要的指标,我们下面就立刻分析一下。
写入效率分析:
那么,想不想知道行存,列存是如何解决各类用户奇葩的多维度查询需求的? 下一篇我们将进入这一领域进行探索。