欢迎投稿

今日深度:

HBase,

HBase,


HBase

  • HBASE简介
  • 行存储VS列存储
  • HBase基本概念
  • HBase完全分布式安装
  • HBase基础指令
  • HBase API
        • 创建表
        • 插入数据
        • 批量插入数据
        • 获取数据
        • 整表扫描
        • 删除数据
        • 删除表
        • 过滤器
  • HBase物理存储原理
  • HBase系统
      • HMaster节点
      • HRegionServer
            • WAL
            • BlockCache
            • MemStore
            • HFile格式
            • HFile的Compaction机制
      • HBase的第一次读写
            • HBase写流程
            • HBase读流程
  • HBase调优

HBASE简介

HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文《Bigtable》一个结构化数据的“分布式存储系统”。HBase是Apache的Hadoop项目的子项目,具有低延迟的数据查询能力,且是NoSQL数据库,即非关系型数据库。

HBase特点总结:

行存储VS列存储

目前大数据存储有两种方案可供选择:行存储(Row-Based)和列存储(Column-Based)。业界对两种存储方案有很多争持,集中焦点是:谁能够更有效地处理海量数据,且兼顾安全、可靠、完整性。从目前发展情况看,关系数据库已经不适应这种巨大的存储量和计算要求,基本是淘汰出局。在已知的几种大数据处理软件中,Hadoop的HBase采用列存储,MongoDB是文档型的行存储,Lexst是二进制型的行存储。

在数据写入上的对比

在数据读取上的对比

传统行式数据库的特性如下:
①数据是按行存储的。
②没有索引的查询使用大量I/O。比如一般的数据库表都会建立索引,通过索引加快查询效率。
③建立索引和物化视图需要花费大量的时间和资源。
④面对查询需求,数据库必须被大量膨胀才能满足需求。

列式数据库的特性如下:
①数据按列存储,即每一列单独存放。
②数据即索引。
③只访问查询涉及的列,可以大量降低系统I/O。
④每一列由一个线程来处理,即查询的并发处理性能高。
⑤数据类型一致,数据特征相似,可以高效压缩。比如有增量压缩、前缀压缩算法都是基于列存储的类型定制的,所以可以大幅度提高压缩比,有利于存储和网络输出数据带宽的消耗。

HBase基本概念


1)Row Key
hbase本质上也是一种Key-Value存储系统。Key相当于RowKey,Value相当于列族数据的集合。
Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。
存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)

2)列族(列簇)
hbase表中的每个列,都归属与某个列族。列族是表的schema的一部分(而列不是),列族必须在使用表之前定义。列名都以列族作为前缀。例如courses:history , courses:math 都属于 courses 这个列族。
访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能 帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因 为隐私的原因不能浏览所有数据)。

