hbase是基于大表的数据库
=====================================
随机访问和实时读写
hbase和hive的区别:
hbase:低延迟实时性,不支持分析
hive:高延迟,分析工具
awk '{print $1}' //默认以'\t'分割,截串取第一个成员
hbase原理:
========================
hdfs://mycluster/user/hbase/data //hbase数据
namespace //文件夹
Htable //文件夹
Hregion //区域,文件夹
CF //列族, 文件夹
数据 //文件形式存在(row,cf,col,value)
hbase在写入数据时,先写入到memstore中,到达一定的值或重启的时候会实例化为文件
scan 'hbase:meta'
ns1:t1,,1522573626932.02aab65df5b31 column=info:regioninfo, timestamp=1522630951985, value={ENCODED => 02aab65df5b31c1145a8b799ced42c92, NAM
c1145a8b799ced42c92. E => 'ns1:t1,,1522573626932.02aab65df5b31c1145a8b799ced42c92.', STARTKEY => '', ENDKEY => ''}
ns1:t1,,1522573626932.02aab65df5b31 column=info:seqnumDuringOpen, timestamp=1522630951985, value=\x00\x00\x00\x00\x00\x00\x00\x15
c1145a8b799ced42c92.
ns1:t1,,1522573626932.02aab65df5b31 column=info:server, timestamp=1522630951985, value=s102:16020
c1145a8b799ced42c92.
ns1:t1,,1522573626932.02aab65df5b31 column=info:serverstartcode, timestamp=1522630951985, value=1522629569862
c1145a8b799ced42c92.
hbase:meta表存放的是所有表的元数据,即表(区域)和regionserver的映射关系
问题1:hbase:meta表是谁负责管辖?
//每个表(包括元数据表),都分别由一个regionserver负责的
问题2:元数据中的元数据,hbase:meta的元数据存放位置?
//hbase:meta表的数据信息,由zk负责管辖
普通表: 表体由regionserver负责管理,元数据是由hbase:meta表负责管理
hbase:meta表: 表体由regionserver负责管理,元数据是由zk负责管辖
写(put)流程: 1、客户端先联系zk,找到hbase:meta表的regionserver,在此请求数据
2、通过hbase:meta的数据,找到表(区域)的映射关系,通过regionserver定位到指定表(区域)
3、查询信息一旦被触发,会被缓存,以便下次使用
4、用户向区域服务器(RegionServer)发起put请求时,会将请求交给对应的区域(region)实例来处理。
5、决定数据是否需要写到由HLog实现的预写日志(WAL)中。
6、一旦数据被写入到WAL中,数据会被放到MemStore中
7、memstore如果写满,则会被刷写(flush)到磁盘中,生成新的hfile
区域:region管理
======================================
是regionserver的基本管理单位
rowKey是以字节数组进行排序
表切割:
split 'ns1:t1','row500'
区域切割:
split 'ns1:t1,row500,1522634115455.b88d6163f7e2eced2f2297c7fe938b3b.','row750'
创建表,进行预切割:
create 'ns1:t2', 'f1', SPLITS => [ '20', '30', '40']
移动区域:
move '43e14524a984a4bf6a813a4f2a8ac8eb' , 's103,16020,1522634665677'
表切割之后,之前表所在的区域会被切割成两个新的区域,原区域处于离线状态,再次启动hbase时,原区域相关信息会被清空
区域合并
merge_region '28d725266b30bcef636e994f08c9e8d9','976bc7a3b8769184c1a0a5aca1339d99'
紧凑compact:
====================================
解决flush产生的小文件(hfile),将其进行合并
compact 'ns1:t1' //合并小文件,重启后生效
compact_rs 's103,16020,1522634665677' //合并regionserver中所有region
major_compact //合并小文件,即刻生效
regionname //区域名,eg:ns1:t1,row500,1522634115455.b88d6163f7e2eced2f2297c7fe938b3b.
encoded_regionname //编码区域名,eg:b88d6163f7e2eced2f2297c7fe938b3b 注意不加'.'
server_name //regionserver名字, eg:s102,16020,1522634665523
hbase数据写入流程,代码分析
==========================================================
在数据写入的时候,hbase会初始化一个2M的mutator缓冲区
通过Arrays.asList(m)将Mutation对象,即数据转换成List
writeAsyncBuffer证明,数据写入是异步写入
每次写入数据都会在内部进行自动清理
写入时将传来的Mutation对象,即数据放在LinkedList中
put命令时:1、传入put对象
在传每个数据时候,都会进行autoflush自动清理,且需要一次rpc通信
2、传入put集合
将集合传入并且处理之后,进行一次flush,需要一次rpc通信
10000 关闭自动flush 关闭写前日志 关闭flush+WAL
----------------------------------------------------------------------
put 38,532ms 3,227ms 10,769ms 2,959ms
putBatch 3,507ms
在put中设置关闭自动flush
HTable.setAutoFlush(false,false);
在put中关闭WAL写入
put.setDurability(Durability.SKIP_WAL);
WAL
=============================
Write ahead log
适用于容灾,当机器发生断电,memstore数据很可能会丢失
WAL:数据写入到WAL则证明写入成功,存储对数据的修改,类似于hadoop的edits文件
如果数据库崩溃,可以有效地回放日志
***** 强烈建议用户不要关闭WAL
实现类:org.apache.hadoop.hbase.regionserver.wal.FSHLog
WAL在0.x版本中使用的是SeqFile
新版中不是
memstore:
================================
1、刷写到磁盘,阈值是5M
<property>
<name>hbase.hregion.preclose.flush.size</name>
<value>5242880</value>
</property>
2、关闭regionserver会使memstore强制刷写到磁盘
hbase基本命令:
============================
create_namespace 'ns1' //create_namespace 'ns1'
drop_namespace 'ns1' //drop_namespace 'ns1'
create 'ns1:t1','f1','f2' //create 'ns1:t1','f1','f2'
put 'ns1:t1','row1','f1:name','tom' //put 'ns1:t1','row1','f1:name','tom'
scan 'ns1:t1' //scan 'ns1:t1'
alter 'ns1:t5', 'f3' //添加列族 //alter 'ns1:t5' , 'f3'
hbase文件:
===========================
根级文件:/user/hbase
WALs //预写日志,和hadoop中的edits文件类似,作用是容灾
//通过以下配置进行周期性滚动
<property>
<name>hbase.regionserver.logroll.period</name>
<value>3600000</value>
<description>毫秒为单位,默认一小时</description>
</property>
oldWALs //旧的预写日志,当时间超过1小时,WAL会被回滚到此处
//十分钟后此文件会被清除,配置文件如下
<property>
<name>hbase.master.logcleaner.ttl</name>
<value>600000</value>
<description>毫秒为单位,默认十分钟</description>
</property>
hbase.id //hbase唯一id
hbase.version //hbase集群版本信息
corrupt //损坏日志文件
表级文件:/user/hbase/data/ns1/t1
.tabledesc/.tableinfo.0000000001 //串行化后的表描述符desc 'ns1:t1'
region级文件:
.regioninfo //region的描述,和tableinfo类似
recovered.edits //WAL日志恢复文件
当region足够大的时候,会自动切分成两个region,将原文件一分为二 region默认大小是10G
==================================================
<property>
<name>hbase.hregion.max.filesize</name>
<value>10737418240</value>
<description>
Maximum HStoreFile size. If any one of a column families' HStoreFiles has
grown to exceed this value, the hosting HRegion is split in two.</description>
</property>
注意:当所有region同时达到阈值时候,会产生切割风暴,严重消耗资源
1、设置预切割:
create 'ns1:t2', 'f1', SPLITS => [ '20', '30', '40']
2、设置一个非常大的值,然后手动切割
HFile:
=============================
是hbase的文件格式,以k/v形式存储,k/v均是字节数组
HFile包括以下内容:
读取或写入压缩块的存储空间。
每个块所指定的I/O操作的压缩编解码器
临时的key存储
临时的value存储
hfile索引,存在于内存,占用空间约为(56+AvgKeySize)*NumBlocks.
性能优化建议
**** 最小块大小,推荐在 8KB to 1MB之间
顺序读写推荐大块,但不便于随机访问(因为需要解压更多的数据)
小块便于随机读写,但是需要占用更多内存,但是创建起来更慢(因为块多,每次压缩都需要flush操作)
由于压缩缓存,最小块大小应该在20KB-30KB.
查看hfile:
====================
hbase hfile -a -b -p -v -h -f hdfs://mycluster/user/hbase/data/ns1/t1/77c55f893ecec3bbb2dfd02e3737c0c2/f1/5406345aabc640309be64abd21a2a500
key:row1/f1:age/1522634241444/Put/vlen=1/seqid=22
value:1
Cell:
=============================
* 1) row
* 2) column family
* 3) column qualifier
* 4) timestamp
* 5) type
* 6) MVCC version //multiple version concurrency contorl 多版本并发控制
* 7) value
scan操作:
=======================
限定列扫描:
HTable.getScanner(Bytes.toBytes("f1"),Bytes.toBytes("name")); //HTable.getScanner(Bytes.toBytes("f1"),Bytes.toBytes("name"));
指定列族扫描:
HTable.getScanner(Bytes.toBytes("f1")); //Htable.getScanner(Bytes.toBytes("f1"));
全表扫描
HTable.getScanner(Scanner) //Htable.getScanner(Scanner)
限定row范围进行扫描:
new Scan(Byte[] startKey,Byte[] stopKey) //new Scan(Byte[] startKey,Byte[] stopKey)
catch和batch
每次it.next()的时候,都会调用一次rpc,效率很差
避免此问题,引入cache的概念
catch:将指定数量行数进行缓存,到达阈值,通过一个RPC共同发给客户端
<property>
<name>hbase.client.scanner.caching</name>
<value>2147483647</value>
<description>每次查询缓存的数量</description>
</property>
cache 10 1000 10000 1
-------------------------------------------------------------
time 6,423ms 5,434ms 5,831ms 14,339ms
batch: 缓存指定的列数,到达阈值,通过一个RPC共同发给客户端
RPC次数: row x col / min(cache,rowNo) / min(batch, colNo) +1
result次数: row x col / min(batch, colNo) //只和batch有关
hbase过滤器:
===================================================
相当于sql的where子句
使用谓词下推的原理,通过server端过滤,返回给client数据
hbase过滤器中的比较方法:
CompareFilter.CompareOp.EQUAL ====> '='
使用过滤器:
1、初始化过滤器 //new RowFilter
2、初始化参数:比较方法 //CompareFilter.CompareOp.EQUAL
比较器 //
过滤器:
RowFilter //行过滤器 //RowFilter
FamilyFilter //列族过滤器 //FamilyFilter
QualifierFilter //列过滤器 //QualifierFilter
ValueFilter //值过滤器 //ValueFileter
SingleColumnValueFilter //单列值过滤器,通过搜索制定列族和制定col的值,返回整行数据 //SingleColumnValueFilter
//相当于select * from xxx where id=1;
TimeStampFilter //时间戳过滤器 //TimeStampFilter
比较器:
BinaryComparator //二进制比较器 //BianryComparator
RegexStringComparator //正则比较器, 比较时最好使用EQUAL //RegexStringComparator
SubstringComparator //子串比较器, 比较时最好使用EQUAL //SubstringComparator
/**
* 组合过滤器
* @throws Exception
*/
@Test
public void testCombineFilter() throws Exception {
long start = System.currentTimeMillis();
//初始化配置信息
//Configuration conf = new Configuration();
Configuration conf = HBaseConfiguration.create();
//入口点,创建连接
Connection conn = ConnectionFactory.createConnection(conf);
//通过getTable方法获取表的实例
HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t5"));
//HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t5"));
//Scan scan = new Scan();
Scan scan = new Scan();
//行过滤器,使用正则过滤器,过滤出111结尾的rowKey
RowFilter filter1 = new RowFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator(".*111"));
//RowFilter filter = new RowFilter(CompareFilter.CompareOpEQUAL,new RegexStringComparator(".*111"));
//列族过滤器,使用二进制过滤器,过滤出f2列族的数据
FamilyFilter filter2 = new FamilyFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("f2")));
//FamilyFilter filter2 = new FamilyFilter(ComparaFilter.CompareOp.EQUAL,new BinaryComparator(Bytes.toBytes("f2")));
FilterList filterList = new FilterList(); //FilterList filterList = new FilterList();
filterList.addFilter(filter1); //filterList.addFilter(filter1);
filterList.addFilter(filter2); //filterList.addFilter(filter2);
scan.setFilter(filterList); //scan.setFilter(filterList);
//通过扫描器得到结果集
ResultScanner rs = table.getScanner(scan); ResultScanner rs = table.getScanner(scan);
//得到迭代器
Iterator<Result> it = rs.iterator(); rs.iterator();
TestFilter.printVal(it); TestFilter.printVal(it);
table.close(); //table.close();
System.out.println(System.currentTimeMillis() - start);
}
public static void printVal(Iterator<Result> it){
while (it.hasNext()){
Result next = it.next();
List<Cell> cells = next.listCells(); //next.listCells();
for (Cell cell : cells) {
String val = Bytes.toString(CellUtil.cloneValue(cell)); //Bytes.toString(CellUtil.cloneValue(cell));
String clo = Bytes.toString(CellUtil.cloneQualifier(cell)); //Bytes.toString(CellUtil.cloneQualifier(cell))
String cf = Bytes.toString(CellUtil.cloneFamily(cell)); //Bytes.toString(CellUtil.cloneFamily(cell));
String row = Bytes.toString(CellUtil.cloneRow(cell)); //Bytes.toString(CellUtil.cloneRow(cell));
System.out.println(row+"/"+cf+"/"+clo+"/"+val);
}
}
}