Hbase的基础知识

一、关系型数据库的查询瓶颈

  1. 高并发的更新操作
  2. 多表关联后的复杂查询
    这些都是关系型数据库的查询瓶颈,所以要用到NoSQL非关系型数据

二、NoSQL

  1. 现在NoSQL被普遍理解理解为“Not Only SQL”,意为不仅仅是SQL
  2. NoSQL和传统的关系型数据库在很多场景下是相辅相成的,谁也不能完全替代谁

三、HBase定义

  1. HBase是一个分布式、可扩展、支持海量数据存储的NoSQL数据库
  2. HBase是面向列存储,构建于hadoop上,提供对10亿级别表数据的快速随机实时读写!

四、HBase的特点

  1. 海量存储
  2. 列式存储
  3. 极易扩展
  4. 高并发
  5. 稀疏

五、HBase优点

  1. HDFS有高容错,高扩展的特点,而Hbase基于HDFS实现数据的存储,因此Hbase拥有与生俱来的超强的扩展性和吞吐量
  2. HBase采用的是Key/Value的存储方式,这意味着,即便面临海量数据的增长,也几乎不会导致查询性能下降
  3. HBase是一个列式数据库,相对于于传统的行式数据库而言。当你的单张表字段很多的时候,可以将相同的列(以regin为单位)存在到不同的服务实例上,分散负载压力。

六、HBase缺点

  1. 架构设计复杂,且使用HDFS作为分布式存储,因此只是存储少量数据,它也不会很快。在大数据量时,它慢的不会很明显
  2. Hbase不支持表的关联操作,因此数据分析是HBase的弱项。常见的 group by或order by只能通过编写MapReduce来实现
  3. Hbase部分支持了ACID

七、适用场景

  1. 适合场景:单表超千万,上亿,且高并发!
  2. 不适合场景:主要需求是数据分析,比如做报表。数据量规模不大,对实时性要求高!

八、数据模型

  1. Name Space:名称空间,类似database
  2. Table:类似关系型数据库的table,不同的是,HBase定义表时只需要声明列族即可
  3. Row:行,HBase表中的每行数据都由一个RowKey和多个Column(列)组成
  4. RowKey:Rowkey由用户指定的一串不重复的字符串定义,是一行的唯一标识
  5. Column Family:列族,官方建议一张表的列族定义的越少越好,列族太多会极大程度地降低数据库性能
  6. Column Qualifier:列修饰符,具体的列,因为HBase中的列全部都是灵活的,可以随便定义的
  7. TimeStamp:时间戳,用于标识数据的不同版本
  8. Cell:单元格,一个列中可以存储多个版本的数据。而每个版本就称为一个单元格
  9. Region:Region由一个表的若干行组成!在Region中行的排序按照行键(rowkey)字典排序。

九、HBase架构

  1. HDFS:为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用的支持
  2. zookeeper:存储RegionServer元数据信息的元数据表
  3. Master:
  1. 对表操作:create、delete、alter
  2. 对RegionServer操作:分配每个Region到每个RegionServer,监控每个RegionServer的状态
  1. RegionServer:
  1. 对数据操作:get、put、delete
  2. 对Region操作:切分Region、合并Region
  1. Region:一张表的多行组成
  2. store:一个列族
  3. WAL:预写日志,防止RegionServer出现故障,导致Mem Store中数据丢失
  4. Mem Store:写缓存,k-v在Mem Store中进行排序,达到阈值后会flush到Store File,每次flush会生成一个新的Store File
  5. Store File:以k-v形式有序的存储在HDFS(HFile)
  6. Block Cache:读缓存,每次新查询后结果会存储之中
  7. 它们之间关系:
  1. 一个Master,多个RegionServer
  2. 一个RegionServer里面有多个Region、一个WAL、一个Block Cache
  3. 一个Region里面有多个Store,
  4. 一个Store里面有一个Mem Store和多个Store File
  5. Store File和WAL都存储在HDFS上

