Hbase的基础知识
一、关系型数据库的查询瓶颈
- 高并发的更新操作
- 多表关联后的复杂查询
这些都是关系型数据库的查询瓶颈,所以要用到NoSQL非关系型数据
二、NoSQL
- 现在NoSQL被普遍理解理解为“Not Only SQL”,意为不仅仅是SQL
- NoSQL和传统的关系型数据库在很多场景下是相辅相成的,谁也不能完全替代谁
三、HBase定义
- HBase是一个分布式、可扩展、支持海量数据存储的NoSQL数据库
- HBase是面向列存储,构建于hadoop上,提供对10亿级别表数据的快速随机实时读写!
四、HBase的特点
- 海量存储
- 列式存储
- 极易扩展
- 高并发
- 稀疏
五、HBase优点
- HDFS有高容错,高扩展的特点,而Hbase基于HDFS实现数据的存储,因此Hbase拥有与生俱来的超强的扩展性和吞吐量。
- HBase采用的是Key/Value的存储方式,这意味着,即便面临海量数据的增长,也几乎不会导致查询性能下降
- HBase是一个列式数据库,相对于于传统的行式数据库而言。当你的单张表字段很多的时候,可以将相同的列(以regin为单位)存在到不同的服务实例上,分散负载压力。
六、HBase缺点
- 架构设计复杂,且使用HDFS作为分布式存储,因此只是存储少量数据,它也不会很快。在大数据量时,它慢的不会很明显
- Hbase不支持表的关联操作,因此数据分析是HBase的弱项。常见的 group by或order by只能通过编写MapReduce来实现
- Hbase部分支持了ACID
七、适用场景
- 适合场景:单表超千万,上亿,且高并发!
- 不适合场景:主要需求是数据分析,比如做报表。数据量规模不大,对实时性要求高!
八、数据模型
- Name Space:名称空间,类似database
- Table:类似关系型数据库的table,不同的是,HBase定义表时只需要声明列族即可
- Row:行,HBase表中的每行数据都由一个RowKey和多个Column(列)组成
- RowKey:Rowkey由用户指定的一串不重复的字符串定义,是一行的唯一标识
- Column Family:列族,官方建议一张表的列族定义的越少越好,列族太多会极大程度地降低数据库性能
- Column Qualifier:列修饰符,具体的列,因为HBase中的列全部都是灵活的,可以随便定义的
- TimeStamp:时间戳,用于标识数据的不同版本
- Cell:单元格,一个列中可以存储多个版本的数据。而每个版本就称为一个单元格
- Region:Region由一个表的若干行组成!在Region中行的排序按照行键(rowkey)字典排序。
九、HBase架构
- HDFS:为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用的支持
- zookeeper:存储RegionServer元数据信息的元数据表
- Master:
- 对表操作:create、delete、alter
- 对RegionServer操作:分配每个Region到每个RegionServer,监控每个RegionServer的状态
- RegionServer:
- 对数据操作:get、put、delete
- 对Region操作:切分Region、合并Region
- Region:一张表的多行组成
- store:一个列族
- WAL:预写日志,防止RegionServer出现故障,导致Mem Store中数据丢失
- Mem Store:写缓存,k-v在Mem Store中进行排序,达到阈值后会flush到Store File,每次flush会生成一个新的Store File
- Store File:以k-v形式有序的存储在HDFS(HFile)
- Block Cache:读缓存,每次新查询后结果会存储之中
- 它们之间关系:
- 一个Master,多个RegionServer
- 一个RegionServer里面有多个Region、一个WAL、一个Block Cache
- 一个Region里面有多个Store,
- 一个Store里面有一个Mem Store和多个Store File
- Store File和WAL都存储在HDFS上
十、HBase写流程(重要)
- 客户端向zookeeper申请存储Hbase元数据信息的meta表
- zookeeper返回存储meta表的RegionServer的地址
- 客户端申请对应RegionServer获取meta表,获取对应rowkey位置的RegionServer
- 客户端再向对应RegionServer申请写入数据的信息
- 对应的RegionServer接收消息,然后执行写操作
- 先将数据顺序追加到WAL
- 再写入Mem Store,会在Men Store进行排序
- 再向客户端发送ack消息
- 等到Men Store达到阈值后,把数据flush到store file中
十一、HBase读流程(重要)
- 客户端向zookeeper申请存储Hbase元数据信息的meta表
- zookeeper返回存储meta表的RegionServer的地址
- 客户端申请对应RegionServer获取meta表,获取对应rowkey位置的RegionServer的Region
- 客户端再向对应RegionServer发送读取对应数据的信息
- 客户端分别在Block Cache、Men Store、Store File中依次寻找,直到查询到数据,将查询到的数据进行合并
- 然后把查询结果存储到Block Cache
- 最后把最终结果返回给客户端
十二、MemStore Flush
- Mem Store存在的意义是在写入HDFS前,将其中的数据整理有序。
- Mem Store刷写时机:(了解)
- 当某个memstore的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写。当memstore的大小达到了hbase.hregion.memstore.flush.size(默认值128M)*hbase.hregion.memstore.block.multiplier(默认值4)时,会阻止继续往该memstore写数据。
- 当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写数据。 - 到达自动刷写的时间,也会触发memstore flush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)。
- 当WAL文件的数量超过hbase.regionserver.max.logs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.max.log以下(该属性名已经废弃,现无需手动设置,最大值为32)。
十三、StoreFile Compaction
- HBase每间隔一段时间都会进行一次合并(Compaction),合并的对象为HFile文件。合并分为两种minor compaction和major compaction
- minor compaction:将临近的若干个较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据
- major compaction:将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉过期和删除的数据
十四、Region Split
- 默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server
- Region Split时机:
- 0.94版本之前:当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize,该Region就会进行拆分
- 0.94版本之后:当1个region中的某个Store下所有StoreFile的总大小超过Min(R^3*hbase.hregion.memstore.flush.size,hbase.hregion.max.filesize)就会拆分,其中R为当前RegionServer中属于该table的region个数
十五、HBase Shell操作
- 进入shell命令行界面
hbase shell
- 查看集群状态
status
- 查看版本
version
- 查看操作用户及组信息
whoami
- 查看表操作信息
table_help
- 查看帮助信息
help
- 查看具体命令的帮助**(注意引号是必须的!)**
help 'get'
- list:列出表,list后可以使用*等通配符来进行表的过滤!
- create:创建表
create 'student','info'
- desc:查看表信息(下面两种都行)
describe 'person'
desc 'person'
- disable:停用表。一般在删除表前,必须停用表。在对表中的列族进行修改时,也需要停用表。
disable 'person' //停用表
disable_all ‘正则表达式’ //可以使用正则来匹配表名
is_disabled '表名' //可以用来判断表是否被停用
- enable:启用表,和disable相反
- exists:判断表是否存在
- count:返回表有多少条数据
- drop:删除表,先disable表,再删除
- truncate:清除表中所有数据
- get_split:获取表所对应的Region个数
- alter:修改表的属性,通常是修改某个列族的属性
alter 'myns:t1',{NAME => 'info',VERSIONS => '5'}
alter 'ns1:t1', 'delete' => 'f1'
- 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 => "过滤器"}: 使用过滤器扫描
- put:插入新的数据
put可以新增记录还可以为记录设置属性。
put '表名', '行键', '列名', '值'
put '表名', '行键', '列名', '值',时间戳
put '表名', '行键', '列名', '值', { '属性名' => '属性值'}
put '表名', '行键', '列名', '值',时间戳, { '属性名' =>'属性值'}
例如:put 'student','1001','info:name','Nick'
- get:获取一条数据,支持scan所支持的大部分属性,如COLUMNS,TIMERANGE,VERSIONS,FILTER
get 'student','1001'
get 'student','1001','info:name'
- delete:删除数据
删除某rowkey的全部数据:
deleteall 'student','1001'
删除某rowkey的某一列数据:
delete 'student','1002','info:sex'
十六、HBase API
- 添加依赖
<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>
- 获取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");
}
- 判断表是否存在
public static boolean isTableExist(String tableName) throws MasterNotRunningException,
ZooKeeperConnectionException, IOException{
//在HBase中管理、访问表需要先创建HBaseAdmin对象
HBaseAdmin admin = new HBaseAdmin(conf);
return admin.tableExists(tableName);
}
- 创建表
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 + "创建成功!");
}
}
- 删除表
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 + "不存在!");
}
}
- 向表中插入数据
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("插入数据成功");
}
- 删除多行数据
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();
}
- 获取所有数据
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)));
}
}
}
- 获取某一行数据
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());
}
}
- 获取某一行指定“列族:列”的数据
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)));
}
}