
在说HBase之前,我想再唠叨几句。做互联网应用的哥们儿应该都清楚,互联网应用这东西,你没办法预测你的系统什么时候会被多少人访问,你面临的用户到底有多少,说不定今天你的用户还少,明天系统用户就变多了,结果您的系统应付不过来了了,不干了,这岂不是咱哥几个的悲哀,说时髦点就叫“杯具啊”。\x0d\\x0d\其实说白了,这些就是事先没有认清楚互联网应用什么才是最重要的。从系统架构的角度来说,互联网应用更加看重系统性能以及伸缩性,而传统企业级应用都是比较看重数据完整性和数据安全性。那么我们就来说说互联网应用伸缩性这事儿对于伸缩性这事儿,哥们儿我也写了几篇博文,想看的兄弟可以参考我以前的博文,对于web server,app server的伸缩性,我在这里先不说了,因为这部分的伸缩性相对来说比较容易一点,我主要来回顾一些一个慢慢变大的互联网应用如何应对数据库这一层的伸缩。\x0d\\x0d\首先刚开始,人不多,压力也不大,搞一台数据库服务器就搞定了,此时所有的东东都塞进一个Server里,包括web server,app server,db server,但是随着人越来越多,系统压力越来越多,这个时候可能你把web server,app server和db server分离了,好歹这样可以应付一阵子,但是随着用户量的不断增加,你会发现,数据库这哥们不行了,速度老慢了,有时候还会宕掉,所以这个时候,你得给数据库这哥们找几个伴,这个时候Master-Salve就出现了,这个时候有一个Master Server专门负责接收写 *** 作,另外的几个Salve Server专门进行读取,这样Master这哥们终于不抱怨了,总算读写分离了,压力总算轻点了,这个时候其实主要是对读取 *** 作进行了水平扩张,通过增加多个Salve来克服查询时CPU瓶颈。一般这样下来,你的系统可以应付一定的压力,但是随着用户数量的增多,压力的不断增加,你会发现Master server这哥们的写压力还是变的太大,没办法,这个时候怎么办呢?你就得切分啊,俗话说“只有切分了,才会有伸缩性嘛”,所以啊,这个时候只能分库了,这也是我们常说的数据库“垂直切分”,比如将一些不关联的数据存放到不同的库中,分开部署,这样终于可以带走一部分的读取和写入压力了,Master又可以轻松一点了,但是随着数据的不断增多,你的数据库表中的数据又变的非常的大,这样查询效率非常低,这个时候就需要进行“水平分区”了,比如通过将User表中的数据按照10W来划分,这样每张表不会超过10W了。\x0d\\x0d\综上所述,一般一个流行的web站点都会经历一个从单台DB,到主从复制,到垂直分区再到水平分区的痛苦的过程。其实数据库切分这事儿,看起来原理貌似很简单,如果真正做起来,我想凡是sharding过数据库的哥们儿都深受其苦啊。对于数据库伸缩的文章,哥们儿可以看看后面的参考资料介绍。\x0d\\x0d\好了,从上面的那一堆废话中,我们也发现数据库存储水平扩张scale out是多么痛苦的一件事情,不过幸好技术在进步,业界的其它弟兄也在努力,09年这一年出现了非常多的NoSQL数据库,更准确的应该说是No relation数据库,这些数据库多数都会对非结构化的数据提供透明的水平扩张能力,大大减轻了哥们儿设计时候的压力。下面我就拿Hbase这分布式列存储系统来说说。\x0d\\x0d\一 Hbase是个啥东东? \x0d\在说Hase是个啥家伙之前,首先我们来看看两个概念,面向行存储和面向列存储。面向行存储,我相信大伙儿应该都清楚,我们熟悉的RDBMS就是此种类型的,面向行存储的数据库主要适合于事务性要求严格场合,或者说面向行存储的存储系统适合OLTP,但是根据CAP理论,传统的RDBMS,为了实现强一致性,通过严格的ACID事务来进行同步,这就造成了系统的可用性和伸缩性方面大大折扣,而目前的很多NoSQL产品,包括Hbase,它们都是一种最终一致性的系统,它们为了高的可用性牺牲了一部分的一致性。好像,我上面说了面向列存储,那么到底什么是面向列存储呢?Hbase,Casandra,Bigtable都属于面向列存储的分布式存储系统。看到这里,如果您不明白Hbase是个啥东东,不要紧,我再总结一下下:\x0d\\x0d\Hbase是一个面向列存储的分布式存储系统,它的优点在于可以实现高性能的并发读写 *** 作,同时Hbase还会对数据进行透明的切分,这样就使得存储本身具有了水平伸缩性。\x0d\\x0d\二 Hbase数据模型 \x0d\HBase,Cassandra的数据模型非常类似,他们的思想都是来源于Google的Bigtable,因此这三者的数据模型非常类似,唯一不同的就是Cassandra具有Super cloumn family的概念,而Hbase目前我没发现。好了,废话少说,我们来看看Hbase的数据模型到底是个啥东东。\x0d\\x0d\在Hbase里面有以下两个主要的概念,Row key,Column Family,我们首先来看看Column family,Column family中文又名“列族”,Column family是在系统启动之前预先定义好的,每一个Column Family都可以根据“限定符”有多个column下面我们来举个例子就会非常的清晰了。\x0d\\x0d\假如系统中有一个User表,如果按照传统的RDBMS的话,User表中的列是固定的,比如schema 定义了name,age,sex等属性,User的属性是不能动态增加的。但是如果采用列存储系统,比如Hbase,那么我们可以定义User表,然后定义info 列族,User的数据可以分为:info:name = zhangsan,info:age=30,info:sex=male等,如果后来你又想增加另外的属性,这样很方便只需要info:newProperty就可以了。\x0d\\x0d\也许前面的这个例子还不够清晰,我们再举个例子来解释一下,熟悉SNS的朋友,应该都知道有好友Feed,一般设计Feed,我们都是按照“某人在某时做了标题为某某的事情”,但是同时一般我们也会预留一下关键字,比如有时候feed也许需要url,feed需要image属性等,这样来说,feed本身的属性是不确定的,因此如果采用传统的关系数据库将非常麻烦,况且关系数据库会造成一些为null的单元浪费,而列存储就不会出现这个问题,在Hbase里,如果每一个column 单元没有值,那么是占用空间的。下面我们通过两张图来形象的表示这种关系:\x0d\\x0d\上图是传统的RDBMS设计的Feed表,我们可以看出feed有多少列是固定的,不能增加,并且为null的列浪费了空间。但是我们再看看下图,下图为Hbase,Cassandra,Bigtable的数据模型图,从下图可以看出,Feed表的列可以动态的增加,并且为空的列是不存储的,这就大大节约了空间,关键是Feed这东西随着系统的运行,各种各样的Feed会出现,我们事先没办法预测有多少种Feed,那么我们也就没有办法确定Feed表有多少列,因此Hbase,Cassandra,Bigtable的基于列存储的数据模型就非常适合此场景。说到这里,采用Hbase的这种方式,还有一个非常重要的好处就是Feed会自动切分,当Feed表中的数据超过某一个阀值以后,Hbase会自动为我们切分数据,这样的话,查询就具有了伸缩性,而再加上Hbase的弱事务性的特性,对Hbase的写入 *** 作也将变得非常快。\x0d\\x0d\上面说了Column family,那么我之前说的Row key是啥东东,其实你可以理解row key为RDBMS中的某一个行的主键,但是因为Hbase不支持条件查询以及Order by等查询,因此Row key的设计就要根据你系统的查询需求来设计了额。我还拿刚才那个Feed的列子来说,我们一般是查询某个人最新的一些Feed,因此我们Feed的Row key可以有以下三个部分构成,这样以来当我们要查询某个人的最进的Feed就可以指定Start Rowkey为,End Rowkey为来查询了,同时因为Hbase中的记录是按照rowkey来排序的,这样就使得查询变得非常快。\x0d\\x0d\三 Hbase的优缺点 \x0d\1 列的可以动态增加,并且列为空就不存储数据,节省存储空间\x0d\\x0d\2 Hbase自动切分数据,使得数据存储自动具有水平scalability\x0d\\x0d\3 Hbase可以提供高并发读写 *** 作的支持\x0d\\x0d\Hbase的缺点:\x0d\\x0d\1 不能支持条件查询,只支持按照Row key来查询\x0d\\x0d\2 暂时不能支持Master server的故障切换,当Master宕机后,整个存储系统就会挂掉\x0d\\x0d\四补充\x0d\1数据类型,HBase只有简单的字符类型,所有的类型都是交由用户自己处理,它只保存字符串。而关系数据库有丰富的类型和存储方式。\x0d\2数据 *** 作:HBase只有很简单的插入、查询、删除、清空等 *** 作,表和表之间是分离的,没有复杂的表和表之间的关系,而传统数据库通常有各式各样的函数和连接 *** 作。 \x0d\3存储模式:HBase是基于列存储的,每个列族都由几个文件保存,不同的列族的文件时分离的。而传统的关系型数据库是基于表格结构和行模式保存的 \x0d\4数据维护,HBase的更新 *** 作不应该叫更新,它实际上是插入了新的数据,而传统数据库是替换修改\x0d\5可伸缩性,Hbase这类分布式数据库就是为了这个目的而开发出来的,所以它能够轻松增加或减少硬件的数量,并且对错误的兼容性比较高。而传统数据库通常需要增加中间层才能实现类似的功能
Region内每个ColumnFamily的数据组成一个Store。每个Store内包括一个MemStore和若干个StoreFile(HFile)组成。
HBase为了方便按照RowKey进行检索,要求HFile中数据都按照RowKey进行排序,Memstore数据在flush为HFile之前会进行一次排序
为了减少flush过程对读写的影响,HBase采用了类似于两阶段提交的方式,将整个flush过程分为三个阶段:
要避免“写阻塞”,貌似让Flush *** 作尽量的早于达到触发“写 *** 作”的阈值为宜。但是,这将导致频繁的Flush *** 作,而由此带来的后果便是读性能下降以及额外的负载。
每次的Memstore Flush都会为每个CF创建一个HFile。频繁的Flush就会创建大量的HFile。这样HBase在检索的时候,就不得不读取大量的HFile,读性能会受很大影响。
为预防打开过多HFile及避免读性能恶化,HBase有专门的HFile合并处理(HFile Compaction Process)。HBase会周期性的合并数个小HFile为一个大的HFile。明显的,有Memstore Flush产生的HFile越多,集群系统就要做更多的合并 *** 作(额外负载)。更糟糕的是:Compaction处理是跟集群上的其他请求并行进行的。当HBase不能够跟上Compaction的时候(同样有阈值设置项),会在RS上出现“写阻塞”。像上面说到的,这是最最不希望的。
提示 :严重关切RS上Compaction Queue 的size。要在其引起问题前,阻止其持续增大。
想了解更多HFile 创建和合并,可参看 Visualizing HBase Flushes And Compactions 。
理想情况下,在不超过hbaseregionserverglobalmemstoreupperLimit的情况下,Memstore应该尽可能多的使用内存(配置给Memstore部分的,而不是真个Heap的)。下图展示了一张“较好”的情况:
hbase使用的是jdk提供的ConcurrentSkipListMap,并对其进行了的封装,Map结构是<KeyValue,KeyValue>的形式。Concurrent表示线程安全。
SkipList是一种高效的数据结构,之前专门写过文章,这里就不表了
写入MemStore中的KV,被记录在kvset中。根据JVM内存的垃圾回收策略,在如下条件会触发Full GC。 1、内存满或者触发阈值。 2、内存碎片过多,造成新的分配找不到合适的内存空间。 RS上服务多个Region,如果不对KV的分配空间进行控制的话,由于访问的无序性以及KV长度的不同,每个Region上的KV会无规律地分散在内存上。Region执行了MemStore的Flush *** 作,再经过JVM GC之后就会出现零散的内存碎片现象,而进一步数据大量写入,就会触发Full-GC。
为了解决因为内存碎片造成的Full-GC的现象,RegionServer引入了MSLAB(HBASE-3455)。MSLAB全称是MemStore-Local Allocation Buffers。它通过预先分配连续的内存块,把零散的内存申请合并,有效改善了过多内存碎片导致的Full GC问题。 MSLAB的工作原理如下: 1、在MemStore初始化时,创建MemStoreLAB对象allocator。 2、创建一个2M大小的Chunk数组,偏移量起始设置为0。Chunk的大小可以通过参数hbasehregionmemstoremslabchunksize调整。 3、 当MemStore有KeyValue加入时,maybeCloneWithAllocator(KeyValue)函数调用allocator为其查找KeyValuegetBuffer()大小的空间,若KeyValue的大小低于默认的256K,会尝试在当前Chunk下查找空间,如果空间不够,MemStoreLAB重新申请新的Chunk。选中Chunk之后,会修改offset=原偏移量+KeyValuegetBuffer()length。chunk内控制每个KeyValue大小由hbasehregionmemstoremslabmaxallocation配置。 4、 空间检查通过的KeyValue,会拷贝到Chunk的数据块中。此时,原KeyValue由于不再被MemStore引用,会在接下来的JVM的Minor GC被清理。
MSLAB解决了因为碎片造成Full GC的问题,然而在MemStore被Flush到文件系统时,没有reference的chunk,需要GC来进行回收,因此,在更新 *** 作频繁发生时,会造成较多的Young GC。 针对该问题,HBASE-8163提出了MemStoreChunkPool的解决方案,方案已经被HBase-095版本接收。它的实现思路: 1、 创建chunk池来管理没有被引用的chunk,不再依靠JVM的GC回收。 2、 当一个chunk没有引用时,会被放入chunk池。 3、chunk池设置阈值,如果超过了,则会放弃放入新的chunk到chunk池。 4、 如果当需要新的chunk时,首先从chunk池中获取。 根据patch的测试显示,配置MemStoreChunkPool之后,YGC降低了40%,写性能有5%的提升。如果是095以下版本的用户,可以参考HBASE-8163给出patch。
WordCountHbaseReaderMapper类继承了TableMapper抽象类,TableMapper类专门用于完成MapReduce中Map过程与Hbase表之间的 *** 作。此时的map(ImmutableBytesWritablekey,Resultvalue,Contextcontext)方法,第一个参数key为Hbase表的rowkey主键,第二个参数value为key主键对应的记录集合,此处的map核心实现是遍历key主键对应的记录集合value,将其组合成一条记录通过contentxwrite(key,value)填充到键值对中。详细源码请参考:WordCountHbaseReader\src\com\zonesion\hbase\WordCountHbaseReaderjavapublicstaticclassWordCountHbaseReaderMapperextendsTableMapper{@Overrideprotectedvoidmap(ImmutableBytesWritablekey,Resultvalue,Contextcontext)throwsIOException,InterruptedException{StringBuffersb=newStringBuffer("");for(Entryentry:valuegetFamilyMap("content"getBytes())entrySet()){Stringstr=newString(entrygetValue());//将字节数组转换为String类型if(str!=null){sbappend(newString(entrygetKey()));sbappend(":");sbappend(str);}contextwrite(newText(keyget()),newText(newString(sb)));}}}3、Reducer函数实现此处的WordCountHbaseReaderReduce实现了直接输出Map输出的键值对,没有对其做任何处理。详细源码请参考:WordCountHbaseReader\src\com\zonesion\hbase\WordCountHbaseReaderjavapublicstaticclassWordCountHbaseReaderReduceextendsReducer{privateTextresult=newText();@Overrideprotectedvoidreduce(Textkey,Iterablevalues,Contextcontext)throwsIOException,InterruptedException{for(Textval:values){resultset(val);contextwrite(key,result);}}}4、驱动函数实现与WordCount的驱动类不同,在Job配置的时候没有配置jobsetMapperClass(),而是用以下方法执行Mapper类:TableMapReduceUtilinitTableMapperJob(tablename,scan,WordCountHbaseReaderMapperclass,Textclass,Textclass,job);该方法指明了在执行job的Map过程时,数据输入源是hbase的tablename表,通过扫描读入对象scan对表进行全表扫描,为Map过程提供数据源输入,通过WordCountHbaseReaderMapperclass执行Map过程,Map过程的输出key/value类型是Textclass与Textclass,最后一个参数是作业对象。特别注意:这里声明的是一个最简单的扫描读入对象scan,进行表扫描读取数据,其中scan可以配置参数,这里为了例子简单不再详述,用户可自行尝试。详细源码请参考:WordCountHbaseReader\src\com\zonesion\hbase\WordCountHbaseReaderjavapublicstaticvoidmain(String[]args)throwsException{Stringtablename="wordcount";Configurationconf=HBaseConfigurationcreate();confset("hbasezookeeperquorum","Master");String[]otherArgs=newGenericOptionsParser(conf,args)getRemainingArgs();if(otherArgslength!=1){Systemerrprintln("Usage:WordCountHbaseReader");Systemexit(2);}Jobjob=newJob(conf,"WordCountHbaseReader");jobsetJarByClass(WordCountHbaseReaderclass);//设置任务数据的输出路径;FileOutputFormatsetOutputPath(job,newPath(otherArgs[0]));jobsetReducerClass(WordCountHbaseReaderReduceclass);Scanscan=newScan();TableMapReduceUtilinitTableMapperJob(tablename,scan,WordCountHbaseReaderMapperclass,Textclass,Textclass,job);//调用jobwaitForCompletion(true)执行任务,执行成功后退出;Systemexit(jobwaitForCompletion(true)0:1);}5、部署运行1)启动Hadoop集群和Hbase服务[hadoop@K-Master~]$start-dfssh#启动hadoopHDFS文件管理系统[hadoop@K-Master~]$start-mapredsh#启动hadoopMapReduce分布式计算服务[hadoop@K-Master~]$start-hbasesh#启动Hbase[hadoop@K-Master~]$jps#查看进程22003HMaster10611SecondaryNameNode22226Jps21938HQuorumPeer10709JobTracker22154HRegionServer20277Main10432NameNode
以上就是关于为什么说hbase是一个面向列的数据库全部的内容,包括:为什么说hbase是一个面向列的数据库、深入理解HBASE(3.4)RegionServer-Memstore、spark1.2.1实现读取hbase的数据后怎么实现实时查询等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)