十、HBase写流程(重要)

  1. 客户端向zookeeper申请存储Hbase元数据信息的meta表
  2. zookeeper返回存储meta表的RegionServer的地址
  3. 客户端申请对应RegionServer获取meta表,获取对应rowkey位置的RegionServer
  4. 客户端再向对应RegionServer申请写入数据的信息
  5. 对应的RegionServer接收消息,然后执行写操作
  6. 先将数据顺序追加到WAL
  7. 再写入Mem Store,会在Men Store进行排序
  8. 再向客户端发送ack消息
  9. 等到Men Store达到阈值后,把数据flush到store file中

十一、HBase读流程(重要)

  1. 客户端向zookeeper申请存储Hbase元数据信息的meta表
  2. zookeeper返回存储meta表的RegionServer的地址
  3. 客户端申请对应RegionServer获取meta表,获取对应rowkey位置的RegionServer的Region
  4. 客户端再向对应RegionServer发送读取对应数据的信息
  5. 客户端分别在Block Cache、Men Store、Store File中依次寻找,直到查询到数据,将查询到的数据进行合并
  6. 然后把查询结果存储到Block Cache
  7. 最后把最终结果返回给客户端

十二、MemStore Flush

  1. Mem Store存在的意义是在写入HDFS前,将其中的数据整理有序。
  2. Mem Store刷写时机:(了解)
  1. 当某个memstore的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写。当memstore的大小达到了hbase.hregion.memstore.flush.size(默认值128M)*hbase.hregion.memstore.block.multiplier(默认值4)时,会阻止继续往该memstore写数据。
  2. 当region server中memstore的总大小达到java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4)
    *hbase.regionserver.global.memstore.size.lower.limit(默认值0.95),region会按照其所有memstore的大小顺序(由大到小)依次进行刷写。直到region server中所有memstore的总大小减小到上述值以下。
    当region server中memstore的总大小达到java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4)时,会阻止继续往所有的memstore写数据。
  3. 到达自动刷写的时间,也会触发memstore flush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)。
  4. 当WAL文件的数量超过hbase.regionserver.max.logs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.max.log以下(该属性名已经废弃,现无需手动设置,最大值为32)。

十三、StoreFile Compaction

  1. HBase每间隔一段时间都会进行一次合并(Compaction),合并的对象为HFile文件。合并分为两种minor compactionmajor compaction
  2. minor compaction:将临近的若干个较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据
  3. major compaction:将一个Store下的所有的HFile合并成一个大HFile,并且清理掉过期和删除的数据

十四、Region Split

  1. 默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server
  2. Region Split时机:
  1. 0.94版本之前:当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize,该Region就会进行拆分
  2. 0.94版本之后:当1个region中的某个Store下所有StoreFile的总大小超过Min(R^3*hbase.hregion.memstore.flush.size,hbase.hregion.max.filesize)就会拆分,其中R为当前RegionServer中属于该table的region个数

十五、HBase Shell操作

  1. 进入shell命令行界面
hbase shell
  1. 查看集群状态
status
  1. 查看版本
version
  1. 查看操作用户及组信息
whoami
  1. 查看表操作信息
table_help
  1. 查看帮助信息
help
  1. 查看具体命令的帮助**(注意引号是必须的!)**
help 'get'
  1. list:列出表,list后可以使用*等通配符来进行表的过滤!
  2. create:创建表
create 'student','info'
  1. desc:查看表信息(下面两种都行)
describe 'person'
desc 'person'
  1. disable:停用表。一般在删除表前,必须停用表。在对表中的列族进行修改时,也需要停用表。
disable 'person'	//停用表
disable_all ‘正则表达式’ 	//可以使用正则来匹配表名
is_disabled '表名'	//可以用来判断表是否被停用
  1. enable:启用表,和disable相反
  2. exists:判断表是否存在
  3. count:返回表有多少条数据
  4. drop:删除表,先disable表,再删除
  5. truncate:清除表中所有数据
  6. get_split:获取表所对应的Region个数
  7. alter:修改表的属性,通常是修改某个列族的属性