3)Cell与时间戳
由{row key, column( = + < label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。
每个 cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。

HBase完全分布式安装

<property>
	<name>hbase.rootdir</name>
	<value>hdfs://hadoop01:9000/hbase</value>
</property> 
<property>
	<name>hbase.cluster.distributed</name>
	<value>true</value>
</property>
#配置Zookeeper的连接地址与端口号
<property>
	<name>hbase.zookeeper.quorum</name>
	<value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>
  1. 配置region服务器,修改conf/regionservers文件,每个主机名独占一行,hbase启动或关闭时会按照该配置顺序启动或关闭主机中的hbase
    hadoop01
    hadoop02
    hadoop03
  2. 将01节点配置好的hbase远程拷贝到02和03节点
  3. 在01,02,03的zookeeper目录下的bin下启动服务:sh zkServer.sh start
  4. 在01节点上启动hadoop:start-all.sh
  5. 在01节点上hbase的bin目录下启动hbase:sh start-hbase.sh
  6. 通过jps指令查看各节点进程是否启动成功
  7. 通过浏览器访问http://ip地址:60010来访问web界面,通过web见面管理hbase
  8. 关闭Hmaster需进入到hbase安装目录下的bin目录执行:stop-hbase.sh
  9. 关闭regionserver,进入到hbase安装目录下的bin目录执行:sh hbase-daemon.sh stop regionserver

HBase基础指令

补充说明:
hbase命令行下不能使用删除,
可以使用 ctrl+删除键 来进行删除
修改xshell配置:
文件->属性->终端->键盘
->delete键序列[VT220Del]
->backspace键序列[ASCII127]

HBase API

创建表

	public void createTbale() throws Exception{
		//获取HBase的环境参数对象
		Configuration conf = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		HBaseAdmin admin = new HBaseAdmin(conf);
		
		HTableDescriptor table =new HTableDescriptor(TableName.valueOf("tb2"));
		HColumnDescriptor cf1 = new HColumnDescriptor("cf1".getBytes());
		HColumnDescriptor cf2 = new HColumnDescriptor("cf2".getBytes());
		//将指定的列族和表绑定
		table.addFamily(cf1);
		table.addFamily(cf2);
		
		//执行创建表
		admin.createTable(table);
		admin.close();
	}

插入数据

public void insertData() throws Exception{
		Configuration conf  = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		
		//创建表对象,并指定操作的表名
		HTable table = new HTable(conf,"tb1");
		//创建行对象,并指定行键
		Put put = new Put("row1".getBytes());
		//--1参:列族名,2参:列名,3参:列值
		put.add("cf1".getBytes(),"name".getBytes(),"tom".getBytes());
		put.add("cf1".getBytes(),"age".getBytes(),"30".getBytes());
		put.add("cf2".getBytes(),"city".getBytes(),"cd".getBytes());
		
		//执行插入
		table.put(put);
		table.close();
	}

批量插入数据

将数据存入LIist中,提高性能

public void batchInsert() throws Exception{
		Configuration conf = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		
		HTable table = new HTable(conf,"tb1");
		List<Put> puts=new ArrayList<>();
		for(int i=0;i<100;i++){
			Put put = new Put(("row"+i).getBytes());
			put.add("cf1".getBytes(),"num".getBytes(),(i+"").getBytes());
			puts.add(put);
		}
		//执行批量插入
		table.put(puts);
		table.close();
	}

获取数据

public void getData() throws Exception{
		Configuration conf = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		
		HTable table = new HTable(conf,"tb1");
		//通过行键去指定读取的数据
		Get get = new Get("row1".getBytes());
		//执行查询,把结果集封装到result中
		Result result = table.get(get);
		//通过列族名和列名获取值
		byte[] name = result.getValue("cf1".getBytes(),"name".getBytes());
		byte[] age = result.getValue("cf1".getBytes(),"age".getBytes());
		byte[] city = result.getValue("cf2".getBytes(),"city".getBytes());
		System.out.println(new String(name)+":"+new String(age)+":"+new String(city));
		table.close();
	}

整表扫描

public void scanTable() throws IOException{
		Configuration conf = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		
		HTable table = new HTable(conf,"tb1");
		//创建扫描对象,可以通过对象扫描整表数据
		Scan scan = new Scan();
		
		//指定扫描的起始行列
		scan.setStartRow("row10".getBytes());
		scan.setStopRow("row19".getBytes());
		
		//将整表数据封装到结果集(包含多行数据)
		ResultScanner rs = table.getScanner(scan);
		//获取行数据的迭代器
		Iterator<Result> it = rs.iterator();
		while(it.hasNext()){
			//每迭代一次就获取一行数据
			Result result = it.next();
			byte[] num = result.getValue("cf1".getBytes(), "num".getBytes());
			System.out.println(new String(num));
		}
		
		table.close();
	}

删除数据

public void delete() throws IOException{
		Configuration conf = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		
		HTable table = new HTable(conf,"tb1");
		
		//根据指定行键删除
		Delete delete = new Delete("row0".getBytes());
		table.delete(delete);
		//还可以通过List<Delete> 来实现批量删除
		table.close();
	}

删除表

需要先将表置为disable

Configuration conf = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		
		HBaseAdmin admin = new HBaseAdmin(conf);
		//先禁用
		admin.disableTable("tb2");
		//再删除
		admin.deleteTable("tb2");
		
		admin.close();

过滤器

public void rowFilter() throws IOException{
		Configuration conf = HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
		
		HTable table = new HTable(conf,"tb1");
		Scan scan = new Scan();
		//指定扫描范围
		//scan.setStartRow("row1".getBytes());
		//scan.setStopRow("row30".getBytes());
		
		//正则过滤器
		//Filter filter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("^.*3.*$"));
		
		//行键比较过滤器
		//Filter filter = new RowFilter(CompareOp.LESS_OR_EQUAL,new BinaryComparator("row30".getBytes()));
		
		//行键前缀过滤器
		//Filter filter = new PrefixFilter("row3".getBytes());
		
		//列值过滤器
		Filter filter = new SingleColumnValueFilter("cf1".getBytes(), "name".getBytes(), CompareOp.EQUAL, "tom".getBytes());
		
		//绑定过滤器
		scan.setFilter(filter);
		//执行扫描,会结合过滤器查询
		ResultScanner rs = table.getScanner(scan);
		Iterator<Result> it = rs.iterator();
		while(it.hasNext()){
			Result result = it.next();
			byte[] num = result.getValue("cf1".getBytes(),"num".getBytes());
			System.out.println(new String(num));
		}
		table.close();
	}

HBase物理存储原理

HBase里的一个Table 在行的方向上分割为多个HRegion。即HBase中一个表的数据会被划分成很多的HRegion,HRegion可以动态扩展并且HBase保证HRegion的负载均衡。HRegion实际上是行键排序后的按规则分割的连续的存储空间。每个HRegion的大小可以是1~20GB。这个大小由hbase.hregion.max.filesize指定,默认为10GB。

HRegion由一个或者多个HStore组成,每个Hstore保存一个columns family(列族)。
每个HStore又由一个memStore(写缓存)和0至多个StoreFile组成。StoreFile以HFile格式保存在HDFS上。如图:

总结:HRegion是分布式的存储最小单位,StoreFile(Hfile)是存储最小单位。

HBase系统

HBase采用Master/Slave架构搭建集群,它隶属于Hadoop生态系统,由以下类型节点组成:

HMaster节点

HMaster没有单点故障问题,可以启动多个HMaster,一般2个,通过ZooKeeper的Master Election机制保证同时只有一个HMaster处于Active状态,其他的HMaster则处于热备份状态。

HRegionServer


功能:

WAL

WAL即Write Ahead Log,在早期版本中称为HLog,它是HDFS上的一个文件,如其名字所表示的,所有写操作都会先保证将写操作写入这个Log文件后,才会真正更新MemStore,最后写入HFile中。采用这种模式,可以保证HRegionServer宕机后,我们依然可以从该Log文件中恢复数据,Replay所有的操作,而不至于数据丢失。

BlockCache

BlockCache是一个读缓存,即“引用局部性”原理(也应用于CPU,分空间局部性和时间局部性,空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能。这样设计的目的是为了提高读缓存的命中率。

MemStore

.MemStore是一个写缓存(In Memory Sorted Buffer),所有数据的写在完成WAL日志写后,会写入MemStore中,由MemStore根据一定的算法(LSM-TREE算法 日志合并树算法,这个算法的作用是将数据顺序写磁盘,而不是随机写,减少磁头调度时间,从而提高写入性能) 将数据Flush到底层的HDFS文件中(HFile),通常每个HRegion中的每个 Column Family有一个自己的MemStore。

有以下三种情况可以触发MemStore的Flush动作:

HFile格式

v1格式:

V1的HFile由多个Data Block、Meta Block、FileInfo、Data Index、Meta Index、Trailer组成,其中Data Block是HBase的最小存储单元,在前文中提到的BlockCache就是基于Data Block的缓存的。一个Data Block由一个魔数和一系列的KeyValue(Cell)组成,魔数是一个随机的数字,用于表示这是一个Data Block类型,以快速检测这个Data Block的格式,防止数据的破坏。Data Block的大小可以在创建Column Family时设置(HColumnDescriptor.setBlockSize()),默认值是64KB,大号的DadaBlock有利于顺序Scan,小号DataBlock利于随机查询

v2格式是一个多层的类B+树索引,采用这种设计,可以实现查找不需要读取整个文件:

Data Block中的Cell都是升序排列,每个block都有它自己的Leaf-Index,每个Block的最后一个Key被放入Intermediate-Index中,Root-Index指向Intermediate-Index。在HFile的末尾还有Bloom Filter(布隆过滤)用于快速定位那么没有在某个Data Block中的Row;TimeRange信息用于给那些使用时间查询的参考。在HFile打开时,这些索引信息都被加载并保存在内存中,以增加以后的读取性能。

HFile的Compaction机制

MemStore每次Flush会创建新的HFile,而过多的HFile会引起读的性能问题,那么如何解决这个问题呢?HBase采用Compaction机制来解决这个问题。在HBase中Compaction分为两种:Minor Compaction和Major Compaction

HBase默认使用的是Minor compaction
API实现:
//–minor compact
admin.compact(“tab2”.getBytes());
//–major compact
admin.majorCompact(“tab2”.getBytes());
指令实现:
compact(‘tab2’)
major_compact(‘tab2’)

HBase的第一次读写

客户端在第一次访问用户Table的流程就变成了:

HBase写流程

当客户端发起一个Put请求时,首先它从hbase:meta表中查出该Put数据最终需要去的HRegionServer。然后客户端将Put请求发送给相应的HRegionServer,在HRegionServer中它首先会将该Put操作写入WAL日志文件中(Flush到磁盘中)。
写完WAL日志文件后,然后会将数据写到Memstore,在Memstore按Rowkey排序,以及用LSM-TREE对数据做合并处理。HRegionServer根据Put中的TableName和RowKey找到对应的HRegion,并根据Column Family找到对应的HStore,并将Put写入到该HStore的MemStore中。此时写成功,并返回通知客户端。

HBase读流程

HBase中扫瞄的顺序依次是:BlockCache、MemStore、StoreFile(HFile)(这个扫描顺序的目的也是为了减少磁盘的I/O次数)。其中StoreFile的扫瞄先会使用Bloom Filter(布隆过滤算法)过滤那些不可能符合条件的DataBlock,然后使用Block Index快速定位Cell,并将其加载到BlockCache中,然后从BlockCache中读取。

HBase调优

硬件和操作系统调优
a. 配置内存
在互联网领域,服务器内存方面的主流配置已经是64GB,所以一定要根据实际的需求和预算配备服务器内存。如果资源很紧张,推荐内存最小在32GB,如果再小会严重影响HBase集群性能。
b. 配置CPU
c.配置硬盘
如果是机械盘,看转速,14000转,一般的是7000转。
可以考虑用SSD固态硬盘,底层是通过电阻器原件构架的,速度接近于内存

垃圾回收器的选择
对于运行HBase相关进程JVM的垃圾回收器,不仅仅关注吞吐量,还关注停顿时间,而且两者之间停顿时间更为重要,因为HBase设计的初衷就是解决大规模数据集下实时访问的问题。那么按照首位是停顿时间短,从这个方面CMS和G1有着非常大的优势。最终选用的垃圾收集器搭配组合是CMS+ParNew(新生代)
配置方式:需要添加到hbase-env.sh文件中
export HBASE_OPTS="-XX:+UseConcMarkSweepGC" -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSCompactAtFullCollection

JVM堆大小设置
堆内存大小参数hbase-env.sh文件中设置,设置的代码如下:export HBASE_HEAPSIZE=16384
在上面代码中指定堆内存大小是16284,单位是MB,即16GB。当然,这个值需要根据节点实际的物理内存来决定。一般不超过实际物理内存的1/2。

调节数据块(data block)的大小
如果mytable表在实际业务中,随机查找业务多,就调小。
如果范围查询(顺序扫描)业务多,就调大。
可以在表实例化时设置数据块大小,代码如下:
hbase(main):002:0> create ‘mytable’,{NAME => ‘colfam1’, BLOCKSIZE => ‘65536’}

适当时机关闭数据块缓存
关闭缓存的原因在于:如果只是执行很多顺序化扫描,会多次使用缓存,并且可能会滥用缓存,从而把应该放进缓存获得性能提升的数据给排挤出去。

数据块缓存默认是打开的。可以在新建表或更改表时关闭数据块缓存属性:hbase(main):002:0> create ‘mytable’, {NAME => ‘colfam1’, BLOCKCACHE => ‘false’}
如果预见到mytable的范围查询(顺序查找)业务较多,这种场景可以将mytable的读缓存机制关掉。

开启布隆过滤器
如果要查找一个很短的行,只在整个数据块的起始行键上建立索引是无法给出更细粒度的索引信息的。布隆过滤器(Bloom Filter)允许对存储在每个数据块的数据做一个反向测验。使用布隆过滤器也不是没有代价,相反,存储这个额外的索引层次占用额外的空间。行级布隆过滤器比列标识符级布隆过滤器占用空间要少。

可以在列族上打开布隆过滤器,代码如下:hbase(main):007:0> create ‘mytable’, {NAME => ‘colfam1’, BLOOMFILTER => ‘ROWCOL’}
布隆过滤器参数的默认值是NONE。另外,还有两个值:ROW表示行级布隆过滤器;ROWCOL表示列标识符级布隆过滤器。行级布隆过滤器在数据块中检查特定行键是否不存在,列标识符级布隆过滤器检查行和列标识符联合体是否不存在。ROWCOL布隆过滤器的空间开销高于ROW布隆过滤器。

开启数据压缩
HFile可以被压缩并存放在HDFS上,这有助于节省硬盘I/O,此外,可以节省带宽。HBase可以使用多种压缩编码,包括LZO、SNAPPY和GZIP,LZO和SNAPPY是其中最流行的两种。

当建表时可以在列族上打开压缩,代码如下:hbase(main):002:0> create ‘mytable’, {NAME => ‘colfam1’, COMPRESSION => ‘SNAPPY’}
注意,数据只在硬盘上是压缩的,在内存中(MemStore或BlockCache)或在网络传输时是没有压缩的。

设置Scan缓存
HBase的Scan查询中可以设置缓存,定义一次交互从服务器端传输到客户端的行数,设置方法是使用Scan类中setCaching()方法,这样能有效地减少服务器端和客户端的交互,更好地提升扫描查询的性能。

显式地指定列
当使用Scan或Get来处理大量的行时,最好确定一下所需要的列。能够很大程度上减少网络I/O的花费,否则会造成很大的资源浪费,且有效地减少网络传输量。使用Scan类中指定列的addColumn()方法。

关闭ResultScanner
ResultScanner类用于存储服务端扫描的最终结果,可以通过遍历该类获取查询结果。但是,如果不关闭该类,可能会出现服务端在一段时间内一直保存连接,资源无法释放,从而导致服务器端某些资源的不可用
代码的最后一行rsScanner.close()就是执行关闭ResultScanner。

使用批量读/写

关闭写WAL日志
在默认情况下,为了保证系统的高可用性,写WAL日志是开启状态。如果应用可以容忍一定的数据丢失的风险,可以尝试在更新数据时,关闭写WAL。关闭写WAL操作通过Put类中的writeToWAL()设置。

设置AutoFlush
HTable有一个属性是AutoFlush,该属性用于支持客户端的批量更新。该属性默认值是true,即客户端每收到一条数据,立刻发送到服务端。如果将该属性设置为false,当客户端提交Put请求时,将该请求在客户端缓存,直到数据达到某个阈值的容量时(该容量由参数hbase.client.write.buffer决定)或执行hbase.flushcommits()时,才向RegionServer提交请求。
这种方式避免了每次跟服务端交互,采用批量提交的方式,所以更高效。

但是,如果还没有达到该缓存而客户端崩溃,该部分数据将由于未发送到RegionServer而丢失。这对于有些零容忍的在线服务是不可接受的。所以,设置该参数的时候要慎重。
table.setAutoFlush(false);
table.setWriteBufferSize(1210241024);
table.flushCommits();

预创建Region
在HBase中创建表时,该表开始只有一个Region,插入该表的所有数据会保存在该Region中。随着数据量不断增加,当该Region大小达到一定阈值时,就会发生分裂(Region Splitting)操作。并且在这个表创建后相当长的一段时间内,针对该表的所有写操作总是集中在某一台或者少数几台机器上,这不仅仅造成局部磁盘和网络资源紧张,同时也是对整个集群资源的浪费。这个问题在初始化表,即批量导入原始数据的时候,特别明显。为了解决这个问题,可以使用预创建Region的方法。

Hbase内部提供了RegionSplitter工具,使用命令如下:
${HBASE_HOME}/bin/hbase org.apache.hadoop.hbase.util.RegionSplitter test2 HexStringSplit -c 10 -f cf1
其中,test2是表名,HexStringSplit表示划分的算法,参数-c 10表示预创建10个Region,-f cf1表示创建一个名字为cf1的列族。

www.htsjk.Com true http://www.htsjk.com/hbase/37245.html NewsArticle HBase, HBase HBASE简介 行存储VS列存储 HBase基本概念 HBase完全分布式安装 HBase基础指令 HBase API 创建表 插入数据 批量插入数据 获取数据 整表扫描 删除数据 删除表 过滤器 HBase物理存储原...
相关文章
    暂无相关文章
评论暂时关闭