alter 'myns:t1',{NAME => 'info',VERSIONS => '5'}
alter 'ns1:t1', 'delete' => 'f1'
  1. scan:全表扫描,全表查询,后可接查询条件
scan命令可以按照rowkey的字典顺序来遍历指定的表的数据。
scan ‘表名’:默认当前表的所有列族。
scan ‘表名’,{COLUMNS=> [‘列族:列名’],…} : 遍历表的指定列
scan '表名', { STARTROW => '起始行键', ENDROW => '结束行键' }:指定rowkey范围。如果不指定,则会从表的开头一直显示到表的结尾。区间为左闭右开。
scan '表名', { LIMIT => 行数量}: 指定返回的行的数量
scan '表名', {VERSIONS => 版本数}:返回cell的多个版本
scan '表名', { TIMERANGE => [最小时间戳, 最大时间戳]}:指定时间戳范围
		注意:此区间是一个左闭右开的区间,因此返回的结果包含最小时间戳的记录,但是不包含最大时间戳记录
scan '表名', { RAW => true, VERSIONS => 版本数}
	显示原始单元格记录,在Hbase中,被删掉的记录在HBase被删除掉的记录并不会立即从磁盘上清除,而是先被打上墓碑标记,然后等待下次major compaction的时候再被删除掉。注意RAW参数必须和VERSIONS一起使用,但是不能和COLUMNS参数一起使用。
scan '表名', { FILTER => "过滤器"} and|or { FILTER => "过滤器"}: 使用过滤器扫描
  1. put:插入新的数据
put可以新增记录还可以为记录设置属性。
put '表名', '行键', '列名', '值'
put '表名', '行键', '列名', '值',时间戳
put '表名', '行键', '列名', '值', { '属性名' => '属性值'}
put '表名', '行键', '列名', '值',时间戳, { '属性名' =>'属性值'}
例如:put 'student','1001','info:name','Nick'
  1. get:获取一条数据,支持scan所支持的大部分属性,如COLUMNS,TIMERANGE,VERSIONS,FILTER
get 'student','1001'
get 'student','1001','info:name'
  1. delete:删除数据
删除某rowkey的全部数据:
	deleteall 'student','1001'
删除某rowkey的某一列数据:
	delete 'student','1002','info:sex'

十六、HBase API

  1. 添加依赖
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-server</artifactId>
    <version>1.3.1</version>
</dependency>

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.3.1</version>
</dependency>

<dependency>
	<groupId>jdk.tools</groupId>
	<artifactId>jdk.tools</artifactId>
	<version>1.8</version>
	<scope>system</scope>
	<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
  1. 获取Configuration对象
public static Configuration conf;
static{
	//使用HBaseConfiguration的单例方法实例化
	conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "192.168.1.101");
conf.set("hbase.zookeeper.property.clientPort", "2181");
}
  1. 判断表是否存在
public static boolean isTableExist(String tableName) throws MasterNotRunningException,
 ZooKeeperConnectionException, IOException{
	//在HBase中管理、访问表需要先创建HBaseAdmin对象
	HBaseAdmin admin = new HBaseAdmin(conf);
	return admin.tableExists(tableName);
}
  1. 创建表
public static void createTable(String tableName, String... columnFamily) throws
 MasterNotRunningException, ZooKeeperConnectionException, IOException{
	HBaseAdmin admin = new HBaseAdmin(conf);
	//判断表是否存在
	if(isTableExist(tableName)){
		System.out.println("表" + tableName + "已存在");
		//System.exit(0);
	}else{
		//创建表属性对象,表名需要转字节
		HTableDescriptor descriptor = new HTableDescriptor(TableName.valueOf(tableName));
		//创建多个列族
		for(String cf : columnFamily){
			descriptor.addFamily(new HColumnDescriptor(cf));
		}
		//根据对表的配置,创建表
		admin.createTable(descriptor);
		System.out.println("表" + tableName + "创建成功!");
	}
}
  1. 删除表
public static void dropTable(String tableName) throws MasterNotRunningException,
 ZooKeeperConnectionException, IOException{
	HBaseAdmin admin = new HBaseAdmin(conf);
	if(isTableExist(tableName)){
		admin.disableTable(tableName);
		admin.deleteTable(tableName);
		System.out.println("表" + tableName + "删除成功!");
	}else{
		System.out.println("表" + tableName + "不存在!");
	}
}
  1. 向表中插入数据
public static void addRowData(String tableName, String rowKey, String columnFamily, String
 column, String value) throws IOException{
	//创建HTable对象
	HTable hTable = new HTable(conf, tableName);
	//向表中插入数据
	Put put = new Put(Bytes.toBytes(rowKey));
	//向Put对象中组装数据
	put.add(Bytes.toBytes(columnFamily), Bytes.toBytes(column), Bytes.toBytes(value));
	hTable.put(put);
	hTable.close();
	System.out.println("插入数据成功");
}
  1. 删除多行数据
public static void deleteMultiRow(String tableName, String... rows) throws IOException{
	HTable hTable = new HTable(conf, tableName);
	List<Delete> deleteList = new ArrayList<Delete>();
	for(String row : rows){
		Delete delete = new Delete(Bytes.toBytes(row));
		deleteList.add(delete);
	}
	hTable.delete(deleteList);
	hTable.close();
}
  1. 获取所有数据
public static void getAllRows(String tableName) throws IOException{
	HTable hTable = new HTable(conf, tableName);
	//得到用于扫描region的对象
	Scan scan = new Scan();
	//使用HTable得到resultcanner实现类的对象
	ResultScanner resultScanner = hTable.getScanner(scan);
	for(Result result : resultScanner){
		Cell[] cells = result.rawCells();
		for(Cell cell : cells){
			//得到rowkey
			System.out.println("行键:" + Bytes.toString(CellUtil.cloneRow(cell)));
			//得到列族
			System.out.println("列族" + Bytes.toString(CellUtil.cloneFamily(cell)));
			System.out.println("列:" + Bytes.toString(CellUtil.cloneQualifier(cell)));
			System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
		}
	}
}
  1. 获取某一行数据
public static void getRow(String tableName, String rowKey) throws IOException{
	HTable table = new HTable(conf, tableName);
	Get get = new Get(Bytes.toBytes(rowKey));
	//get.setMaxVersions();显示所有版本
    //get.setTimeStamp();显示指定时间戳的版本
	Result result = table.get(get);
	for(Cell cell : result.rawCells()){
		System.out.println("行键:" + Bytes.toString(result.getRow()));
		System.out.println("列族" + Bytes.toString(CellUtil.cloneFamily(cell)));
		System.out.println("列:" + Bytes.toString(CellUtil.cloneQualifier(cell)));
		System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
		System.out.println("时间戳:" + cell.getTimestamp());
	}
}
  1. 获取某一行指定“列族:列”的数据
public static void getRowQualifier(String tableName, String rowKey, String family, String
 qualifier) throws IOException{
	HTable table = new HTable(conf, tableName);
	Get get = new Get(Bytes.toBytes(rowKey));
	get.addColumn(Bytes.toBytes(family), Bytes.toBytes(qualifier));
	Result result = table.get(get);
	for(Cell cell : result.rawCells()){
		System.out.println("行键:" + Bytes.toString(result.getRow()));
		System.out.println("列族" + Bytes.toString(CellUtil.cloneFamily(cell)));
		System.out.println("列:" + Bytes.toString(CellUtil.cloneQualifier(cell)));
		System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
	}
}